Scheme 如何格式化lisp代码?
考虑来自SICP的这个迭代阶乘过程Scheme 如何格式化lisp代码?,scheme,lisp,code-formatting,Scheme,Lisp,Code Formatting,考虑来自SICP的这个迭代阶乘过程 (define (fact-iter product counter max-count) (if (> counter max-count) product (fact-iter (* counter product) (+ counter 1) max-count))) 在这里,我们看到: 阶乘的声明没有前导空格。我认为这很正常 函数体每行需要两个前导
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
在这里,我们看到:
- 阶乘的声明没有前导空格。我认为这很正常
- 函数体每行需要两个前导空格
- 我们在if语句的第一个子句和第二个子句的开头添加了四个空格。总共有6个空间
- 我们在最后两行中添加了11个空格,这是if语句第二个子句的其余部分。总共有17个空间
Lisps(包括Scheme)的一个定义特性是,您编写的代码本质上是AST(抽象语法树)。像Java这样的语言有很多语法,所以它们需要解析器来正确地消除潜在的歧义语法。中缀运算符就是一个典型的例子。在java中考虑以下语句:
intx=1+y*2;
代码的文本表示形式当然并不意味着任何类似树的结构,但实际上,该语句有一个单独的规范解析,实际上是一个树。它看起来像这样:
=
/ \
int x +
/ \
1 *
/ \
y 2
另一方面,等效的方案代码使所有嵌套非常明确:
(define x (+ 1 (* y 2)))
注意显式分组如何创建定义良好的表达式树。与大多数其他语言一样,不需要运算符优先级规则。这种简单性是一种有意的设计选择,因为当源代码表示非常简单时,编写转换它的宏非常容易。Lisp倾向于大量使用宏,因为与其他编程语言相比,操作AST相对轻松,因为语法非常简单
考虑到所有这些,缩进规则可能会变得更加明显:Lisp代码通常以这样的方式缩进,以便AST的结构立即可见。考虑一个替代版本的<代码>事实ITER < /Cord>示例函数,它使用了一种“更简单”的缩进样式:
(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更难立即摸索,因为压痕在视觉上明显不那么引人注目
例如,使用“简单”缩进模型在方案中是灾难性的,考虑以下两个等价表达式:
(string->number (if (string? x) x
(format "~a" x)))
(string->number (if (string? x) x
(format "~a" x)))
第一个示例维护AST。很容易看出format
调用是if
表单的“else”情况,因为它嵌套在该表单下。第二个示例没有维护AST,乍一看不清楚对格式的调用是否嵌套在if
中,或者它是否只是传递给字符串->数字的第二个参数。您可以看到,Lisp的语法并没有真正说明这一点
Scheme缩进一开始看起来有点古怪,但一旦你习惯了,它会让代码更容易看,而不必在你的脑子里动括号。语法的一致性既是福也是祸:它使编写宏变得微不足道,但它删除了一些使代码更容易理解的可视标记。使用更具语义的缩进系统有助于缓解这一缺点。Lisp有一些缩进代码的规则。使用最紧凑的版本。列表中元素的对齐非常重要。水平空间与可读性的最佳使用也是要考虑的。
- 宏和特殊窗体可以具有自定义缩进规则。见下文
- 函数有一些基于可用水平空间的缩进变体
例如:
(append a b c) ; all arguments fit on a line
(append a ; arguments are aligned
b
c)
(append ; saving horizontal space, elements are aligned
a
b
c)
像IF-THEN-ELSE这样的简单宏/运算符通常像函数一样对齐
更为复杂的情况是:
(define (FUNCTION-NAME ARG0 ... ARGN) BODY-FORM-0 ... BODY-FORM-N)
示例:
(define (foo a b) (print a) (append a b))
(define (foo a b)
(print a)
(append a b))
(define (foo a
b)
(print a)
(append a b))
(define (foo
a
b)
(print a)
(append a b))
典型的Lisp pretty打印机会根据可用的水平空间选择缩进变量。函数调用的参数与第一个参数对齐。这就清楚了它们是谁的参数,所以你不需要开始计算参数来确定MAX-COUNT
是FACT-ITER
的第三个参数(这同样适用于IF
,尽管它当然不是一个函数)。它还将参数列表与宏体区分开来。@jkiiski我们在“产品”中增加了4个空格来对齐它(如果?是这样吗?为什么只在“(如果”?为什么2?)中添加两个空格?@morbidCode:某些“特殊”形式,如定义和绑定形式,有自己的规则,它们是(我想:我相信编辑知道)表单的“body”由两个字符缩进。这包括Scheme中的define
、CL中的defun
和let
或lambda
中的任何一种。这些通常是“body”的概念可以理解的表单。(注意,我在这里使用的是非技术意义上的“special”)。是否有自动执行此操作的工具?@morbidCode是的,但这取决于您的编辑器。DrRacket具有自动缩进功能。Emacs有多种主要模式用于编辑不同类型的lisp代码。其他编辑器也有类似的功能。