In a lazy language like Lazy Racket you can use the normal order version, but not in any of the applicative order programming languages like Scheme. They will just go into an infinite loop.
The applicative version of Y is often called a Z combinator:
(define Z
(lambda (f)
((lambda (g) (g g))
(lambda (g)
(f (lambda args (apply (g g) args)))))))
Now the first thing that happens when this is applied is (g g)
and since you can always substitute a whole application with the expansion of it's body the body of the function can get rewritten to:
(define Z
(lambda (f)
((lambda (g)
(f (lambda args (apply (g g) args))))
(lambda (g)
(f (lambda args (apply (g g) args)))))))
I haven't really changed anything. It's just a little more code that does exactly the same. Notice this version uses apply
to support multiple argument functions. Imagine the Ackermann function:
(define ackermann
(lambda (m n)
(cond
((= m 0) (+ n 1))
((= n 0) (ackermann (- m 1) 1))
(else (ackermann (- m 1) (ackermann m (- n 1)))))))
(ackermann 3 6) ; ==> 509
This can be done with Z
like this:
((Z (lambda (ackermann)
(lambda (m n)
(cond
((= m 0) (+ n 1))
((= n 0) (ackermann (- m 1) 1))
(else (ackermann (- m 1) (ackermann m (- n 1))))))))
3
6) ; ==> 509
Notice the implementations is exactly the same and the difference is how the reference to itself is handled.
EDIT
So you are asking how the evaluation gets delayed. Well the normal order version looks like this:
(define Y
(lambda (f)
((lambda (g) (g g))
(lambda (g) (f (g g))))))
If you look at how this would be applied with an argument you'll notice that Y never returns since before it can apply f
in (f (g g))
it needs to evaluate (g g)
which in turn evaluates (f (g g))
etc. To salvage that we don't apply (g g)
right away. We know (g g)
becomes a function so we just give f
a function that when applied will generate the actual function and apply it. If you have a function add1
you can make a wrapper (lambda (x) (add1 x))
that you can use instead and it will work. In the same manner (lambda args (apply (g g) args))
is the same as (g g)
and you can see that by just applying substitution rules. The clue here is that this effectively stops the computation at each step until it's actually put into use.
Y ≡ (λy.(λx.y(xx))(λx.y(xx)))
version assumes lazy evaluation. One way to delay evaluation of a function is to wrap a function expressione
in(lambda (arg) (e arg))
, so in this case the function expression(procedure procedure)
is re-written as(lambda (arg) ((procedure procedure) arg))
. – Eskil(define (REC procedure iterations) ( (ISZERO iterations) 0 (+ iterations (procedure (- iterations 1))) ) )
How would I use this with Y-combinator in scheme.? – Liquidateif
). But in general, you would defineprocedure
as(Y (lambda (procedure) ....))
. – Eskil(procedure procedure)
doesn't work. – Stingaree