I just stumbled upon an interesting example of recursive lambdas and I don't really understand why it works in this manner.
rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec
rec = lambda x: x+1
print(f(10))
Same in javascript.
var rec = function(a) {
if (a == 0) return 1;
return rec(a - 1) * a;
}
var f = rec
rec = function(a) {
return a + 1;
}
console.log(f(10));
To my surprise both of these print 100 instead of 10! (as I would expect).
Why does the reassignment of rec change the behavior of the f function? When the rec variable is captured in lambda, doesn't it refer to the lambda itself?
Edit. As most of the answers explain what is going on, let me rephrase the question because I am looking for a more in depth explanation.
So at the moment of the declaration of the function rec in the first line why doesn't the rec in the body of the function bind to itself?
For example if you take JavaScript and rewrite the first line in a seemingly "same" way as suggested in one of the answers you get:
var rec =function rec(a) {
if (a == 0) return 1;
return rec(a - 1) * a;
};
f = rec;
rec = function (a) {
return a + 1;
}
console.log(f(10));
This one prints out 10! as one would expect.
So in this case the "inner rec"(in the function body) binds to the rec of the function-name instead of looking at rec variable and the reassignment of the variable rec didn't change the behavior.
So what I am really asking is the mechanism by which those languages decide what to bind the variables in the lambdas.
I am writing an interpreter myself for a class project and I came across to the same question of when and where to bind those variables. So I wanted to understand how this works in popular languages to implement something similar.
I will address it for python because that is what i am familiar with.
First, this behaviour is only possible because python (and looks like javascript i presume) follows late-binding for their closures. further read
Late binding is when the names within a closure are looked up at runtime (unlike early binding where the names are looked up at compile time.)
What this allows is to mutate behaviours at run time, by rebinding variables that are being looked up at runtime(such as functions like rec).
The last step then is just converting the lambda functions into equivalent def
syntax so the real behaviour is clearer.
The code:
rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec
rec = lambda x: x+1
print(f(10))
Can be equivalent to:
First:
def somefunc(x):
return 1 if x==0 else rec(x-1)*x
Note, python will not complain about rec not existing even on a clean session/kernel because it does not look the value up during function definition. late binding means unless this function is called, python does not care about what rec is.
Then:
rec = somefunc
f = rec
def someotherfunc(x):
return x + 1
f(10) #3628800
Now we change the rec
function
rec = someotherfunc
And observe that subsequent function calls of f
will use the late-bound rec, that is looked up on the function call.
f(10) #100
PS. Complete code added below:
def somefunc(x):
return 1 if x==0 else rec(x-1)*x
rec = somefunc
f = rec
def someotherfunc(x):
return x + 1
f(10) #3628800
rec = someotherfunc
f(10) #100