avril
2012
Il m’est apparu que dodo a parfois besoin d’interpréter une valeur de plusieurs façons. Par exemple, une valeur numérique doit pouvoir être interprétée comme une série de bits pour y appliquer des opérations binaires. Cela peut se faire à l’aide de fonctions de conversion.
struct Int32bits is Countable, is Indexed, is Iterable { toInt => Integ32 } def Integ32 = new Integer(size: 32) { toBits => Int32bits } Integ32 n = 345 .n = n.toBits << 1 # assigne à n en convertissant avec toInt
Cependant, on peut vouloir interpréter la valeur comme une série d’octets au lieu de bits. Ou bien vouloir encore d’autres interprétations… La définition du type ne peut pas prévoir toutes les permutations.
De plus, la fonction de conversion peut être difficile à écrire en dodo.
Le langage C offre deux alternatives bien connues pour résoudre cela: la coercion de type et l’union. Ce sont deux mécanismes qui sont responsables pour grande partie du caractère dangereux de la programmation C, avec l’utilisation des pointeurs.
Il faut noter que Java a aussi la coercion de type (avec des limites) mais pas les pointeurs. Comme Java est généralement considéré sûr, on pourrait juste le copier.
Mais je n’aime pas me contenter de l’existant
Voici quelques idées de travail.
Comme je l’ai remarqué entre parenthèses plus haut, la coercion de type en Java est limitée. Il y a des coercions prédéfinies, comme (int) 'A'
qui convertit un caractère en son code Unicode (entier). Pour cela les fonctions de conversion font bien l’affaire.
Il y a aussi les coercions d’un type plus générique vers un type spécialisé, notamment pour le type Object
que l’on veut réinterpréter comme un type particulier. C’est une coercion dynamique dans le sens que Java vérifie que le type demandé est bien valide en cours d’exécution (grâce à la façon dont Java fait le polymorphisme). Si la coercion est invalide on a un ClassCastException
.
Ce type de coercion ne résout pas le problème que je me posais, qui était, par exemple, de réinterpréter un entier comme une série d’octets ou tout autre arrangement qui convient. Pour qu’une telle coercion soit valide il faut simplement que le nouveau type occupe la même place en mémoire que l’original, ou même moins de place.
Le type union de C semble une bonne solution. De plus, il permet de définir non pas juste deux, mais n’importe quel nombre de types qui peuvent occuper le même emplacement mémoire. Le coût est qu’il faut une variable pour désigner l’union, puis si le type désiré contient plusieurs champs une autre variable dedans pour la structure. On se retrouve avec des constructions très imbriquées.
Je propose de se limiter à juste deux types simultanés. De plus, pour éviter les changements incontrôlés sur une valeur qui est normalement régie par des règles particulières, la valeur originale est copiée avant d’être utilisée dans l’union. Cette copie peut être retrouvée à l’aide d’une fonction de conversion. J’appelle le type union alias
et la fonction de conversion unalias
:
def n' = alias(n) { byte[4] octets } .n'.octets[1] = n'.octets[3] .n = n' # conversion avec n'.unalias
Le problème est que l’on ne veut pas nécessairement exposer la représentation mémoire d’un objet, et surtout pas permettre de modifier un pointeur ou un attribut de taille en mémoire! On a déjà vu assez d’horreurs en C.
Comment peut-on s’assurer que la valeur retournée par alias
ou par unalias
est valide? Avez-vous des suggestions?