2

在研究 Emacs Lisp 的符号单元时,我发现对于一个示例函数,例如

(defun a (&rest x)
    x)

我可以调用(symbol-function 'a),它返回(lambda (&rest x) x)。如果我愿意,我可以使用它

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

它与上面的原始功能具有相同的功能。现在,这让我想起了 Scheme,其中 lambda 表达式是函数的主体,并被分配给具有 Scheme 的 all-purpose 的变量名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 表达式变成一个函数,为什么函数 cell 还要a包含呢?(lambda (&rest x) x)

4

3 回答 3

8

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

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

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

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

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

一种是使用诸如funcalland之类apply的函数,这些函数接受一个函数和一些参数,并将函数应用于参数,如下所示:

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

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

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

请注意,它fset适用于名称(又名符号),因此f需要引用名称,否则它将被读取为变量的值。这就是为什么调用一个变量的函数setq,“q”代表“引用”,所以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是可以设置您不知道名称但可以通勤的变量。

于 2020-02-13T07:31:21.403 回答
3

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

我想解释为什么 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 的重要知识:默认情况下它是动态作用域的。这意味着,bar从 调用时,和foo的绑定在中可见carcarbar

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

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

然后:

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

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

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


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

于 2020-02-13T12:45:54.673 回答
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,我们首先讨论语言的运行时系统中的实际真正的内存对象,称为符号

一个符号有这个和那个 - 它就像一个具有多个字段的结构,可以用一些数据填充或留空。符号的记忆表示,记忆中的实际结构,有一个叫做“变量单元”的字段,它有一个叫做“函数单元”的字段,你有什么。

当我们调用 时(fset 'f (lambda (x) (+ x x x))),我们将评估(lambda (x) (+ x x x))表单的结果存储在符号F“功能单元格”中。

如果我们(+ f 2)在此之后调用,则会查看F变量单元格”以找出其作为变量的值,从而导致“未定义变量”错误。

如果我们调用(f 2),F“函数单元格”,将查看它作为函数的值(这(symbol-function 'f)也是正在做的事情)。发现它保存了评估的结果(lambda (x) (+ x x x)),因此进行了等效的函数调用((lambda (x) (+ x x x)) 2)


编辑:如果要将存储在符号“变量单元格”中的函数作为函数调用,则需要使用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作为参数
于 2020-03-27T16:10:47.407 回答