Lorsque vous expliquez le combinateur Y dans le contexte de Haskell, il est généralement noté que l'implémentation simple ne vérifie pas le type dans Haskell en raison de son type récursif.
Par exemple, à partir de Rosettacode :
The obvious definition of the Y combinator in Haskell canot be used
because it contains an infinite recursive type (a = a -> b). Defining
a data type (Mu) allows this recursion to be broken.
newtype Mu a = Roll { unroll :: Mu a -> a }
fix :: (a -> a) -> a
fix = \f -> (\x -> f (unroll x x)) $ Roll (\x -> f (unroll x x))
Et en effet, la définition «évidente» ne tape pas check:
λ> let fix f g = (\x -> \a -> f (x x) a) (\x -> \a -> f (x x) a) g
<interactive>:10:33:
Occurs check: cannot construct the infinite type:
t2 = t2 -> t0 -> t1
Expected type: t2 -> t0 -> t1
Actual type: (t2 -> t0 -> t1) -> t0 -> t1
In the first argument of `x', namely `x'
In the first argument of `f', namely `(x x)'
In the expression: f (x x) a
<interactive>:10:57:
Occurs check: cannot construct the infinite type:
t2 = t2 -> t0 -> t1
In the first argument of `x', namely `x'
In the first argument of `f', namely `(x x)'
In the expression: f (x x) a
(0.01 secs, 1033328 bytes)
La même limitation existe dans Ocaml:
utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
Error: This expression has type 'a -> 'b but an expression was expected of type 'a
The type variable 'a occurs inside 'a -> 'b
Cependant, dans Ocaml, on peut autoriser les types récursifs en passant le -rectypes
commutateur:
-rectypes
Allow arbitrary recursive types during type-checking. By default, only recursive
types where the recursion goes through an object type are supported.
En utilisant -rectypes
, tout fonctionne:
utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
val fix : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
utop # let fact_improver partial n = if n = 0 then 1 else n*partial (n-1);;
val fact_improver : (int -> int) -> int -> int = <fun>
utop # (fix fact_improver) 5;;
- : int = 120
Étant curieux de connaître les systèmes de types et l'inférence de types, cela soulève des questions auxquelles je ne suis toujours pas en mesure de répondre.
- Tout d'abord, comment le vérificateur de type propose-t-il le type
t2 = t2 -> t0 -> t1
? Ayant trouvé ce type, je suppose que le problème est que le type (t2
) se réfère à lui-même sur le côté droit? - Deuxièmement, et peut-être le plus intéressant, quelle est la raison pour laquelle les systèmes de type Haskell / Ocaml interdisent cela? Je suppose qu'il y a une bonne raison car Ocaml ne le permettra pas non plus par défaut même s'il le peut gérer des types récursifs si on lui donne le
-rectypes
commutateur.
Si ce sont des sujets vraiment importants, j'apprécierais des pointeurs vers la littérature pertinente.