10

我正在尝试深入学习和理解 Lisp 编程语言。该函数+按应用顺序评估其参数:

(+ 1 (+ 1 2))

(+ 1 2)将被评估然后(+ 1 3)将被评估,但if函数的工作方式不同:

(if (> 1 2) (not-defined 1 2) 1)

由于(not-defined 1 2)未评估表单,因此程序不会中断。

相同的语法如何导致不同的参数评估?如何if定义函数以使其参数不被评估?

4

6 回答 6

13

if是一个特殊的操作符,不是一个普通的函数。

这意味着在调用与元素关联的函数之前计算复合表单rest中的元素的常规规则适用(因为它类似于宏表单)。first

在编译器和/或解释器中实现的方式是查看复合形式并根据first元素决定如何处理它:

  • 如果它是一个特殊的运算符,它会做它特殊的事情;
  • 如果它是一个,它的宏函数得到整个形式;
  • 否则它被视为一个函数 - 即使没有定义函数。

请注意,某些特殊形式可以定义为扩展为其他特殊形式的宏,但某些特殊形式必须实际存在。

例如,可以定义ifcond

(defmacro my-if (condition yes no) 
  `(cond (,condition ,yes)
         (t ,no)))

反之亦然(更复杂 - 实际上,cond 一个宏,通常扩展为一个ifs 序列)。

PS。请注意,系统提供的宏和特殊运算符之间的区别虽然在技术上清晰明了(参见special-operator-pmacro-function),但在意识形态上却是模糊的,因为

一个实现可以自由地将 Common Lisp 特殊运算符实现为宏。实现可以自由地将任何宏运算符实现为特殊运算符,但前提是还提供了宏的等效定义。

于 2013-09-29T11:49:48.297 回答
5

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将产生trueor false,如果我们要使用参数调用该结果

(lambda () then-part)
(lambda () else-part)

那么只有其中一个会被调用,所以实际上只有一个then-partandelse-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")))

一般来说,如果存在某种形式并且某些参数没有得到评估,那么 ( carof the) 形式要么是 Common Lisp 特殊运算符要么是宏。如果您需要编写一个函数来评估参数,但您不希望某些形式被评估,您可以将它们包装在lambda表达式中并让您的函数有条件地调用这些匿名函数。

if如果您还没有在语言中使用它,这是一种可能的实现方式。当然,现代计算机硬件不是基于 lambda 演算解释器,而是基于具有测试和跳转指令的 CPU,因此语言提供if原语并编译成适当的机器指令更有效。

于 2013-09-29T18:38:10.707 回答
5

Lisp 语法是规则的,比其他语言更规则,但它仍然不完全规则:例如在

(let ((x 0))
   x)

let不是函数的名称,((x 0))也不是在第一个位置使用非 lambda 形式的列表的错误形式。

有很多“特殊情况”(当然,仍然比其他语言少很多)没有遵循每个列表作为函数调用的一般规则,并且if是其中之一。Common Lisp 有很多“特殊形式”(因为绝对最小化不是重点),但您可以使用其中只有五个的方案方言:、、、if和(如果您需要宏,则可以使用六个)prognquotelambdaset!

虽然 Lisp 的语法并不完全统一,但代码的底层表示是非常统一的(只是列表和原子),并且表示的统一性和简单性促进了元编程(宏)。

“Lisp 没有语法”是一个有一定道理的陈述,但它是“Lisp 有两种语法”的陈述:一种语法是使用reader将字符流转换为 s 表达式,另一种语法是使用编译器/evaluator 从 s 表达式转换为可执行代码。

Lisp 没有语法也是事实,因为这两个级别都不是固定的。与其他编程语言不同,您可以自定义第一步(使用阅读器宏)和第二步(使用宏)。

于 2013-09-29T11:56:06.367 回答
4

这样做没有任何意义。示例:(if (ask-user-should-i-quit) (quit) (continue))。是否应该退出,即使用户不想这样做?

IF不是 Lisp 中的函数。它是一个特殊的内置运算符。Lisp 有几个内置的特殊运算符。请参阅:特殊表格。这些不是功能。

于 2013-09-29T11:14:23.440 回答
2

参数不会像函数一样被评估,因为if它是一个特殊的 operator。特殊运算符可以以任意方式求值,这就是它们被称为特殊运算符的原因。

考虑例如

(if (not (= x 0))
    (/ y x))

如果总是对除法进行评估,则可能会出现零误差除法,这显然不是故意的。

于 2013-09-29T11:12:09.723 回答
2

如果不是函数,则它是一种特殊形式。如果您想自己实现类似的功能,可以通过定义宏而不是函数来实现。

这个答案适用于 Common Lisp,但对于大多数其他 Lisp 来说可能是相同的(尽管在某些if情况下可能是宏而不是特殊形式)。

于 2013-09-29T11:12:10.220 回答