41 votes

Variantes ou Variantes polymorphes?

J'ai remarqué que, parmi les programmeurs OCaml que je connais, certains d'entre eux utilisent toujours des variantes polymorphiques (variantes qui ne sont pas déclarées, préfixées par un backquote), tandis que d'autres ne utilisent jamais de variantes polymorphiques et préfèrent les variantes déclarées dans les types.

À part pour des raisons de performance (les variantes polymorphiques sont actuellement compilées moins efficacement que les variantes simples), comment les développeurs OCaml experts choisissent-ils entre les deux ?

1 votes

Je pense que le nom préféré est maintenant "variantes ouvertes".

3 votes

"La variante ouverte" est clairement plus facile à comprendre intuitivement que "la variante polymorphe"

0 votes

Je pense que "la variante ouverte" fait référence de manière plus précise à des types comme exn.

44voto

Daniel Bünzli Points 2653

Mon utilisation peut être divisée en 5 catégories suivantes. 1. interface 2. modularité 3. lisibilité 4. brièveté 5. astuces

  1. Si le type de variante est uniquement interne au module, j'utilise des variantes régulières, car comme vous l'avez dit, elles sont compilées de manière plus efficace.
  2. Si le type de variante est exporté dans l'interface et que je sens que certaines situations pourraient apparaître dans d'autres modules mais qu'il ne serait pas nécessaire de les rendre dépendantes du module, j'utilise des variantes polymorphes car elles ne sont pas liées au système de namespace du module. Exemples : le type encoding de Xmlm. Avoir également le type signal en tant que type de variante signifie que vous pouvez développer des modules en utilisant la même idée pour le traitement XML sans introduire de dépendance à Xmlm.
  3. Si le type de variante est exporté dans l'interface, je trouve parfois trop verbeux d'utiliser des variantes régulières lorsque les valeurs du type de variante sont données aux fonctions du module. Exemple : le type version de Uuidm. Au lieu d'écrire Uuidm.create Uuidm.V4, vous pouvez simplement écrire Uuidm.create `V4, ce qui est aussi clair et moins verbeux.
  4. Parfois une fonction particulière peut renvoyer différents cas. Si ces cas ne sont utilisés que par cette fonction, je déclare le type de fonction dans l'interface sans avoir à introduire une définition de type. Par exemple parse : string -> [`Error of string | `Ok of t]
  5. Les variantes polymorphes et leur sous-typage vous permettent de faire respecter statiquement des invariants avec des types fantômes. En outre, la possibilité de les définir de manière progressive peut être utile, à la fois pour faire respecter des invariants statiquement et à des fins de documentation.

Enfin, j'utilise parfois des variantes polymorphes dans l'implémentation d'un module selon le point 4., mais sans qu'elles n'apparaissent dans l'interface. Je déconseille cette utilisation à moins que vous ne déclariez les variantes polymorphes et ne les refermiez car cela affaiblit la discipline de typage statique.

17voto

Martin Jambon Points 1522

La seule raison pour laquelle j'utilise des variantes polymorphes dans la plupart des interfaces de modules est de contourner les problèmes de nommage des variantes classiques.

Si ce qui suit pouvait fonctionner, les variantes polymorphes ne seraient plus utiles dans la majorité des cas :

type t1 = String of string | Int of int | Bool of bool | List of t1 list
type t2 = String of string | Int of int | Autre

let simplifier x =
  match (x : t1) with
      String s -> String s
    | Int n -> Int n
    | Bool \_
    | List \_ -> Autre

2014-02-21 Mise à jour : le code ci-dessus est maintenant valide en OCaml 4.01. Hourra !

13voto

Yttrill Points 2461

Il n'est pas vrai que les variantes polymorphiques sont toujours moins efficaces. En utilisant l'exemple de Martin :

type base = [`String of string | `Int of int]
type t1 = [base | `Bool of bool | `List of t1 list]
type t2 = [base | `Other]

let simplify (x:t1):t2 = match x with
| #base as b -> b
| `Bool _ | `List _ -> `Other

Pour faire cela avec des variantes standard, il faut deux types distincts et un recodage complet, avec les variantes polymorphiques, le cas de base est physiquement invariant. Cette fonctionnalité prend vraiment tout son sens lors de l'utilisation de la récursion ouverte pour la réécriture des termes :

type leaf = [`String of string | `Int of int]
type 'b base = [leaf | `List of 'b list]
type t1 = [t1 base | `Bool of bool ]
type t2 = [t2 base | `Other]

let rec simplify (x:t1):t2 = match x with
| #leaf as x -> x
| `List t -> `List (List.map simplify t)
| `Bool _ -> `Other

et les avantages sont encore plus grands lorsque les fonctions de réécriture sont également factorisées avec une récursion ouverte.

Malheureusement, l'inférence de type Hindley-Milner d'Ocaml n'est pas assez forte pour faire ce genre de chose sans typage explicite, ce qui nécessite une factorisation soigneuse des types, ce qui rend le prototypage difficile. De plus, des coercitions explicites sont parfois nécessaires.

Le gros inconvénient de cette technique est que pour les termes avec plusieurs paramètres, on se retrouve rapidement avec une explosion combinatoire des types plutôt confuse, et à la fin, il est plus facile d'abandonner l'application statique et d'utiliser un type "évier de cuisine" avec des jokers et des exceptions (c'est-à-dire une typage dynamique).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X