自定义自引用表单:有用



Lisps经常声明,某些类型是自我评估的。 例如,在 emacs-lisp 数字中,"字符串"、:关键字符号和更多的东西可以自己评估。

或者,更具体地说:评估表单并再次评估结果会得到相同的结果。

也可以创建自定义的自我评估表格,例如

(defun my-list (&rest args) 
  (cons 'my-list (mapcar (lambda (e) (list 'quote e)) args)))
(my-list (+ 1 1) 'hello)
  => (my-list '2 'hello)
(eval (my-list (+ 1 1) 'hello))
  => (my-list '2 'hello)

定义这种形式是否有任何实际用途,或者这更像是一个深奥的概念?

我想创建"自定义类型"作为自我评估表单,例如,评估可以对参数执行类型检查。当尝试在我的代码中使用此类类型时,与简单地使用例如使用 plist 相比,我通常发现它不方便。

*

编辑*我再次检查,似乎我混淆了"自我评估"和"自我引用"。在 emacs lisp 中,后一个术语被应用于lambda形式,至少在没有词汇绑定的上下文中是这样。请注意,lambda 形式永远不会计算自身 (eq (,即使结果是 equal

(setq form '(lambda () 1))              ;; => (lambda () 1)
(equal form (eval form))                ;; => t
(equal (eval form) (eval (eval form)))  ;; => t
(eq form (eval form))                   ;; => nil
(eq (eval form) (eval (eval form)))     ;; => nil

正如约书亚在他的回答中所说:eval函数的不动点(关于equal(。

您提供的代码未定义自我评估表单的类型。 一种自我评估形式,当作为参数传递时,eval 将返回该形式。 让我们仔细看看。首先,有一个函数接受一些参数并返回一个新列表:

(defun my-list (&rest args) 
  (cons 'my-list (mapcar (lambda (e) (list 'quote e)) args)))

新列表将符号 my-list 作为第一个元素。 其余元素是包含符号引用和传递给函数的元素的双元素列表:

(my-list (+ 1 1) 'hello)
;=> (my-list '2 'hello)

现在,这确实为您提供了一个关于相等评估的固定点,因为

(eval (my-list (+ 1 1) 'hello))
;=> (my-list '2 'hello)

(eval (eval (my-list (+ 1 1) 'hello)))
;=> (my-list '2 'hello)

同样的情况是,自我评估形式是关于等式的不动点,但在Common Lisp中,自我评估形式是关于eq(或者eql(的评估的不动点。

指定自我评估表单的语言的要点实际上是定义评估者与表单的关系。 从概念上讲,评估将定义如下:

(defun self-evaluating-p (form)
  (or (numberp form)
      (stringp form)
      (and (listp form)
           (eql 2 (length form))
           (eq 'quote (first form)))
      ; ...
      ))
(defun eval (form)
  (cond
    ((self-evaluating-p form) form)
    ((symbolp form) (symbol-value-in-environment form))
    ;...
    ))

重点不在于自我评估形式是评估等价(对于某些等价关系(值的形式,而是 eval 不必做任何工作的形式。

编译器宏

虽然通常没有太多用于评估自身(模等价(关系的形式,但有一个非常重要的地方使用了非常相似的东西 Common Lisp:编译器宏(强调添加(:

3.2.2.1 编译器宏

编译器-宏-函数返回的函数是两个函数 参数,称为扩展函数。若要展开编译器宏, 扩展函数通过调用宏扩展钩子来调用 扩展函数作为其第一个参数,整个编译器 宏形式作为其第二个参数,以及当前编译 环境(或当前词汇环境,如果形式为 由编译文件以外的其他东西处理(作为其第三个 论点。宏展开钩子反过来调用扩展函数 以形式为第一个参数,以环境为第二个参数 论点。扩展函数的返回值,即 由宏扩展钩子传递,可能是相同的形式, 或者一种形式,可以,由代码自行决定执行 扩展,代替原始形式使用。

宏定义

编译器宏

  • 与普通宏不同,编译器宏可以仅通过返回与原始宏相同的形式来拒绝提供扩展 (可以使用 &whole 获得(。

举个例子:

(defun exponent (base power)
  "Just like CL:EXPT, but with a longer name."
  (expt base power))
(define-compiler-macro exponent (&whole form base power)
  "A compiler macro that replaces `(exponent base 2)` forms
with a simple multiplication.  Other invocations are left the same."
  (if (eql power 2)
      (let ((b (gensym (string '#:base-))))
        `(let ((,b ,base))
           (* ,b ,b)))
      form))

请注意,这与自我评估表单并不完全相同,因为编译器仍在经历以下过程:检查表单是否是一个缺点,其汽车具有关联的编译器宏,然后使用该表单调用该编译器宏函数。 但它的相似之处在于,形式转到某物并且相同的形式返回的情况很重要。

你描述的内容和自我评估的形式(不是类型!(是无关的。

? (list (foo (+ 1 2)))

可能评估为

-> (foo 3)

但是它正在运行函数foo,它返回一些带有符号foo及其第一个参数值的列表。而已。您已经编写了一个函数。但不是自定义的自我评估表单

表单是一些要评估的数据。它必须是有效的 Lisp 代码。

关于表单评估

当您有这样的来源时,表单评估是一个主题:

(defun foo ()
  (list #(1 2 3)))

上面的向量是怎么回事?(foo)是否返回一个以向量作为其第一个元素的列表?

在Common Lisp中,这种向量形式是自我评估的。在其他一些Lisps中,情况有所不同。在一些较旧的Lisp方言中,人们可能不得不编写下面的代码以使编译器满意。口译员甚至可能有所不同。(我以前在标准 Lisp 变体的一些实现中见过这种情况(。

(defun foo ()
  (list '#(1 2 3)))    ; a vector form quoted

请注意报价。必须引用非自我评估表格。这相对容易做到。您必须查看源代码并确保引用此类表单。但还有另一个问题使它更加困难。此类数据对象可能由代码中的宏引入。因此,还必须确保宏生成的所有代码都引用了所有文字数据。这使它成为一个真正的痛苦。

这在其他一些Lisp方言中是错误的(不是Common Lisp(:

(defmacro foo (a)
  (list 'list a #(1 2 3)))

甚至(注意添加的quote(

(defmacro foo (a)
  (list 'list a '#(1 2 3)))

(foo 1)

将是代码(list 1 #(1 2 3)).但是在这些 Lisps 中会缺少一个引号......所以那里是错误的。

人们必须写:

(defmacro foo (a)
  (list 'list a ''#(1 2 3)))  ; note the double quote

因此

(foo 1)

将是代码(list 1 '#(1 2 3))。然后起作用。

为了摆脱这些问题,像Common Lisp这样的Lisp方言要求除了符号和缺点之外的所有形式都是自我评估的。请参阅 CL 标准:自我评估对象。这也与使用解释器或编译器无关。

请注意,Common Lisp 也没有提供任何机制来改变这一点。

定制机械师可以做什么?人们可以让数据形式计算出不同的东西。或者可以实施不同的评估方案。但是在Common Lisp中没有这样的东西。基本上,我们将符号作为变量,将缺点作为特殊形式/函数/宏,其余的都是自我评估的。对于任何不同的内容,您需要编写自定义评估器/编译器。

最新更新