Scheme 如何格式化lisp代码?

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))) 在这里,我们看到: 阶乘的声明没有前导空格。我认为这很正常 函数体每行需要两个前导

考虑来自SICP的这个迭代阶乘过程

(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个空间
为什么是这样?间隔让我感到困惑。为什么它不能像java一样(在代码的每个内部部分添加四个空格)?如何格式化lisp代码?

tl;dr:它使表达式的嵌套变得清晰
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代码。其他编辑器也有类似的功能。