我正在学习Clojure,并试图通过类比Python的类似特性来理解reader、引用、eval和同象性。
在Python中,避免(或延迟)求值的一种方法是将表达式括在引号之间,例如:'3 + 4'
。稍后可以使用eval
对其进行评估,例如。eval('3 + 4')
生成7
。(如果你只需要引用Python的值,你可以使用repr
函数而不是手动添加引号。)
在Lisp中,您使用quote
或'
来引用,使用eval
来求值。(eval '(+ 3 4))
生成7
因此,在Python中,"引号"的内容由字符串表示,而在Lisp中,它由以quote
为第一项的列表表示。
我的问题,最后:为什么Clojure允许(eval 3)
,虽然3
没有引用?这仅仅是Lisp风格的问题(尽可能给出答案而不是错误),还是背后有其他原因?这种行为对Lisp来说是必要的吗?
简短的回答是数字(例如符号和字符串)对自己求值。引用指示lisp(读者)传递未求值的引用后面的内容。eval
然后得到你写的列表,但没有引号,然后求值(在(eval '(+ 3 4))
的情况下,eval
将在两个参数上求值函数调用(+
))。
最后一个表达式的情况如下:
- 当您按enter键时,将对表达式求值。它包含一个普通函数调用(
eval
)和一些参数。 - 计算参数。第一个参数包含一个引号,它告诉读者生成引号之后的内容(实际的
(+ 3 4)
列表)。 - 没有更多的参数,实际的函数调用被求值。这意味着使用列表
(+ 3 4)
作为参数调用eval
函数。 -
eval
函数再次执行相同的步骤,找到正常函数+
和参数,并应用它,获得结果。
其他答案已经解释了机制,但我认为哲学要点在于lisp和python看待"代码"的不同方式。在python中,表示代码的唯一方式是字符串,因此尝试计算非字符串当然会失败。Lisp有更丰富的代码数据结构:列表、数字、符号等等。因此表达式(+ 1 2)
是一个列表,包含一个符号和两个数字。在求值一个列表时,必须首先求值它的每个元素。
因此,在运行lisp代码的普通过程中,需要计算一个数字是非常自然的。为此,数字被定义为"自我评估",这意味着它们在评估后与之前是一样的:只是一个数字。eval
函数对裸"代码片段"3
应用的规则与编译器在编译大型表达式(例如(+ 5 3)
)的第三个元素时应用的规则相同。对于数字来说,这意味着别管它。
3
的值应该是多少?Lisp将一个数字求值给它自己是最有意义的。我们想要求在代码中引用数字吗?那将不太方便,而且极有问题:
(defun add-fourtytwo (n)
(+ n 42))
我们必须写
(defun add-fourtytwo (n)
(+ n '42))
代码中的每个数字都需要加引号。缺少引号会触发错误。这可不是什么好东西。
作为旁注,想象一下当您想在代码中使用eval
时会发生什么。
(defun example ()
(eval 3))
上面的是错误的。数字需要加引号
(defun example ()
(eval '3))
上面的可以,但是在运行时生成错误。Lisp计算'3
为数字3。但是在这个数字上调用eval
将是一个错误,因为它们需要被引用。
所以我们需要写:
(defun example ()
(eval ''3))
这不是很有用…
在Lisp历史中,数总是自求值的。但是在早期的Lisp实现中,其他一些数据对象,比如数组,并不是自求值的。同样,由于这是一个巨大的错误来源,像Common Lisp这样的Lisp方言已经定义了所有数据类型(除了列表和符号)都是自求值的。
要回答这个问题,我们需要看一下lisp中的eval
定义。例如,在CLHS中有一个定义:
语法:eval form => result*参数和值:表单-表单。
结果-表单评估产生的值。
其中form
为
- 任何要求值的对象。
- 符号、复合形式或自求值对象。
- (用于操作符,如在
<<operator>> form'') a compound form having that operator as its first element.
中)常数的形式。"
在您的案例中,编号"3"是self-evaluating object
。Self-evaluating object
为a form that is neither a symbol nor a cons is defined to be a self-evaluating object
。我相信对于clojure,我们可以用list
代替cons
。
lists
被eval
解释为函数调用。其他数据结构和对象作为自求值对象求值。
'(+ 3 4)
= (list '+ 3 4)
。'
(由阅读器转换为quote
函数)只是避免给定形式的求值。因此在表达式(eval '(+ 3 4))
中,eval
将列表数据结构('+ 3 4)
作为参数。