Lisp代码格式化

38

在我关于Clojure/LISP语法的另一个问题中,有人花时间评论指出我没有按照标准LISP的方式编写示例代码。他很友善地重写了代码片段,这对我帮助很大。但这也引发了我的另一个问题。为什么会这样:

(if (= a something)
  (if (= b otherthing)
    (foo)))

标准的LISP格式是否比这种形式更可取:

(if (= a something)
  (if (= b otherthing)
    (foo)
  )
)

由于我的C++开发背景,我本能地会采用第一种方案来格式化代码。我想知道后一种格式化方式是否有任何好处,或者它只是一种根深蒂固的标准(就像QWERTY键盘一样)。我并不是在争论——只是很难理解为什么第一种形式更可取。第二种形式帮助我更容易地看到代码结构。


你可以考虑使用下面的代码,而不是<pre>(if (= a something) (if (= b otherthing) (foo))) </pre><pre> (when (and (= a something) (= b otherthing)) (foo)) </pre> - jlf
5个回答

41

在额外的行上添加右括号并不能真正帮助我们看到代码的结构,因为你可以从缩进水平获得相同的信息。然而,第二种形式占用了几乎两倍的行数,在阅读代码时需要更频繁地滚动。

如果需要更仔细地检查嵌套的括号,具有突出显示匹配括号功能的编辑器会很有用。当匹配的括号不太远时,这也会更容易。

如果表达式变得过长和复杂,难以轻易地阅读,这也可能是您应该将部分功能提取到单独的函数中的信号。


37

Lisp代码的缩进方式有点像Python中的显式空格,但它当然是可选的。基本的规则是,如果列表中的项目不在同一行上,则将它们垂直放置在彼此下方。

(a (b (c (d e)
         (f g))
      (h i j))
   (k l m n))

即使没有看括号,你也可以看到 (d e)(f g) 是参数传递给 c(c (d e) (f g))(h i j) 是参数传递给 b(b (c (d e) (f g)) (h i j))(k l m n) 是参数传递给 a

以你的示例为例,它应该更正确地格式化为以下形式:

(if (= a something)
    (if (= b otherthing)
        (foo)))

    ^   ^
  notice how they line up

现在缩进级别变得有意义了,你不再需要依靠平衡括号来获取该信息,而且由于将它们放在同一行作为结束语句更加紧凑,这就是lisper所做的。当然,Lisp代码不必采用这种格式,但人们通常使用并可以依赖它。


1
关于 Python 和空格的问题,你可以把括号看作是编辑器帮助你确定缩进的工具,否则就忽略它们。顺便说一下,当你习惯了 Lisp 之后,你将不再“看到”它们。 - Svante
1
我认为你缩进IF表达式的方式在Lisp语言族群中更加传统和美观,比起OP的方式更具有吸引力。然而不幸的是(仅是我的个人看法),OP的方式实际上却是Clojure的约定惯例。 - Matthias Benkard
有趣的是,我刚刚检查了Emacs,它像OP一样缩进IF,但是用随机名称缩进就像我上面做的那样。也许IF有什么特殊之处,但不一致性很烦人。 - Kyle Cronin
不同的缩进是因为“foo”是条件成立时发生的情况,因此不是IF的参数。 - Sujoy
就我所知,我见过一些要求在同一行放置闭合花括号的Java编码标准。 - Constantin

3
简单的回答是你的方式不是Lisp漂亮打印程序的方式。对于代码来说,拥有一个唯一的格式总是件好事情,而pprint宏使得这种格式内置于语言中。
当然,因为pprint宏存在,严格遵循标准代码格式并不是必须的,因为人们可以通过pprint运行你的代码并获得他们习惯的输出。然而,由于其他人都使用pprint或手动近似它,如果你不按照相同的方式编写代码,你将很难阅读代码,并且没有一个简单的宏可以将他们的代码转换为你的首选格式。

1
您可以使用Sreafctor包对Lisp代码进行重新格式化:主页
一些演示: 可用命令:
  • srefactor-lisp-format-buffer: 整理整个缓冲区
  • srefactor-lisp-format-defun: 整理光标所在的当前 defun
  • srefactor-lisp-format-sexp: 整理光标所在的当前 sexp。
  • srefactor-lisp-one-line: 将同一级别的当前 sexp 转换为一行;使用前置参数,递归将所有内部 sexp 转换为一行。

这些格式命令也可用于 Common Lisp 和 Scheme。

如果有任何问题,请提交问题报告,我会很高兴解决它。


0

当你需要关闭10个括号时,它变得非常笨重。

当我以前编写Lisp程序时,我在同一行的开放括号和其余部分之间留了一个空格,以简化计数,像这样:

(if (= a something)
  (if (= b otherthing)
    (foo) ))

我猜现在不再需要这样做了,因为编辑器更加有帮助。


1
我认为这是一个有用的约定;我得试一下。它可以在代码经常编辑时真正帮助,因为你可以看到应该放置换行符的位置。 - Aaron
那真是太糟糕的建议了。 - Luís Oliveira
9
这有什么可怕的? - starblue
2
1962年的《LISP 1.5程序员手册》中有一个例子:http://kazimirmajorinc.blogspot.de/2012/03/few-examples-of-lisp-code-typography.html - starblue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接