dimanche 31 mai 2009

Types

Les articles se succèdent rapidement à mesure que le développement de Lama avance...
Parfois une petite avancée se fait au prix de quelques heures de deboguage, parfois ça fonctionne du premier coup, comme par miracle. Dans les deux cas, ce serait l'occasion d'un cri guttural (à la manière du genre de musique que j'écoute) de victoire, si je n'avais pas de voisins.
Cela tombe bien, la dernière avancée concerne un aspect dont j'avais dit que je le développerais plus tard dans le message précédent: les types.

Dans Lama, il y aura quatre types primitifs: entier, réel, booléen et chaîne. Des librairies permettront d'ajouter d'autres types; ce seront essentiellement des types opaques ou abstraits comme les flux ("streams").
Le programmeur peut créer à partir des types de base ces propres types selon le mécanisme classique d'agrégation qui donne les "structures" que l'on voit, avec diverses syntaxes, dans la plupart des langages.
Ces structures sont d'un point de vue mathématique des produits cartésiens d'ensembles. Par exemple, un mathématicien dirait d'une structure "Point" constituée de deux champs pour les coordonnées X et Y, qu'elle est une paire de réels, autrement dit l'ensemble résultant du produit cartésien de R par R (R étant l'ensemble des réels).
Mais en programmation, les types sont un peu plus que cela: Le même produit cartésien de R par R peut aussi bien être utilisé pour représenter un point, un vecteur ou un complexe. Et il n'est pas question d'ajouter un vecteur à un point par exemple. Le type en programmation porte une information supplémentaire ayant trait à la nature de l'entité. En mathématiques, on n'ajoute pas non plus un vecteur à un point car l'opération + : Point, Vecteur -> Point n'est normalement pas définie.
Pourtant, aussi bien l'ensemble des points que l'ensemble des vecteurs sont tout deux des sous-ensembles de l'ensemble des paires d'entier. L'interdiction demeure parce que Point ou Vecteur ne sont pas vraiment des ensembles; ce sont plutôt des structures algébriques, qui combinent ensembles et opérations sur les éléments de ces ensembles.
Les types sont en fait des structures algébriques: ils sont constitués d'une partie structurelle (définie par le mot-clef "struct" en C, "type" en Lama) et d'une partie "opérationnelle", qui est l'ensemble des fonctions qui s'appliquent à ces éléments. La distinction entre élément (d'ensemble) et opérateur des mathématiques rejoint la distinction informatique entre programme et donnée.

Pour clore ce chapitre, remarquons qu'assez étrangement, le potentiel anti-bogue des systèmes de types semble sous-exploités. Je me souviens d'un merveilleux exemple donné dans un tutoriel du langage Anubis, qui illustre cela. Le thème est le calcul de grandeurs électriques; la puissance électrique, qui se mesure en Watts, est le produit de la tension (en Volts) par l'intensité (en Ampères), autrement dit P=U x I. L'exemple montrait comment utiliser les types pour éviter les courts-circuits (je transcris en Lama):

type Watts Real

type Volts Real

type Amps Real

proc calculerAmperage Volts u, Watts p -> Amps i
set i : p/u
end

let tensionGen : 12 as Volts

let puissanceGen : 1 as Volts

let amperageGen : calculerAmperage(tensionGen, puissanceGen
)

Le fin mot de l'histoire est que, en utilisant des types spécifiques pour les volts et les ampères plutôt que de simple réels comme on le fait souvent, une interversion entre les deux paramètres de calculAmperage ne fera pas d'étincelles lorsque l'on mettra le tout sous tension. Cela a l'air d'être trop simpliste pour être efficace? Et pourtant (anglais - crash d'une sonde martienne due à une confusion entre système métrique et unités anglo-saxones)

En Lama, le mot-clef "type" définit à la fois une structure et un nom de type - une combinaison du typedef et du struct de C:

type Complex [ Real: realOf , Real: imaOf ]

Ceci définit le type "Complex" qui est structurellement une paire de nombres réels. Dans l'exemple précédent sur les unités de mesure, la syntaxe était différente parce que les types définis étaient de simples "variantes" d'un type déjà existant. Il est possible que j'utilise plutôt un mot-clef spécifique pour cela (par exemple "subtype") par la suite, car dans ce cas le type défini, du fait de sa "parenté" directe avec un autre type, pourrait bénéficier de quelques privilèges.
Les symboles "realOf" et "imaOf" sont deux identificateurs de champs. Ce sont des quasi-fonctions définies automatiquement. "quasi fonctions" parce qu'on les utilise syntaxiquement comme des fonctions, mais qu'il est permis de leur affecter une valeur. "realOf" et "imaOf" sont définies comme prenant en paramètre un Complex et retournent un Real. Voyons leur usage concrètement sur la procédure d'addition de deux complexes:

proc + infix Complex a, Complex b -> Complex z
set z : [ realOf a + realOf b , imaOf a + imaOf b ] as Complex
end

Les crochets utilisés dans la définition des types sont également utilisés pour sa contrepartie, la formation des t-uples (ici une simple paire).
La partie "as Complex" est une syntaxe qui permet de doter une expression d'un type particulier. Ici, elle est utilisée pour donner le type "Complex" à notre paire d'entiers. Le compilateur n'acceptera de faire une telle opération que si le type original de l'expression à la même structure que le type que l'on veut lui donner.

Ce mot-clef "as" permet, vous l'aurez compris, de "transtyper" une expression, autrement dit de court-circuiter (en partie) la vérification des types. Sans cette syntaxe, il serait impossible de créer une entité de type Complex (plus généralement, il n'aurait pas été possible de créer des variables dotées d'un type défini par l'utilisateur). Il existe certainement des alternatives, mais toute brutale qu'elle soit, cette méthode est sans doute la plus élégante.
Notamment parce qu'elle permet de résoudre le problème de "l'initialisation oubliée" dont je parlais dans un précédent message. Le fait que le compilateur vérifie la compatibilité structurelle, contrairement au transtypage de C, permet d'intercepter ce genre d'erreur: si on ajoute un champ à la définition du type, toutes les opérations de transtypage deviennent incorrectes. Le programme ne se compilera pas tant qu'on aura pas ajouté ce champ dans tous les transtypages. Ce n'est certes pas parfait car la permutation de deux champs identiques passera entre les mailles du filet, mais c'est un progrès notable.


Aucun commentaire:

Enregistrer un commentaire