有趣的递归lambda示例

时间:2019-06-09 17:50:25

标签: javascript python lambda semantics

我偶然发现了一个有趣的递归lambda例子,我真的不明白为什么它会以这种方式起作用。

rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec 
rec = lambda x: x+1
print(f(10))  

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

令我惊讶的是,这两张纸都打印了100张而不是10张! (正如我所期望的)。

为什么重新分配rec会改变f函数的行为?当在lambda中捕获rec变量时,它不是指向lambda本身吗?


编辑。 大多数答案都解释了正在发生的事情,现在让我改一下这个问题,因为我正在寻找更深入的解释。

因此,在第一行中声明函数rec时,为什么函数主体中的rec不绑定到自身?

例如,如果您使用JavaScript并按照看似在其中一个答案中所建议的“相同”方式重写第一行,则会得到:

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

这个打印出10张!正如人们所期望的那样。

因此,在这种情况下,“内部rec”(在函数主体中)绑定到功能名称的rec,而不是查看rec变量,并且对变量rec的重新分配不会改变其行为。

所以我真正要问的是这些语言决定绑定lambda变量的机制。

我正在为一个班级项目编写自己的解释器,我遇到了相同的问题,即何时何地绑定这些变量。因此,我想了解它如何在流行语言中实现相似的功能。

4 个答案:

答案 0 :(得分:2)

您可以添加一些console.log并查看,首先用f调用10,然后用rec调用9,结果为{{1 }}。

10 * 10

保持var rec = function(a) { console.log('f', a); if (a == 0) return 1; return rec(a - 1) * a; }; f = rec; rec = function(a) { console.log('rec', a); return a + 1; } console.log(f(10));

rec

答案 1 :(得分:2)

我将为python解决这个问题,因为这是我所熟悉的。

首先,这种行为仅可能发生,因为python(并且我认为它类似于javascript)遵循了其绑定的后期绑定。 further read

后期绑定是在运行时查找闭包中的名称(与早期绑定不同,后者是在编译时查找名称)。

这允许通过重新绑定在运行时查找的变量(例如rec等函数)来在运行时更改行为。

然后,最后一步就是将lambda函数转换为等效的def语法,以便更清楚地了解实际行为。

代码:

rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec 
rec = lambda x: x+1
print(f(10)) 

可以等同于:

第一:

def somefunc(x):
    return 1 if x==0 else rec(x-1)*x 

注意,即使在干净的会话/内核中,python也不会抱怨rec不存在,因为它不会在函数定义期间查找值。后期绑定意味着除非调用此函数,否则python不会在乎rec是什么。

然后:

rec = somefunc
f = rec

def someotherfunc(x):
    return x + 1

f(10) #3628800

现在我们更改rec函数

rec = someotherfunc

然后观察f的后续函数调用将使用在函数调用中查找的后期绑定记录。

f(10) #100

PS。完整的代码添加如下:

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

答案 2 :(得分:0)

这3条语句可以总计为一条语句

rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec 
rec = lambda x: x+1

来自1和2

f = lambda x : 1 if x==0 else rec(x-1)*x

从上方&3

f = lambda x : 1 if x==0 else x*x

答案 3 :(得分:0)

我建议正确使用变量名,在这里您不需要重新分配

  

为什么使用第二个rec而不是第一个?

重新分配rec后,您的函数调用就会发生,因此您拥有rec as中的最新值

rec = function(a) {
  return a + 1;
}

var f = function(a) {
  if (a == 0) return 1;
  return rec(a - 1) * a;
}

var rec = function(a) {
  return a + 1;
}
console.log(f(10));