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

Jam*_*ter 7 lisp elisp lexical-closures dynamic-scope lexical-scope

有问题的性别是

(((lambda (b)
  (lambda (a)
    (+ b a))) 3) 5)
Run Code Online (Sandbox Code Playgroud)

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

Debugger entered--Lisp error: (invalid-function ((lambda (b) (lambda (a) (+ b a))) 3))
Run Code Online (Sandbox Code Playgroud)

它似乎在告诉我

((lambda (b)
  (lambda (a)
    (+ b a))) 3)
Run Code Online (Sandbox Code Playgroud)

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

(lambda (a) (+ b a))
Run Code Online (Sandbox Code Playgroud)

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

Jos*_*lor 14

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

句法问题

在的Emacs Lisp(以及其他的Lisp Lisp的2族),函数调用看起来像(f args...),其中f是一个或者具有函数值,或者一个符号lambda表达.例如,

(list 1 2 3)  
=> (1 2 3)
Run Code Online (Sandbox Code Playgroud)

因为list有一个功能绑定.也,

((lambda (x y) (list x x y y)) 1 2)
=> (1 1 2 2)
Run Code Online (Sandbox Code Playgroud)

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

(let ((id (lambda (x) x)))
  (id 3))
Run Code Online (Sandbox Code Playgroud)

信号a Lisp error: (void-function id).但我们可以使用funcall以下方法调用函数值

(let ((id (lambda (x) x)))
  (funcall id 3))
=> 3
Run Code Online (Sandbox Code Playgroud)

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

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

(((lambda (b)
    (lambda (a)
      (+ b a)))
  3)
 5)
Run Code Online (Sandbox Code Playgroud)

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

((lambda (b)
   (lambda (a)
     (+ b a)))
 3)
=> (lambda (a) (+ b a))
Run Code Online (Sandbox Code Playgroud)

现在,您还想调用返回的匿名函数5.我们funcall用来做到这一点:

(funcall ((lambda (b)
            (lambda (a)
              (+ b a)))
          3)
         5)
Run Code Online (Sandbox Code Playgroud)

范围问题

令人遗憾的是,这段代码产生了一个Lisp error: (void-variable b).是我们最终遇到动态与词汇范围问题的地方.因为变量b是动态绑定的,所以它的值不会保存在匿名函数中(lambda (a) (+ b a)).我们可以通过将整个表单包含在绑定b和查看发生的事情中来检查这是发生了什么:

(let ((b 100))
  (funcall ((lambda (b)
              (lambda (a)
                (+ b a)))
            3)
           5))
=> 105
Run Code Online (Sandbox Code Playgroud)

我不是一个Emacs Lisp黑客,所以我不确定在Emacs中获得词法封闭的最佳方法.我读过Emacs 24有它,但我现在仍然在23岁.但是,基于这个答案,我们可以lexical-let用来获得我们需要的结果:

(funcall ((lambda (b)
            (lexical-let ((b b))
              (lambda (a)
                (+ b a))))
          3)
         5)
=> 8
Run Code Online (Sandbox Code Playgroud)

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--))
Run Code Online (Sandbox Code Playgroud)

另外,词法绑定变量b与动态绑定名称相同并不重要b; 我们可以用(lexical-let ((c b)) ... (+ c a) ...),而不是.