Al explicar el combinador Y en el contexto de Haskell, generalmente se observa que la implementación sencilla no verificará en Haskell debido a su tipo recursivo.
Por ejemplo, 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))
Y, de hecho, la definición "obvia" no escribe 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 misma limitación existe en 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
Sin embargo, en Ocaml, uno puede permitir tipos recursivos al pasar el -rectypes
interruptor:
-rectypes
Allow arbitrary recursive types during type-checking. By default, only recursive
types where the recursion goes through an object type are supported.
Al usar -rectypes
, todo funciona:
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
Siendo curioso sobre los sistemas de tipos y la inferencia de tipos, esto plantea algunas preguntas que todavía no puedo responder.
- Primero, ¿cómo aparece el verificador de tipos
t2 = t2 -> t0 -> t1
? Habiendo creado ese tipo, supongo que el problema es que el tipo (t2
) se refiere a sí mismo en el lado derecho. - En segundo lugar, y quizás lo más interesante, ¿cuál es la razón por la cual los sistemas de tipo Haskell / Ocaml no permiten esto? Supongo que es una buena razón, ya Ocaml también no permitirá que de forma predeterminada, incluso si se puede tratar con tipos recursivos si se les da el
-rectypes
interruptor.
Si estos son temas realmente importantes, agradecería los consejos sobre literatura relevante.
En OCaml, debe pasar
-rectypes
como parámetro al compilador (o ingresar#rectypes;;
en el nivel superior). Hablando en términos generales, esto desactivará la "verificación de ocurrencia" durante la unificación. La situaciónThe type variable 'a occurs inside 'a -> 'b
ya no será un problema. El sistema de tipos seguirá siendo "correcto" (sonido, etc.), los árboles infinitos que surgen como tipos a veces se denominan "árboles racionales". El sistema de tipos se debilita, es decir, se hace imposible detectar algunos errores del programador.Consulte mi conferencia sobre cálculo lambda (comenzando en la diapositiva 27) para obtener más información sobre operadores de punto fijo con ejemplos en OCaml.
fuente