Nous avons déjà croisé dans plusieurs librairies des constructeurs spéciaux, commençant par le caractère `. Par exemple, dans Core.Std.Sys, la fonction is_file retourne une valeur parmi trois possibles : `Yes, `No, ou `Unknown.
On aurait pu définir le type suivant :
Cela aurait très bien fonctionné, mais il y a quelques désavantages. Ce type devrait être défini dans un module (disons Answer, chaque utilisation devra donc faire référence au module où il est défini, soit par l'utilisation d'un open Answer, soit par référence systématique au module (par exemple Answer.Yes). Comme c'est un type potentiellement utile dans de nombreux contextes, toutes les librairies souhaitant l'utiliser devrait soit dépendre de la librairie contenant ce type, soit le redéfinir. Mais s'il est redéfini, les différentes définitions ne sont pas compatibles.
OCaml possède un autre mécanisme pour produire des types sommes de façon plus libres. On peut à tout moment utiliser un constructeur quelconque (peut-être non-déclaré) à condition de le précéder du caractère `. Par exemple, `Yes, `True et `Unknown ne sont définis nulle part, mais peuvent être utilisés n'importe où. Ces constructeurs sont appelés variants polymorphes (les types sommes normaux sont souvent appelés variants).
Du coup, quel est le type de is_hero ? Il est important ici que is_hero retourne dans tous les cas des variants polymorphes. Le type de is_hero va alors contenir toute l'information nécessaire pour connaître quelle sorte de résultat la fonction peut produire. Spécifiquement, OCaml attribue un type listant tous les variants polymorphes possibles :
is_hero peut donc retourner l'un de ces trois variants. En fait la liste commence par le caractère >, qui signifie que la fonction peut retourner ces trois variants, mais qu'ils font peut-être parti d'une liste plus grande de variants; il aurait peut-être été possible de retourner `Sometimes, ou d'autres valeurs. Le type [> `No | `Unknown | `Yes ] doit donc se comprendre comme : un type qui contient au moins les 3 variants listés.
À l'inverse, considérons la fonction suivante :
Quel est le type du premier argument hero_status ? On devrait probablement avoir la même liste de trois constructeurs.
Cette fois, le signe est début de liste est <, ce qui veut dire que greets doit être utilisé avec un type qui admet au plus ces trois variants. On peut donc l'utiliser avec une valeur de type [ `Yes | `No ] ou une valeur de type [ `Yes | `No | `Unknown], mais pas avec une valeur de type [ `Yes | `Maybe ] car `Maybe n'est pas autorisé.
Comme les constructeurs normaux, les variants polymorphes peuvent avoir une information associée. Par exemple, la librairie Yojson utilise ce type pour représenter des informations encodables au format json :
Ici le type json est simplement une abbréviation de type : le type existe déjà mais on lui donne un nom pour le manipuler plus simplement. Par contre cela permet d'avoir un type inductif. Cela ressemble donc en tout point à un type somme inductif comme ceux que nous avons vu.
Les variants polymorphes apportent essentiellement de la flexibilité par rapport aux types algébriques usuels. En contre-partie, le typage des valeurs utilisant des variants polymorphes est plus complexe, puisque certains types sont plus grand que d'autres et l'algorithme de typage d'OCaml tient compte de ces relations, de quels sont les variants possibles et les variants interdits dans chaque fonction. Nous n'apprendrons pas à concevoir de nouveaux variants polymorphes et exploiter le potentiel qu'ils offrent. En revanche, ils sont utilisés dans de nombreuses librairies, on retiendra donc qu'il suffit de les utiliser comme des variants classiques.