Nous avons déjà vu l'existence de deux sortes de types :
Dans cette section, nous étudions cette deuxième sorte de types, qui sont appelés types paramétré.
Imaginons une petite base de données, décrite par l'interface suivante :
Nous avons un module Entry qui permet de représenter une entrée du répertoire, le type d'une entrée est Entry.t(nous ne connaissons pas sa représentation interne). Le type Directory.t représente les répertoires, c'est-à-dire des collections d'entrées. Là aussi, nous ne connaissons pas sa représentation interne.
On peut créer un petit répertoire ainsi :
Ce qui a pour effet d'afficher dans l'interpréteur :
On peut ensuite par exemple réordonner les entrées :
ou bien chercher une entrée :
Ce petit exemple possède déjà quelques gros défauts. Par exemple, que se passe-t-il si on cherche l'âge d'une personne qui n'est pas dans le répertoire ? La fonction Directory.find_age retourne un entier, mais si la personne n'est pas dans le répertoire elle n'aura rien à retourner. On voudrait donc avoir comme type Directory.find_age : name:string -> Directory.t -> int_or_nothing où int_or_nothing serait un type adapté.
Cela résoudrait le problème de Directory.find_age, mais on serait obligé de procéder de même pour la fonction Directory.find_nickname, il faudrait qu'elle retourne une valeur de type string_or_nothing.
De façon plus générale, on pourrait imaginer avoir pour tout type t besoin d'un type t_or_nothing. Mais on ne souhaite pas avoir à écrire chaque type deux fois. En Java, la solution serait de faire une classe générique pour représenter une valeur ou l'absence d'une valeur (cette classe existe dans Java 8 , c'est la classe Optional).
En OCaml, la solution va être de créer un type, qu'on peut appeler provisoirement or_nothing, qui dépend d'un autre type t, ce que l'on notera t or_nothing. Ici or_nothing définit en réalité non pas un type, mais une famille de types distincts : int or_nothing, string or_nothing, (float or_nothing) or_nothing, (int -> int) or_nothing, etc. Un tel type s'appelle type paramétré : or_nothing dépend d'un paramètre, qui peut être un type quelconque.
Le véritable nom du type or_nothing en OCaml est option. On peut donc corriger l'interface du module Directory ainsi :
Et on obtient alors :
Un autre problème est qu'on peut trier le répertoire par nom et par âge, mais pas par surnom. C'est un choix qui semble arbitraire. On pourrait vouloir trier selon n'importe quelle fonction de comparaison sur les entrées. En OCaml comme dans de nombreux langages, les fonctions de comparaisons prennent deux arguments et retourne un entier, on pourrait donc mettre dans le module Directory une fonction du type :
Ce n'est malheureusement pas très lisible. On pourrait donc introduire un nouveau type pour représenter les fonctions de comparaisons. Comme on peut faire des fonctions de comparaisons pour n'importe quel type, il semble naturel de faire un type paramétré par le type des valeurs que l'on souhaite comparer, ce qui nous amène à la solution :
Et remplacer les fonctions de tri de Directory par
Nous continuerons à développer les comparaisons dans la suite de ce cours. Continuons donc sur l'exemple du répertoire de noms en se posant la question de comment représenter le répertoire lui-même, c'est-à-dire la collection des entrées ? Il nous faut une structure de données pour regrouper toutes les entrées, par exemple un tableau. En OCaml, le type adapté est celui de liste, il nous faut donc une liste d'entrées. On imagine bien que si sur ce problème on veut avoir une collection d'entrées, sur un autre problème on voudra une collection d'entiers, de chaînes de caractères, ou bien d'un type quelconque adapté aux données. Là encore, on ne veut pas avoir à définir un nouveau type à chaque fois qu'on trouve une nouvelle application pour les listes. Le type des listes est donc paramétré par le contenu de la liste. On trouve donc les types int list, bool list, Entry.t list, (float list) list, etc.
Comme dans les exemples que nous venons de voir, les types paramétrés s'écrivent sous la forme :
On peut créer un nouveau type paramétré grâce au mot-clé type. Le paramètre est nécessairement une variable de type (donc commence par une apostrophe), puisqu'il s'agit de l'abstraction d'un type (tout comme les variables sont des abstractions de valeurs).
Il est possible de faire des types avec plusieurs paramêtres, en utilisant des parenthèses pour regrouper les paramètres et des virgules pour les séparer :
Regardons quelques exemples :
Les deux premiers montrent que les paires (plus généralement les tuples) et les fonctions sont des types paramétrés, avec deux paramètres. Simplement ils n'obéissent pas à la syntaxe usuelle de mettre la liste des paramètres suivi du nom du type, mais utilisent un opérateur infixe ('*' ou '->').
Les deux exemples suivants sont tirés de la librairie standard. Les listes sont les conteneurs de bases, elles ont un rôle similaire aux tableaux des langages procéduraux (mais ne s'utilisent pas de la même façon). Nous apprendrons à les utiliser plus tard. Les options, comme nous l'avons vu, codent l'absence ou la présence d'une valeur.
Notons que les types paramétrés permettent de profiter pleinement du polymorphisme : en définissant une fonction polymorphe en 'elt sur les listes 'elt list, on définit cette fonction pour tous les types possibles de listes, en une seule fois.
La plupart des librairies en OCaml utilisent d'une façon ou d'une autre des types paramétrés. Ils existent plusieurs grandes raisons de faire des types paramétrés, et nous allons donc les catégoriser dans la suite du cours, selon leur usage.