我正在尝试深入学习和理解 Lisp 编程语言。该函数+
按应用顺序评估其参数:
(+ 1 (+ 1 2))
(+ 1 2)
将被评估然后(+ 1 3)
将被评估,但if
函数的工作方式不同:
(if (> 1 2) (not-defined 1 2) 1)
由于(not-defined 1 2)
未评估表单,因此程序不会中断。
相同的语法如何导致不同的参数评估?如何if
定义函数以使其参数不被评估?
我正在尝试深入学习和理解 Lisp 编程语言。该函数+
按应用顺序评估其参数:
(+ 1 (+ 1 2))
(+ 1 2)
将被评估然后(+ 1 3)
将被评估,但if
函数的工作方式不同:
(if (> 1 2) (not-defined 1 2) 1)
由于(not-defined 1 2)
未评估表单,因此程序不会中断。
相同的语法如何导致不同的参数评估?如何if
定义函数以使其参数不被评估?
这意味着在调用与元素关联的函数之前计算复合表单rest
中的元素的常规规则不适用(因为它类似于宏表单)。first
在编译器和/或解释器中实现的方式是查看复合形式并根据其first
元素决定如何处理它:
请注意,某些特殊形式可以定义为扩展为其他特殊形式的宏,但某些特殊形式必须实际存在。
例如,可以定义if
为cond
:
(defmacro my-if (condition yes no)
`(cond (,condition ,yes)
(t ,no)))
反之亦然(更复杂 - 实际上,cond
是一个宏,通常扩展为一个if
s 序列)。
PS。请注意,系统提供的宏和特殊运算符之间的区别虽然在技术上清晰明了(参见special-operator-p
和macro-function
),但在意识形态上却是模糊的,因为
一个实现可以自由地将 Common Lisp 特殊运算符实现为宏。实现可以自由地将任何宏运算符实现为特殊运算符,但前提是还提供了宏的等效定义。
sds的回答很好地回答了这个问题,但我认为还有一些更一般的方面值得一提。正如那个答案和其他人所指出的那样,if
, 作为特殊运算符内置于语言中,因为它确实是一种原语。最重要的if
是,不是一个函数。
也就是说,可以只使用函数和普通函数调用来实现功能,其中所有参数都被评估。if
因此,可以在lambda 演算中实现条件,该家族中的语言在某种程度上基于该演算,但没有条件运算符。
在 lambda 演算中,可以将 true 和 false 定义为两个参数的函数。这些参数被假定为函数,true 调用它的第一个参数,而 false 调用第二个。(这是Church 布尔值的一个细微变化,它只返回它们的第一个或第二个参数。)
true = λ[x y].(x)
false = λ[x y].(y)
(这显然与 Common Lisp 中的布尔值有所不同,其中nil
为假,其他为真。)不过,这样做的好处是我们可以使用布尔值来调用两个函数之一,具体取决于布尔值是否是真的还是假的。考虑 Common Lisp 形式:
(if some-condition
then-part
else-part)
如果使用上面定义的布尔值,那么评估some-condition
将产生true
or false
,如果我们要使用参数调用该结果
(lambda () then-part)
(lambda () else-part)
那么只有其中一个会被调用,所以实际上只有一个then-part
andelse-part
会被评估。通常,将某些表单包装在 alambda
中是一种能够延迟对这些表单进行评估的好方法。
Common Lisp 宏系统的强大功能意味着我们实际上可以if
使用上述布尔类型定义宏:
(defconstant true
(lambda (x y)
(declare (ignore y))
(funcall x)))
(defconstant false
(lambda (x y)
(declare (ignore x))
(funcall y)))
(defmacro new-if (test then &optional else)
`(funcall ,test
(lambda () ,then)
(lambda () ,else)))
有了这些定义,一些代码如下:
(new-if (member 'a '(1 2 3))
(print "it's a member")
(print "it's not a member"))))
扩展为:
(FUNCALL (MEMBER 'A '(1 2 3)) ; assuming MEMBER were rewritten
(LAMBDA () (PRINT "it's a member")) ; to return `true` or `false`
(LAMBDA () (PRINT "it's not a member")))
一般来说,如果存在某种形式并且某些参数没有得到评估,那么 ( car
of the) 形式要么是 Common Lisp 特殊运算符要么是宏。如果您需要编写一个函数来评估参数,但您不希望某些形式被评估,您可以将它们包装在lambda
表达式中并让您的函数有条件地调用这些匿名函数。
if
如果您还没有在语言中使用它,这是一种可能的实现方式。当然,现代计算机硬件不是基于 lambda 演算解释器,而是基于具有测试和跳转指令的 CPU,因此语言提供if
原语并编译成适当的机器指令更有效。
Lisp 语法是规则的,比其他语言更规则,但它仍然不完全规则:例如在
(let ((x 0))
x)
let
不是函数的名称,((x 0))
也不是在第一个位置使用非 lambda 形式的列表的错误形式。
有很多“特殊情况”(当然,仍然比其他语言少很多)没有遵循每个列表作为函数调用的一般规则,并且if
是其中之一。Common Lisp 有很多“特殊形式”(因为绝对最小化不是重点),但您可以使用其中只有五个的方案方言:、、、if
和(如果您需要宏,则可以使用六个)progn
。quote
lambda
set!
虽然 Lisp 的语法并不完全统一,但代码的底层表示是非常统一的(只是列表和原子),并且表示的统一性和简单性促进了元编程(宏)。
“Lisp 没有语法”是一个有一定道理的陈述,但它是“Lisp 有两种语法”的陈述:一种语法是使用reader
将字符流转换为 s 表达式,另一种语法是使用编译器/evaluator 从 s 表达式转换为可执行代码。
Lisp 没有语法也是事实,因为这两个级别都不是固定的。与其他编程语言不同,您可以自定义第一步(使用阅读器宏)和第二步(使用宏)。
这样做没有任何意义。示例:(if (ask-user-should-i-quit) (quit) (continue))
。是否应该退出,即使用户不想这样做?
IF
不是 Lisp 中的函数。它是一个特殊的内置运算符。Lisp 有几个内置的特殊运算符。请参阅:特殊表格。这些不是功能。
参数不会像函数一样被评估,因为if
它是一个特殊的 operator。特殊运算符可以以任意方式求值,这就是它们被称为特殊运算符的原因。
考虑例如
(if (not (= x 0))
(/ y x))
如果总是对除法进行评估,则可能会出现零误差除法,这显然不是故意的。
如果不是函数,则它是一种特殊形式。如果您想自己实现类似的功能,可以通过定义宏而不是函数来实现。
这个答案适用于 Common Lisp,但对于大多数其他 Lisp 来说可能是相同的(尽管在某些if
情况下可能是宏而不是特殊形式)。