Les unités de mesure F # peuvent-elles être implémentées dans OCaml?

F # a une capacité d’unités de mesure (il y a plus de détails dans ce document de recherche ).

[] type unit-name [ = measure ] 

Cela permet de définir des unités telles que:

 type [] USD type [] EUR 

Et le code à écrire comme suit:

 let dollars = 25.0 let euros = 25.0 // Results in an error as the units differ if dollars > euros then printfn "Greater!" 

Il gère également les conversions (je suppose que cela signifie que Mesure a des fonctions définies pour que les Mesures soient multipliées, divisées et exponentiées):

 // Mass, grams. [] type g // Mass, kilograms. [] type kg let gramsPerKilogram : float = 1000.0 let convertGramsToKilograms (x : float) = x / gramsPerKilogram 

Cette fonctionnalité pourrait-elle être implémentée dans OCaml? Quelqu’un a suggéré que je regarde les types de fantômes mais ils ne semblent pas composer de la même manière que les unités.

(Divulgation: j’ai posé cette question sur Haskell il y a quelques mois, j’ai eu une discussion intéressante mais pas de réponse définitive au-delà de “probablement pas”).

Réponse rapide: Non, cela dépasse les capacités de l’inférence de type OCaml actuelle.

Pour l’expliquer un peu plus: l’inférence de type dans la plupart des langages fonctionnels est basée sur un concept appelé unification , qui n’est en réalité qu’une manière spécifique de résoudre des équations. Par exemple, déduire le type d’une expression comme

 let flij = (i, j) = List.nth l (i + j) 

implique d’abord la création d’un ensemble d’équations (où les types de l , i et j sont respectivement 'a , 'b et 'c et List.nth : 'd list -> int -> 'd , (=) : 'e -> 'e -> bool , (+) : int -> int -> int ):

 'e ~ 'b * 'c 'a ~ 'd list 'b ~ int 'c ~ int 'd ~ 'e 

et ensuite résoudre ces équations, ce qui donne 'a ~ (int * int) list et f : (int * int) list -> int -> int -> bool . Comme vous pouvez le voir, ces équations ne sont pas très difficiles à résoudre; en fait, la seule théorie qui sous-tend l’unification est l’ égalité syntaxique , c’est-à-dire si deux choses sont égales si et seulement si elles sont écrites de la même manière (avec une considération spéciale pour les variables non liées).

Le problème avec les unités de mesure est que les équations générées ne peuvent pas être résolues de manière unique en utilisant l’égalité syntaxique; la théorie correcte à utiliser est la théorie des groupes abéliens (inverses, élément d’identité, opération commutative). Par exemple, les unités de mesure m * s * s⁻¹ doivent être équivalentes à m . Il y a une autre complication en ce qui concerne les types principaux et la généralisation. Par exemple, les éléments suivants ne vérifient pas F #:

 fun x -> let yz = x / z in (y mass, y time) 

car y est supposé avoir le type float<'_a> -> float<'b * '_a⁻¹> , au lieu du type plus général float<'a> -> float<'b * 'a⁻¹>

Quoi qu’il en soit, pour plus d’informations, je vous recommande de lire le chapitre 3 de la thèse suivante:

http://adam.gundry.co.uk/pub/thesis/thesis-2013-12-03.pdf

Il ne peut pas être directement exprimé dans la syntaxe du type système, mais certains encodages sont possibles. On en a proposé un par exemple dans ce message sur la liste des caml https://sympa.inria.fr/sympa/arc/caml-list/2014-06/msg00069.html . Voici le contenu formulé de la réponse. Au fait, je ne vois aucune raison pour laquelle cela ne serait pas applicable à Haskell.

 module Unit : sig type +'a suc type (+'a, +'b) quantity val of_float : float -> ('a, 'a) quantity val metre : ('a, 'a suc) quantity val mul : ('a, 'b) quantity -> ('b, 'c) quantity -> ('a, 'c) quantity val add : ('a, 'b) quantity -> ('a, 'b) quantity -> ('a, 'b) quantity val neg : ('a, 'b) quantity -> ('a, 'b) quantity val inv : ('a, 'b) quantity -> ('b, 'a) quantity end = struct type 'a suc = unit type ('a, 'b) quantity = float let of_float x = x let metre = 1. let mul xy = x *. y let add xy = x +. y let neg x = 0. -. x let inv x = 1. /. x end 

Cela suit avec succès la dimension des quatities:

 # open Unit;; # let m10 = mul (of_float 10.) metre;; val m10 : ('a, 'a Unit.suc) Unit.quantity =  # let sum = add m10 m10;; val sum : ('a, 'a Unit.suc) Unit.quantity =  # let sq = mul m10 m10;; val sq : ('a, 'a Unit.suc Unit.suc) Unit.quantity =  # let cube = mul m10 (mul m10 m10);; val cube : ('a, 'a Unit.suc Unit.suc Unit.suc) Unit.quantity =  # let _ = add (mul sq (inv cube)) (inv m10);; - : ('a Unit.suc, 'a) Unit.quantity =  

et cela donnera des erreurs si elles ne sont pas utilisées correctement:

 # let _ = add sq cube;; Characters 15-19: let _ = add sq cube;; ^^^^ Error: This expression has type ('a, 'a Unit.suc Unit.suc Unit.suc) Unit.quantity but an expression was expected of type ('a, 'a Unit.suc Unit.suc) Unit.quantity The type variable 'a occurs inside 'a Unit.suc # let _ = add m10 (mul m10 m10);; Characters 16-29: let _ = add m10 (mul m10 m10);; ^^^^^^^^^^^^^ Error: This expression has type ('a, 'a Unit.suc Unit.suc) Unit.quantity but an expression was expected of type ('a, 'a Unit.suc) Unit.quantity The type variable 'a occurs inside 'a Unit.suc 

Cependant, il en découlera des types trop ressortingctifs pour certaines choses:

 # let sq x = mul xx;; val sq : ('a, 'a) Unit.quantity -> ('a, 'a) Unit.quantity =