我正在尝试深入学习和理解Lisp编程语言。函数+
按应用顺序评估其参数:
(+ 1 (+ 1 2))
将评估 (+ 1 2)
,然后评估(+ 1 3)
,但if
函数的工作方式不同:
(if (> 1 2) (not-defined 1 2) 1)
由于未评估表单(not-defined 1 2)
,程序不会中断。
相同的语法如何导致不同的参数评估?如何定义if
函数以便不评估其参数?
答案 0 :(得分:13)
if
是special operator,而非普通函数。
这意味着在调用与rest
元素相关联的函数之前评估compound form中的first
元素的常规规则是不适用(因为它类似于macro forms)。
在编译器和/或解释器中实现的方式是查看复合形式并根据its first
element决定如何处理它:
请注意,某些特殊形式可以定义为扩展为其他特殊形式的宏,但实际上必须存在一些特殊形式。
例如,可以用cond
:
if
(defmacro my-if (condition yes no)
`(cond (,condition ,yes)
(t ,no)))
反之亦然(更复杂 - 实际上,cond
是宏,通常会扩展为if
s的序列。
PS。请注意,系统提供的宏和特殊运算符之间的区别,而技术上清晰明确(请参阅special-operator-p
和macro-function
),在意识形态上模糊because
实现可以自由地实现Common Lisp特殊运算符 作为一个宏。实现可以自由地实现任何宏运算符 作为一个特殊的运算符,但只有当一个等价的定义 还提供了宏。
答案 1 :(得分:5)
Lisp语法是常规的,比其他语言更常规,但它仍然不是完全正常的:例如在
中(let ((x 0))
x)
let
不是函数的名称,((x 0))
不是一个错误形式,其中第一个位置使用的不是lambda形式的列表。
有很多“特殊情况”(当然还比其他语言少得多),其中不遵循每个列表的一般规则是函数调用,if
就是其中之一。 Common Lisp有很多“特殊形式”(因为绝对最小化不是重点),但你可以在方案方言中只用其中的五个来逃避:if
,progn
,{{ 1}},quote
和lambda
(如果需要宏,则为6)。
虽然Lisp的语法并不完全一致,但代码的基本表示却非常统一(只是列表和原子),表示的一致性和简单性有助于元编程(宏)。 / p>
“Lisp没有语法”是一个含有一些真理的语句,但是它的语句是“Lisp有两种语法”:一种语法是使用set!
从字符流转换为s表达式的语法,另一种语法是使用编译器/赋值器将s表达式转换为可执行代码。
Lisp也没有语法,因为这两个级别都没有修复。与其他编程语言不同,您可以自定义第一步(使用阅读器宏)和第二步(使用宏)。
答案 2 :(得分:5)
sds's answer很好地回答了这个问题,但我认为还有一些更为一般的方面值得一提。正如答案和其他人所指出的那样,if
作为特殊算子被内置于语言中,因为它确实是一种原始的。最重要的是,if
不是一个函数。
也就是说,if
的功能可以使用函数和普通函数调用来实现,其中所有参数都被评估。因此,条件可以在lambda calculus中实现,该族中的语言在某些基础上,但没有条件运算符。
在lambda演算中,可以将true和false定义为两个参数的函数。假定参数是函数,true调用其第一个参数,而false调用第二个参数。 (这是Church booleans的略微变化,只是返回他们的第一个或第二个参数。)
true = λ[x y].(x)
false = λ[x y].(y)
(这显然与Common Lisp中的布尔值不同,其中nil
为false且其他任何内容都是真的。)但是,这样做的好处是我们可以使用布尔值来调用其中一个两个函数,取决于布尔值是true还是false。考虑Common Lisp形式:
(if some-condition
then-part
else-part)
如果正在使用上面定义的布尔值,那么评估some-condition
将生成true
或false
,如果我们使用参数调用该结果
(lambda () then-part)
(lambda () else-part)
然后只会调用其中一个,因此实际上只会评估then-part
和else-part
中的一个。一般来说,将某些表单包装在lambda
中是一种很好的方法,可以延迟对这些表单的评估。
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
)形式是Common Lisp特殊运算符或宏。如果您需要编写一个将评估参数的函数,但是您希望不评估某些表单,则可以将它们包装在lambda
个表达式中并让你的函数有条件地调用那些匿名函数。
这是实现if
的一种可能方式,如果您还没有使用该语言。当然,现代计算机硬件不是基于lambda演算解释器,而是基于具有测试和跳转指令的CPU,因此提供if
基元并编译为适当的语言更有效。机器说明。
答案 3 :(得分:4)
这样做没有任何意义。示例:(if (ask-user-should-i-quit) (quit) (continue))
。是否应该退出,即使用户不想?
IF
不是Lisp中的函数。它是一个特殊的内置操作员。 Lisp有几个内置的特殊运算符。见:Special Forms。那些不是功能。
答案 4 :(得分:2)
不会对函数计算参数,因为if
是特殊运算符。可以以任意方式评估特殊运算符,这就是为什么它们被称为特殊运算符。
考虑例如。
(if (not (= x 0))
(/ y x))
如果总是评估除法,则可能存在零误差除法,这显然不是意图。
答案 5 :(得分:2)
如果不是函数,它是一种特殊形式。如果您想自己实现类似功能,可以通过定义宏而不是函数来实现。
这个答案适用于Common Lisp,但对于大多数其他Lisp来说可能是相同的(尽管在某些if
中可能是宏而不是特殊形式)。