Quando si spiega il combinatore Y nel contesto di Haskell, di solito si nota che l'implementazione diretta non controllerà il tipo in Haskell a causa del suo tipo ricorsivo.
Ad esempio, da 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))
E in effetti, la definizione "ovvia" non digita 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 stessa limitazione esiste in 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
Tuttavia, in Ocaml, si possono consentire tipi ricorsivi passando nello -rectypes
switch:
-rectypes
Allow arbitrary recursive types during type-checking. By default, only recursive
types where the recursion goes through an object type are supported.
Usando -rectypes
, tutto funziona:
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
Essere curiosi di sistemi di tipo e inferenza di tipo, questo solleva alcune domande a cui non sono ancora in grado di rispondere.
- Innanzitutto, come viene eseguito il controllo del tipo con il tipo
t2 = t2 -> t0 -> t1
? Avendo escogitato quel tipo, immagino che il problema sia che type (t2
) si riferisce a se stesso sul lato destro? - Secondo, e forse il più interessante, qual è la ragione per cui i sistemi di tipo Haskell / Ocaml non lo consentono? Immagino che ci sia una buona ragione dal momento che anche Ocaml non lo consentirà per impostazione predefinita anche se può gestire tipi ricorsivi se gli viene dato lo
-rectypes
switch.
Se questi sono argomenti davvero importanti, apprezzerei i suggerimenti per la letteratura pertinente.