当在Haskell的上下文中解释Y组合器时,通常会注意到,由于Haskell的递归类型,因此直接实现不会在Haskell中进行类型检查。
例如,从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))
实际上,“显而易见的”定义不会键入检查:
λ> 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)
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
但是,在Ocaml中,可以通过传递-rectypes
开关来允许递归类型:
-rectypes
Allow arbitrary recursive types during type-checking. By default, only recursive
types where the recursion goes through an object type are supported.
通过使用-rectypes
,一切正常:
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
对类型系统和类型推断感到好奇,这引发了一些我仍然无法回答的问题。
- 首先,类型检查器如何提出类型
t2 = t2 -> t0 -> t1
?提出该类型后,我想问题是类型(t2
)指向自身的右侧? - 其次,也许是最有趣的是,Haskell / Ocaml类型的系统不允许这样做的原因是什么?我想这是有充分的理由的,因为即使在给定开关的情况下,即使Ocaml 可以处理递归类型,它也默认不会允许它
-rectypes
。
如果这些真的是很重要的话题,我将感谢相关文献的指导。