Emacs Lisp 可以将 lambda 形式分配给 Scheme 之类的变量吗?

Can Emacs Lisp assign a lambda form to a variable like Scheme?

在调查 Emacs Lisp 的符号单元时,我发现对于像这样的示例函数

(defun a (&rest x)
    x)

我可以调用 (symbol-function 'a),returns (lambda (&rest x) x)。如果需要,我可以使用它

> ((lambda (&rest x) x) 1 2 3 4 5)
(1 2 3 4 5)

与上面的原始功能具有相同的功能。现在,这让我想起了 Scheme,其中 lambda 表达式是函数的主体,并被分配给具有 Scheme 通用 define 的变量名。例如

(define atom?
    (lambda (x)
        (and (not (pair? x)) (not (null? x)))))

只是将 lambda 表达式分配给 atom? —— 现在 atom? 是一个函数。那么 elisp 可以做到这一点吗,即将一个 lambda 表达式分配给一个符号,然后将它用作一个函数?我试过了

(setq new-a (lambda (&rest x) x))

如果我尝试将它用作函数,它会给出 (void-function new-a)。有没有办法在这个问题上模仿Scheme世界?看来一定是有办法的。如果我们不能将此 lambda 表达式转换为函数,为什么 a 的函数单元格包含 (lambda (&rest x) x)

scheme 和 emacs lisp(以及大多数其他 lisp)之间的一个重要区别是 scheme 有一个单独的名称space,而 emacs lisp 有单独的名称space用于函数和变量。被评估的列表形式中的第一个位置命名一个函数,该名称在函数名称 space 中查找。在 scheme 中,所有名称都在同一个 space 中,绑定到名称的值会在出现的任何地方被查找和使用。

这意味着在 emacs lisp 中你可以这样:

(defun f (x) (+ x x))
(setq f 2)
(f f) ;=> 4

这在 scheme 中是不可能的,这里只有一个 f 如果你设置它的值,它会从(比如说)一个函数变成一个数字。

在 emacs lisp 中有不同的处理方式。

一种是使用funcallapply等函数,这些函数接受一个函数和一些参数并将函数应用于参数,如:

(setq f (lambda (x) (+ x x)))
(funcall f 2) ;=> 4

另一种方法是操纵函数名称 f 的含义。有一个名为 fset 的函数允许您将函数附加到名称(在函数名称 space 中):

(fset 'f (lambda (x) (+ x x x)))
(f 2) ;=> 6

请注意,fset 作用于名称(又名符号),因此名称 f 需要被引用,否则它将被读取为变量的值。这就是为什么变量函数被称为 setq,"q" 代表 "quoted" 所以 setq 实际上是一个特殊的函数,它引用了它的第一个参数,这样程序员不必这样做。有一个等效的普通函数叫做 set,它不做任何引用,如:

(setq x 1)  ; x is 1
(set 'x 2)  ; x is 2
(setq x 'x) ; x is the symbol x
(set x 3)   ; x is now 3

最后一种形式可能看起来令人困惑,但由于 set 是一种正常形式,它将查找变量 x 的值,该值是符号 x 并且然后命名将要更改的变量(即 x)。因此 set 的一个优点是可以设置您不知道其名称但更确切地说是通勤的变量。

这是 的附录。另一个答案解释了 lisp-1s(lisps 具有用于函数和变量绑定的单个命名空间的 lisps)和 lisp-2s(lisps 具有用于函数绑定的单独命名空间的 lisps)之间的区别。

我想解释为什么 lisp-2 可以让事情变得更好,尤其是为什么它在历史上这样做。

首先让我们考虑一下 Scheme 代码:

(define (foo x)
  (let ([car (car x)])
    ... in here (car ...) is probably not going to get the car
    (bar car)))


(define (bar thing)
  ... but in here, car is what you expect ...)

因此,在 foo 中,我已将 car 绑定到参数的汽车。在 Scheme 中,这可能是一种糟糕的风格,这意味着,在该绑定的主体中,car 在用作函数时可能不会执行您期望的操作。但是这个问题只在 car 的绑定的词法范围内有关系:例如 bar 没有关系。

现在,在 Common Lisp 中,我可以编写等效代码:

(defun foo (x)
  (let ((car (car x)))
    ... (car ...) is fine in here ...
    (bar car)))

(defun bar (thing)
  ... and here ...)

所以这样更好一些,也许:在 car 的绑定主体中,将 car 用作函数仍然没问题,而且编译器确实可以做出非常强大的假设car 是由语言定义的函数,CL 在标准中有措辞以确保这始终为真。

这意味着,在风格上,在 CL 中,这样的东西可能是可以的。特别是我经常做这样的事情:

(defmethod manipulate-thing ((thing cons))
  (destructuring-bind (car . cdr) thing
    ...use car & cdr...))

而且我认为这很好:在 Scheme 中,等价物会很糟糕。

这就是 lisp-2 非常方便的原因之一。然而,还有一个更强大的适用于 CL 但适用于 elisp。

在 elisp 中考虑以下代码:

(defun foo (x)
  (let ((car (car x))
        (cdr (cdr x)))
    (bar car cdr)))

(defun bar (thing-1 thing-2)
  ...)

关于 elisp,现在有一件重要的事情需要了解:默认情况下它是动态范围的。这意味着,当从 foo 调用 bar 时,carcar 的绑定在 bar.

例如,如果我将 bar 重新定义为:

(defun bar (thing-1 thing-2)
  (cons cdr thing-1))

然后:

ELISP> (foo '(1 . 2))
(2 . 1)

所以,现在,想想如果 elisp 是 lisp-1 会发生什么:foo 调用的任何函数都会发现 (car x) 没有按预期执行!这是一场灾难:这意味着如果我将一个函数的名称——任何函数,包括我可能不知道存在的函数——绑定为一个变量,那么该绑定的动态范围内的任何代码都不会执行它应该执行的操作。

因此,对于具有动态范围的 Lisp,正如 elisp 过去和现在默认情况下一样,成为 lisp-1 是一场灾难。好吧,从历史上看,很多 lisp 实现确实具有动态范围(至少在解释代码中:编译代码通常具有不同的范围规则,并且范围规则通常有些不连贯)。所以对于那些实现,成为 lisp-2 是一个非常重要的优势。而且,当然,一旦存在大量假设 lisp-2-ness 的代码,即使在词法范围语言中具有优势,以兼容性为目标的语言(例如 CL)也更容易保持 lisp-2s不太清楚。


注意:很久以前我使用过 lisp,它是动态范围的(至少在解释器中?)和 lisp-1。我至少有一次非常糟糕的经历(我认为需要硬重置一台多用户机器,这台机器因为分页太多而变得紧张,这让我不受所有其他用户的欢迎)因此。

描述语言有两种方式,一种更抽象,另一种更具体。

一个例子是这样说的,在Scheme中,

(define (f x) (+ x x x))

导致评价

(f y)

的评价相同
((lambda (x) (+ x x x)) y)

的评价相同
(let ((x y)) (+ x x x))

的评价相同
(+ y y y)

请注意,我们还没有说明所有这些是如何实现的。


另一种方法是参考机器中特定实现的细节

因此,对于 Common Lisp / Emacs Lisp,我们首先讨论语言的 运行 时间系统中真正的 内存对象 ,称为 符号

一个symbol有这个和那个--它就像一个有几个字段的结构,可以用一些数据填充或留空。符号的内存表示,内存中的实际结构,有一个名为"variable cell"的字段,它有一个名为"function cell"[=90的字段=],你有什么。

当我们调用(fset 'f (lambda (x) (+ x x x)))时,我们将计算(lambda (x) (+ x x x))形式的结果存储在符号F"function cell"[=90中=].

如果我们在那之后调用 (+ f 2)F"variable cell" 会被调查以找出它的值 作为变量,导致"undefined variable"错误。

如果我们调用 (f 2)F"function cell" 会被调查以找出其值 一个函数(这也是(symbol-function 'f)也在做的)。发现它保存了评估 (lambda (x) (+ x x x)) 的结果,因此进行了等同于 ((lambda (x) (+ x x x)) 2) 的函数调用。


编辑: 如果您想调用存储在符号“变量 单元格”中的函数作为 function,你需要使用funcall,它将符号的值作为变量访问,并将其用作函数。在 Common Lisp (CLISP) 中,另一种 Lisp-2 语言:

[14]> (setq a (lambda (x) (+ x x x)))
#<FUNCTION :LAMBDA (X) (+ X X X)>
[15]> (funcall a 3)
9
[16]> (symbol-value 'a)
#<FUNCTION :LAMBDA (X) (+ X X X)>
[17]> (let ((x (symbol-value 'a))) (funcall x 3))
9
[18]> (let ((x 1)) (setf (symbol-function 'x) (symbol-value 'a)) (x 3))
9
  • setf 是 Common Lisp 的 "set place" 原语
  • (setq a <val>) 等同于 (setf (symbol-value 'a) <val>)
  • symbol-value 访问符号的变量单元格(其值作为变量)
  • symbol-function 访问符号的函数单元格(其值作为函数)
  • (funcall x 3) 获取 (symbol-value 'x) 并使用 3 作为参数调用结果
  • (x 3) 获取 (symbol-function 'x) 并使用 3 作为参数调用结果