什么是“向下的乐趣”?

Jamie Zawinski在他(1997)的文章“java sucks”中使用了这个术语,就好像你应该知道这意味着什么:

我真的很讨厌缺乏向下的东西, 匿名课是一个蹩脚的替代品。 (我可以没有长寿的closures生活,但我觉得缺乏function指针是巨大的痛苦。)

这似乎是Lisper的俚语,我可以在这里find以下简短的定义,但不知何故,我想我仍然不明白:

许多closures只在绑定的范围内使用; 这些被称为Lisp说法中的“向下的乐趣”。

如果不是因为史蒂夫·叶格 ,我现在只是觉得自己很愚蠢,但是看起来好像可以问一下:

杰米·扎文斯基是英雄。 一个活的传奇 […] 一个人可以使用“向下funargs”一词,然后瞪着你只是敢于让他解释它,你克雷廷。

– XEmacs死了,XEmacs万岁

那么有没有一个Lisper可以为我这样的C程序员编译?

向下的funargs是本地函数,不返回或以其他方式离开其声明范围。 它们只能从当前范围向下传递给其他函数。

两个例子。 这是一个向下的乐趣:

function () { var a = 42; var f = function () { return a + 1; } foo(f); // `foo` is a function declared somewhere else. } 

虽然这不是:

 function () { var a = 42; var f = function () { return a + 1; } return f; } 

为了更好地理解术语的来源,你需要知道一些历史。

一个老的Lisp黑客之所以能够区分下面的 funargs是因为向下的funargs很容易在传统的缺乏词汇variables的Lisp中实现,而一般的情况很难。

传统上,在Lisp解释器中通过向环境添加绑定 (variables的符号名称,与其值匹配)来实现本地variables。 使用关联列表来实现这样的环境很简单。 每个函数都有自己的环境和一个指向父函数环境的指针。 通过查看当前环境来解决variables引用问题,如果没有find,那么在父环境中等等,直到达到全局环境。

在这样的实现中,局部variables映射具有相同名称的全局variables。 例如,在Emacs Lisp中, print-length是一个全局variables,它在缩写之前指定要打印的列表的最大长度。 通过将这个variables绑定到一个函数上,你可以改变这个函数中的打印语句的行为:

 (defun foo()(print'(1 2 3 4 5 6))); 输出取决于打印长度的值

 (foo); 使用打印长度的全局值
   ==>(1 2 3 4 5 6)

 (((print-length 3))(foo)); 在foo的调用周围绑定本地打印长度。
   ==>(1 2 3 ...)

你可以看到,在这样的实现中, 向下的 funargs实际上很容易实现,因为当创build函数时,处于函数环境中的variables在评估时仍然处于函数的环境中。

像这样的variables称为特殊variables或dynamicvariables,您可以使用special声明在Common Lisp中创build它们。

在Common Lisp中:

 (let ((a 3)) (mapcar (lambda (b) (+ ab)) (list 1 2 3 4))) -> (4 5 6 7) 

在上面的forms中,lambda函数向下传递。 当由高阶函数MAPCAR(它获得一个函数和一个值列表作为参数,然后将该函数应用于列表的每个元素并返回结果列表)调用时,lambda函数仍然引用variables'L'expression式。 但是这一切都发生在LETexpression式中。

与以上版本比较:

 (mapcar (let ((a 3)) (lambda (b) (+ ab))) (list 1 2 3 4)) 

这里的lambda函数是从LET返回的。 向上一点。 然后它被传递给MAPCAR。 当MAPCAR调用lambda函数时,其周围的LET不再执行 – 函数仍然需要从LET引用variables“a”。

Wiki上有一篇很漂亮的描述性文章,叫做Funarg问题

“一个向下的funarg也可以指当一个函数没有实际执行时的一个函数的状态,然而,因为根据定义,在创build它的函数的执行过程中,存在一个向下的函数,函数的激活logging通常仍然可以存储在堆栈中。“