Emacs lisp:为什么这个sexp会导致无效功能错误?

时间:2013-06-14 06:02:29

标签: lisp elisp lexical-scope dynamic-scope lexical-closures

有问题的性别是

(((lambda (b)
  (lambda (a)
    (+ b a))) 3) 5)

对我来说,看起来它应该评估为8,而在其他lisps(例如Racket)中它确实如此,但在elisp中,它会抛出此错误:

Debugger entered--Lisp error: (invalid-function ((lambda (b) (lambda (a) (+ b a))) 3))

似乎在告诉我

((lambda (b)
  (lambda (a)
    (+ b a))) 3)

不是有效的功能。这似乎是错误的,因为当我评估那个表达式时我得到了

(lambda (a) (+ b a))

对我来说看起来像是一个有效的功能。有谁知道为什么会这样?它与动态范围有关吗?

4 个答案:

答案 0 :(得分:14)

这里有两个问题。第一个是语法问题,正如其他答案所指出的那样。问题中提到的第二个问题是范围问题。

语法问题

在Emacs Lisp(以及Lisp-2系列中的其他Lisps)中,函数调用看起来像(f args...),其中f是具有函数值的符号,或者{ {3}}。例如,

(list 1 2 3)  
=> (1 2 3)

因为list具有功能绑定。另外,

((lambda (x y) (list x x y y)) 1 2)
=> (1 1 2 2)

因为(lambda (x y) (list x x y y))lambda表达式。但是,你不能做的是使用某种功能的价值。

(let ((id (lambda (x) x)))
  (id 3))

发出Lisp error: (void-function id)信号。但我们可以使用funcall调用函数值:

(let ((id (lambda (x) x)))
  (funcall id 3))
=> 3

注意:这是看待它的一种相当好的方式,但事实上事情有点复杂。有关详细信息和功能间接等深奥位,请参阅手册中的lambda expression

所以,现在我们可以解决语法问题了。原始代码重新格式化以指示哪些函数获取哪些参数:

(((lambda (b)
    (lambda (a)
      (+ b a)))
  3)
 5)

据我了解,目的是首先使用参数(lambda (b) ...)调用3以获取匿名函数(lambda (a) ...)。在Emacs Lisp中将是:

((lambda (b)
   (lambda (a)
     (+ b a)))
 3)
=> (lambda (a) (+ b a))

现在,您还希望使用5调用返回的匿名函数。我们使用funcall来执行此操作:

(funcall ((lambda (b)
            (lambda (a)
              (+ b a)))
          3)
         5)

范围问题

令人失望的是,此代码生成Lisp error: (void-variable b)这个是我们最终遇到动态与词汇范围问题的地方。由于变量b是动态绑定的,因此其值不会保留在匿名函数(lambda (a) (+ b a))中。我们可以通过将整个表单包含在绑定b并查看会发生什么的内容中来检查这是发生了什么:

(let ((b 100))
  (funcall ((lambda (b)
              (lambda (a)
                (+ b a)))
            3)
           5))
=> 105

我不是一个Emacs Lisp黑客,所以我不确定最好的方式来获取Emacs中的词法闭包。我读过Emacs 24有它,但我现在仍然在23岁。但是,根据9.2 Kinds of Forms,我们可以使用lexical-let来获得我们需要的结果:

(funcall ((lambda (b)
            (lexical-let ((b b))
              (lambda (a)
                (+ b a))))
          3)
         5)
=> 8

lexical-let建立了我们需要的词法绑定,以便匿名函数(lambda (a) ...)确实将3插入其中。更具体地说,我们引入了b的词法绑定,而(lambda (a) …)引用的是词法绑定。事实上,如果我们现在查看返回的匿名函数,它不仅仅是(lambda (a) (+ b a)),而是以更复杂(并且不太有用)的方式打印:

((lambda (b)
   (lexical-let ((b b))
     (lambda (a)
       (+ b a))))
 3)
=> (lambda (&rest --cl-rest--) (apply (lambda (G27322 a) (+ ... a)) (quote --b--) --cl-rest--))

另外,词汇绑定变量b与动态绑定b具有相同的名称并不重要;我们可以使用(lexical-let ((c b)) ... (+ c a) ...)代替。

答案 1 :(得分:3)

简短回答:这是Lisp-1 vs. Lisp-2

的影响

稍微长一点答案:对于应用程序,Emacs Lisp调用列表中第一个符号的函数值,但lambda只返回一个简单的函数对象。这意味着您必须使用funcall来使事情有效。 funcall将其第二个参数应用于其余参数(很像apply,但后者将其最后一个参数作为列表)。

(funcall (lambda (a) (funcall (lambda (b) (+ a b)) 5)) 3)
=> 8

请查看emacs文档(例如C-h f funcall)以获取更多信息。

答案 2 :(得分:2)

car的{​​{1}}既不是关联有效函数定义的符号,也不是符号(((lambda (b) (lambda (a) (+ b a))) 3) 5),因此它不是有效的函数调用。

答案 3 :(得分:1)

FWIW,另一个重新安排会给你这个:

((lambda (b)
   ((lambda (a)
      (+ b a))
    3))
 5)

=> 8

(但我认为这不是你想要做的)