Cours 4

Htmligure
Version & licenses
Creative Commons License

Les variants polymorphes

Guyslain
Thursday, 21 July 2016

Retour au sommaire.

Variants polymorphes

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 :

  1. type yes_no_answer =
  2. | Yes
  3. | No
  4. | Maybe

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).

  1. let heroes = ["Batman"; "Superman"]
  2. let villains = ["Lex Luthor"; "Joker"]

  3. let is_hero name =
  4. if List.mem name heroes then `Yes
  5. else if List.mem name villains then `No
  6. else `Unknown

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 :

  1. val is_hero : string -> [> `No | `Unknown | `Yes ] = <fun>

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 :

  1. let greets hero_status name =
  2. match hero_status with
  3. | `Yes -> Printf.printf "May the Force be with you, %s!%!" name
  4. | `No -> Printf.printf "Go to Hell, %s!%!" name
  5. | `Unknown -> Printf.printf "Hello %s.%!" name

Quel est le type du premier argument hero_status ? On devrait probablement avoir la même liste de trois constructeurs.

  1. val greets : [< `No | `Unknown | `Yes ] -> string -> unit = <fun>

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 :

  1. type json =
  2. [ `Assoc of (string * json) list
  3. | `Bool of bool
  4. | `Float of float
  5. | `Int of int
  6. | `List of json list
  7. | `Null
  8. | `String of string
  9. ]

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.

Retour au sommaire.