patternpythonMinor
Understanding Applicative Evaluation Order with the Z-Combinator
Viewed 0 times
understandingorderevaluationthewithcombinatorapplicative
Problem
I am trying to understand how the Z-combinator (Y-combinator for applicative order languages) definition came about. As Python is applicative I am using Python for this.
So I know Python's evaluation order is applicative. But I seem to be overlooking something in how applicative order works. To compensate for applicative evaluation order I reasoned to build a Y-combinator that does not dive off into infinite recursion it would be sufficient to write it like this:
I arrived at this conclusion by first manually deriving
And then working my way backwards like this, adding in a lambda abstraction hat would delay the recursion until a value is passed in:
When I manually evaluate
in applicative order I get
```
factorial 3 = (lambda n : 1 if n == 0 else n * (lambda z : Y factorial_ z)(n-1)) 3
-> 3 * (lambda z : Y factorial_ z)(3-1)
-> 3 * (Y factorial_ 2)
So I know Python's evaluation order is applicative. But I seem to be overlooking something in how applicative order works. To compensate for applicative evaluation order I reasoned to build a Y-combinator that does not dive off into infinite recursion it would be sufficient to write it like this:
Y = lambda f : (lambda x : f( lambda z: x(x) (z) )) (lambda x : f(x(x)))I arrived at this conclusion by first manually deriving
Y g like soY g = (lambda f : (lambda x : f(x x)) (lambda x : f(x x))) g # Definition of Y
-> (lambda x : g(x x)) (lambda x : g(x x)) # beta reduction
-> g((lambda x : g(x x)) (lambda x : g(x x))) # beta reduction
-> g((lambda f : (lambda x : f(x x)) (lambda x : f(x x))) g) # lambda abstraction
= g(Y g) # put in YAnd then working my way backwards like this, adding in a lambda abstraction hat would delay the recursion until a value is passed in:
= g(lambda z : Y g z)
= g(lambda z : (lambda f (lambda x : f(x x)) (lambda x : f(x x))) g z)
-> g(lambda z : (lambda x : g(x x)) (lambda x : g(x x)) z)
-> (lambda x : g(lambda z : x x z)) (lambda x : g(x x))
Y g = (lambda f : (lambda x : f( lambda z : x x z)) (lambda x : f(x x))) gWhen I manually evaluate
factorial 3 wherefactorial_ = lambda f : lambda n : 1 if n == 0 else n * f(n-1)
factorial = Y(factorial_)in applicative order I get
```
factorial 3 = (lambda n : 1 if n == 0 else n * (lambda z : Y factorial_ z)(n-1)) 3
-> 3 * (lambda z : Y factorial_ z)(3-1)
-> 3 * (Y factorial_ 2)
Solution
Let's call your proposal
For convenience, we can rewrite it as
Now, when we invoke
Assume
However,
In order to prevent that, we need to add
X, instead:X = lambda f : (lambda x : f( lambda z: x(x) (z) )) (lambda x : f(x(x)))For convenience, we can rewrite it as
M = (lambda x : f(x(x))) # depends on f
X = lambda f : (lambda x : f( lambda z: x(x) (z) )) MNow, when we invoke
X(f), we getX(f) =
(lambda x : f( lambda z: x(x) (z) )) M =
f( lambda z: M(M) (z) )Assume
f "recurses", invoking its argument with some value, e.g. 5. Its code might look asc = M(M)(5)
do something with c
return somethingHowever,
M(M) above is exactly the non terminating Y(f). The additional lambda z: disappears after the first recursive call.In order to prevent that, we need to add
lambda z: inside M as well. Hence we get the actual Z combinator:Z = lambda f : (lambda x : f( lambda z: x(x) (z) )) (lambda x : f(lambda z: x(x) (z)))Code Snippets
X = lambda f : (lambda x : f( lambda z: x(x) (z) )) (lambda x : f(x(x)))M = (lambda x : f(x(x))) # depends on f
X = lambda f : (lambda x : f( lambda z: x(x) (z) )) MX(f) =
(lambda x : f( lambda z: x(x) (z) )) M =
f( lambda z: M(M) (z) )c = M(M)(5)
do something with c
return somethingZ = lambda f : (lambda x : f( lambda z: x(x) (z) )) (lambda x : f(lambda z: x(x) (z)))Context
StackExchange Computer Science Q#88903, answer score: 2
Revisions (0)
No revisions yet.