Cours 2

Htmligure
Version & licenses
Creative Commons License

Les types polymorphes

Guyslain
Thursday, 21 July 2016

Retour au sommaire.

Les types polymorphes.

Un exemple.

Étant maintenant bien entendu que OCaml déduit lui-même le type de chaque expression, comment fait-il s'il ne peut rien déduire sur le type d'une variable, par exemple parce qu'on ne l'utilise pas ? Dans le premier cours nous avons déjà vu un exemple :

  1. # let identity x = x;;
  2. val identity : 'a -> 'a = <fun>

La fonction identité retourne son argument, sans modification. En particulier, peu importe ce que x représente, calculer l'identité de x est possible. OCaml ne peut pas distinguer, par l'usage qui est fait de x, si x est de type entier, flottant, ou même si c'est un couple ou une fonction. Pourtant l'interprète a accepté la définition, et lui a donné un type : 'a -> 'a.

Que veut dire 'a, puisque ce n'est pas un type que l'on connait ? Les noms de types commençant par une apostrophe sont des variables de type, autrement dit ces noms servent à abstraire un autre type. Cela fonctionne de la même façon que la variable argument d'une fonction dans un programme : on peut ensuite remplacer la variable par une valeur (ainsi par un type), pour obtenir un type plus précis. Par exemple, si on remplace 'a par float, on obtient pour identity le type float -> float, mais si on le remplace par int, on obtient int -> int. On peut ainsi obtenir une infinité de types différents pour identity, par exemple (int * float) -> (int * float) ou ('a -> 'b) -> ('a -> 'b).

Cela veut-il dire que identity a plusieurs types simultanément ? Pas exactement. Le type 'a -> 'a se lit " pour tout type 'a, est une fonction de 'a dans 'a" . Il s'agit d'un seul type, mais faisant intervenir une quantification universelle (pour tout). Informellement, identity a le type d'avoir tous ces types.

Un type quantifié universellement est dit polymorphe.

Les variables de types.

Ainsi, les noms de types commençant par une apostrophe sont des variables de types. OCaml utilise les noms 'a, 'b, 'c, etc, mais tous les identifiants formés d'une apostrophe, suivi d'une lettre, suivi d'un nombre arbitraire de lettres et de chiffres sont autorisés. Comme toute variable, le bon nommage d'une variable de type est important, et on évitera donc de simplement copier l'usage du compilateur en choisissant des noms explicites pour les variables de types. Pour la fonction identité, on peut dire :

  1. let identity : 'any -> 'any = fun x -> x

Il n'y a pas de limites au nombre de variables de types distincts apparaissant dans une expression de type, ni sur le nombre d'occurence d'une même variable. Dès qu'une expression de type possède une variable de type, elle est considérée comme quantifiée universellement, sur toutes les variables de types apparaissant dans l'expression. Par exemple :

  1. val fst : ('left * 'right) -> 'left
  2. val snd : ('left * 'right) -> 'right

Le type de fst se lit : " pour tous types 'leftet 'right, est une fonction d'un couple de 'leftet 'right dans 'left.

Une variable de type est nécessairement quantifiée sur tous les types, on peut donc remplacer une variable par n'importe quel autre type, sans aucune restriction (contrairement aux variables des programmes qui doivent être remplacées par des valeurs du même type que la variable). En ce sens, en OCaml, les variables de types n'ont pas de type. Certains langages, par exemple Haskell, possèdent des mécanismes pour imposer des restrictions sur les variables de types, des sortes de types sur les types.

Des types polymorphes, pour quoi faire ?

Les types polymorphes, sous cette forme, n'existent pas dans les langages les plus couramment utilisés, il est donc légitime de se demander si c'est bien utile, puisque visiblement on s'en passe assez bien. Mais, s'en-passe-t'on vraiment si bien ?

En C, l'absence de polymorphisme pose problème dès qu'on souhaite manipuler des données sans vouloir faire d'hypothèse sur leur représentation. Par exemple, pour créer une structure de donnée, comme une liste simplement chainée. Soit il faut refaire le travail d'écrire la structure de donnée pour chaque type différent pour lequel on veut la structure de liste. Soit on triche en faisant une structure stockant des pointeurs, donc en manipulant des valeurs de type void *, ce qui a pour effet d'affaiblir la discipline de type. On fait des coercions (cast), et donc on perd les informations de types. En C++, la solution est d'utiliser les templates. C'est très puissant (plus que le polymorphisme) mais aussi bien plus complexe, et donc plus verbeux s'il s'agit de ne faire que du polymorphisme.

En Java, au début la solution était la même qu'en C, sauf qu'on coerçait vers Object plutôt que void *, mais avec les mêmes désagréments. Java a par contre été bien inspiré, dans son évolution, par les langages fonctionnelles, ce qui a amené l'introduction des génériques, qui sont une forme de polymorphisme. Par rapport à OCaml, la principale différence est que le polymorphisme est explicite en Java : on utilise les notations <?> pour préciser le polymorphisme. Cela autorise aussi d'ajouter des contraintes sur les types polymorphes, par exemple pour trier une collection :

  1. public static <T extends Comparable<? super T>> void sort(List<T> list)

sort est polymorphe par rapport à la classe T, mais T doit être comparable. On ne peut pas écrire l'équivalent directement en OCaml, mais on dispose d'autres mécanismes pour faire cela. Une des solutions possibles dans ce cas est similaire au qsort de C : passer la fonction de comparaison en argument de la fonction de tri. On retombe alors sur une fonction polymorphe.

  1. val List.sort : ('elt -> 'elt -> int) -> 'elt list -> 'elt list

En Python, le langage n'est pas typé, en quelque sorte toutes les fonctions sont polymorphes. En particulier lorsqu'elles devraient ne pas l'être. Les programmeurs en Python préfèrent avoir des erreurs à l'exécution plutôt que pendant le développement. Évidemment on développe beaucoup plus vite avec ce genre de pratique...

Ces quelques langages proposent donc des solutions plus ou moins bonnes, pour ce problème de typer des expressions pouvant manipuler indifférement plusieurs types de données. Nous allons voir dans la suite du cours de nombreux exemples de valeurs ayant des types polymorphes, qui illustreront petit-à-petit leur utilité.

Retour au sommaire.