简短概述:它使表达式的嵌套更加清晰
Lisp语言,包括Scheme在内,最具特色的功能之一就是你所编写的代码本质上就是抽象语法树(AST)。像Java这样的语言有很多语法,因此需要解析器来正确消除潜在的歧义语法。中缀运算符就是一个典型的例子。考虑在Java中以下语句:
int x = 1 + y * 2;
代码的文本表示并不意味着任何类似树形结构的东西,但实际上,该语句有一个单一的标准解析结果,确实是一个树形结构。它可能看起来像这样:
=
/ \
int x +
/ \
1 *
/ \
y 2
另一方面,等效的Scheme代码使得所有这些嵌套变得非常明确:
(define x (+ 1 (* y 2)))
注意显式分组如何创建一个非常明确定义的表达式树。不需要像大多数其他语言那样使用运算符优先级规则。这种简单性是一种有意的设计选择,因为当源代码表示如此简单时,编写转换宏非常容易。Lisp通常会广泛使用宏,因为相对于其他编程语言,操作AST相对简单,这是由于语法非常简单。
考虑到这一点,缩进规则可能变得更加明显:Lisp代码通常以一种使AST结构立即可见的方式进行缩进。考虑您的
fact-iter
示例函数的另一种“简单”缩进风格的替代版本:
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
在这种情况下,缩进并不是灾难性的,但是对
fact-iter
的递归调用现在更难以视觉解析。 Lisp/Scheme语法的一致性使得很难立即注意到
fact-iter
被用三个参数调用,因为第一个参数不再与后两个对齐。
这个问题可以通过将所有参数放在单独的行中来至少部分解决:
(fact-iter
(* counter product)
(+ counter 1)
max-count)
这种写法是可行的Lisp风格,但会浪费垂直空间,并且由于缩进较少,使AST有点难以立即理解。
如果在Scheme中使用“简单”缩进模型将导致灾难,请考虑以下两个等价表达式:
(string->number (if (string? x) x
(format "~a" x)))
(string->number (if (string? x) x
(format "~a" x)))
第一个例子维护了AST。很容易看出format
调用是“else”情况的一部分,因为它嵌套在其中。第二个例子没有保留AST,乍一看不清楚对format
的调用是嵌套在if
中还是只是作为string->number
的第二个参数传递。你可以看到,Lisp的语法并没有真正地表明这一点。
Scheme缩进可能一开始看起来有点奇怪,但一旦你习惯了它,就不必在脑海中处理括号,代码就更容易理解了。语法的统一既是一种福音,又是一种诅咒:它使编写宏变得微不足道,但也移除了一些让代码更容易理解的视觉标记。有一个更语义化的缩进系统可以帮助缓解这个缺点。
MAX-COUNT
是FACT-ITER
的第三个参数(即使IF
当然不是函数,但同样适用)。它还区分了参数列表和宏体。 - jkiiskidefine
,CL中的defun
以及任何一种语言中的let
或lambda
。这些通常是概念上存在“主体”的形式。(请注意,我在这里使用“特殊”一词并非技术意义上的)。 - user5920214