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中没有这样的东西。基本上,我们将符号作为变量,将缺点作为特殊形式/函数/宏,其余的都是自我评估的。对于任何不同的内容,您需要编写自定义评估器/编译器。