Type checking and recursive types (Writing the Y combinator in Haskell/Ocaml)

Posted by beta on Programmers See other posts from Programmers or by beta
Published on 2013-10-27T20:43:44Z Indexed on 2013/10/27 22:00 UTC
Read the original article Hit count: 353

When explaining the Y combinator in the context of Haskell, it's usually noted that the straight-forward implementation won't type-check in Haskell because of its recursive type.

For example, from Rosettacode [1]:

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))

And indeed, the “obvious” definition does not type 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)

The same limitation exists 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

However, in Ocaml, one can allow recursive types by passing in the -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.

By using -rectypes, everything works:

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

Being curious about type systems and type inference, this raises some questions I'm still not able to answer.

  • First, how does the type checker come up with the type t2 = t2 -> t0 -> t1? Having come up with that type, I guess the problem is that the type (t2) refers to itself on the right side?
  • Second, and perhaps most interesting, what is the reason for the Haskell/Ocaml type systems to disallow this? I guess there is a good reason since Ocaml also will not allow it by default even if it can deal with recursive types if given the -rectypes switch.

If these are really big topics, I'd appreciate pointers to relevant literature.

[1] http://rosettacode.org/wiki/Y_combinator#Haskell

© Programmers or respective owner

Related posts about haskell

Related posts about type-systems