Y Combinator in Haskell
Asked Answered
C

5

53

Is it possible to write the Y Combinator in Haskell?

It seems like it would have an infinitely recursive type.

 Y :: f -> b -> c
 where f :: (f -> b -> c)

or something. Even a simple slightly factored factorial

factMaker _ 0 = 1
factMaker fn n = n * ((fn fn) (n -1)

{- to be called as
(factMaker factMaker) 5
-}

fails with "Occurs check: cannot construct the infinite type: t = t -> t2 -> t1"

(The Y combinator looks like this

(define Y
    (lambda (X)
      ((lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg))))
       (lambda (procedure)
         (X (lambda (arg) ((procedure procedure) arg)))))))

in scheme) Or, more succinctly as

(λ (f) ((λ (x) (f (λ (a) ((x x) a))))
        (λ (x) (f (λ (a) ((x x) a))))))

For the applicative order And

(λ (f) ((λ (x) (f (x x)))
        (λ (x) (f (x x)))))

Which is just a eta contraction away for the lazy version.

If you prefer short variable names.

Carlile answered 25/11, 2010 at 3:6 Comment(0)
C
24

Oh

this wiki page and This Stack Overflow answer seem to answer my question.
I will write up more of an explanation later.

Now, I've found something interesting about that Mu type. Consider S = Mu Bool.

data S = S (S -> Bool)

If one treats S as a set and that equals sign as isomorphism, then the equation becomes

S ⇋ S -> Bool ⇋ Powerset(S)

So S is the set of sets that are isomorphic to their powerset! But we know from Cantor's diagonal argument that the cardinality of Powerset(S) is always strictly greater than the cardinality of S, so they are never isomorphic. I think this is why you can now define a fixed point operator, even though you can't without one.

Carlile answered 25/11, 2010 at 3:17 Comment(1)
As the other answer shows, if you want a fixed-point combinator you can just write y f = f (y f) without giving a type and the compiler will infer the type (t -> t) -> t by itself. This is a different fixed-point combinator and not strictly speaking the y combinator, as the Wikipedia article shows.Nomography
P
60

Here's a non-recursive definition of the y-combinator in haskell:

newtype Mu a = Mu (Mu a -> a)
y f = (\h -> h $ Mu h) (\x -> f . (\(Mu g) -> g) x $ x)

hat tip

Plexor answered 4/5, 2011 at 14:44 Comment(1)
I've posted an answer to make thes codes more readable. The Answer Link. Please vote me up if it is helpful. Many thanks.Henotheism
C
49

The Y combinator can't be typed using Hindley-Milner types, the polymorphic lambda calculus on which Haskell's type system is based. You can prove this by appeal to the rules of the type system.

I don't know if it's possible to type the Y combinator by giving it a higher-rank type. It would surprise me, but I don't have a proof that it's not possible. (The key would be to identify a suitably polymorphic type for the lambda-bound x.)

If you want a fixed-point operator in Haskell, you can define one very easily because in Haskell, let-binding has fixed-point semantics:

fix :: (a -> a) -> a
fix f = f (fix f)

You can use this in the usual way to define functions and even some finite or infinite data structures.

It is also possible to use functions on recursive types to implement fixed points.

If you're interested in programming with fixed points, you want to read Bruce McAdam's technical report That About Wraps it Up.

Coincidental answered 4/5, 2011 at 19:29 Comment(2)
it is not possible to type the y combinator with a higher ranked type--system F is strongly normalizingTraditional
on the other hand, it is trivial to type the y combinator with recursive typesTraditional
H
33

The canonical definition of the Y combinator is as follows:

y = \f -> (\x -> f (x x)) (\x -> f (x x))

But it doesn't type check in Haskell because of the x x, since it would require an infinite type:

x :: a -> b -- x is a function
x :: a      -- x is applied to x
--------------------------------
a = a -> b  -- infinite type

If the type system were to allow such recursive types, it would make type checking undecidable (prone to infinite loops).

But the Y combinator will work if you force it to typecheck, e.g. by using unsafeCoerce :: a -> b:

import Unsafe.Coerce

y :: (a -> a) -> a
y = \f -> (\x -> f (unsafeCoerce x x)) (\x -> f (unsafeCoerce x x))

main = putStrLn $ y ("circular reasoning works because " ++)

This is unsafe (obviously). rampion's answer demonstrates a safer way to write a fixpoint combinator in Haskell without using recursion.

Haggard answered 4/5, 2011 at 15:53 Comment(2)
Nice! This is exactly what unsafeCoerce is for: bypassing the limitations of the type system.Inamorato
"Although this is well-typed, it doesn't type check in Haskell because of the x x." That statement is a contradiction. In fact, type theory was invented basically to forbid self-applications.Phagocytosis
C
24

Oh

this wiki page and This Stack Overflow answer seem to answer my question.
I will write up more of an explanation later.

Now, I've found something interesting about that Mu type. Consider S = Mu Bool.

data S = S (S -> Bool)

If one treats S as a set and that equals sign as isomorphism, then the equation becomes

S ⇋ S -> Bool ⇋ Powerset(S)

So S is the set of sets that are isomorphic to their powerset! But we know from Cantor's diagonal argument that the cardinality of Powerset(S) is always strictly greater than the cardinality of S, so they are never isomorphic. I think this is why you can now define a fixed point operator, even though you can't without one.

Carlile answered 25/11, 2010 at 3:17 Comment(1)
As the other answer shows, if you want a fixed-point combinator you can just write y f = f (y f) without giving a type and the compiler will infer the type (t -> t) -> t by itself. This is a different fixed-point combinator and not strictly speaking the y combinator, as the Wikipedia article shows.Nomography
H
4

Just to make rampion's code more readable:

-- Mu :: (Mu a -> a) -> Mu a
newtype Mu a = Mu (Mu a -> a) 

w :: (Mu a -> a) -> a
w h = h (Mu h)

y :: (a -> a) -> a
y f = w (\(Mu x) -> f (w x))
-- y f = f . y f

in which w stands for the omega combinator w = \x -> x x, and y stands for the y combinator y = \f -> w . (f w).

Henotheism answered 29/4, 2020 at 16:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.