【问题标题】:Emacs lisp: why does this sexp cause an invalid-function error?Emacs lisp:为什么这个 sexp 会导致无效函数错误?
【发布时间】:2013-06-10 17:21:08
【问题描述】:

有问题的性别是

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

在我看来,它应该评估为 8,而在其他 lisp(例如 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))

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

【问题讨论】:

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


    【解决方案1】:

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

    句法问题

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

    (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 表达式。但是,您不能做的是使用 which of 的值是函数的东西。

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

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

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

    注意:这是一种相当好的看待它的方式,但实际上事情要复杂一些。有关详细信息和函数间接等深奥部分,请参阅手册中的9.2 Kinds of Forms

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

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

    据我了解,其目的是首先使用参数3 调用(lambda (b) ...) 以获取匿名函数(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。但是,基于this answer,我们可以使用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) ...)

    【讨论】:

    • 哇,感谢您的详尽回答! (void-variable b) 是我所期待的,我只是不知道 Lisp-2 的问题。
    • @JamesPorter 很高兴为您提供帮助!我也学会了回答它!我知道 Lisp-1 与 Lisp-2 的问题,忘记了 Emacs 的动态作用域问题,并且学到了一些关于 Emacs 评估模型的好东西(函数间接位真的很有趣)。
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案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)了解更多信息。

      【讨论】:

      • 最外层的形式虽然不需要重写。 Emacs Lisp 与 Common Lisp、Scheme 和许多其他 Lisp 一样,确实允许在函数位置使用 lambda 表达式。这意味着,例如,((lambda (x) (+ x 3)) 5) 产生 8。请参阅 GNU Emacs Lisp 手册中的 lambda
      • 糟糕,我的错误;我误读了@JamesPorter 写的内容。关于 ((lambda (x) ...) y) 的部分仍然有效(即,lambda 表达式在函数位置是可以的),但这不是 OP 的问题。
      • 实际上,您提供的翻译并不等同于 OP 发布的内容(除了它运行并生成 8 并且问题中的代码没有) .由于您正在调用内部lambda 函数 调用外部lambda 函数,a 的绑定仍然有效。 OP 的原始代码(带有适当的funcalls)将返回一个匿名函数(lambda (a) ...),旨在关闭b 的值,然后使用5 调用它。在此时,动态范围存在问题,因为b 实际上不会被绑定。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-04
        相关资源
        最近更新 更多