Lisp括号

48
为什么Lisp程序员会像示例1中展示的那样格式化他们的代码,而不是像示例2中展示的那样呢?对我来说(以及我猜大多数来自不同编程背景的人),示例2中展示的格式更容易阅读。那么,Lisp程序员为什么喜欢示例1中的样式呢?
(defun factorial (n)
  (if (<= n 1)
    1
    (* n (factorial (- n 1)))))

示例 2

(defun factorial (n)
  (if (<= n 1)
    1
    (* n (factorial (- n 1)))
  )
)

9
你尝试过在非平凡函数上使用样式2吗? :-) - Ken
为什么Lisp社区更喜欢将所有括号累积在函数末尾?-程序员堆栈交换 - MasterMastic
13个回答

40

LISP集成开发环境会自动平衡括号并根据嵌套级别进行管理缩进。在这些情况下,示例2并没有带来任何好处。

在 C/FORTRAN/Pascal 的传统中,你更注重序列而不是嵌套(代码解析树更加扁平和宽广)。作用域的结束是您代码中更重要的事件:因此强调程度一直到某种程度上仍然很重要。


26

对于有经验的 Lisp 用户来说,嵌套层数比找到闭合括号更重要。将闭合括号放在自己的行中并不能真正帮助到嵌套层级。

基本思想是括号直接围绕着它们的内容。

(a)

而不是

(a
)

接下来是这样的:

(defun foo (bar)
  (foo (bar (baz
              ...
             )
         ...
        )
    ...
   )
 )

vs.

->

对比。

(defun foo (bar)
  (foo (bar (baz ...) ...) ...))

在编辑Lisp文本时,基本的想法之一是,您可以通过(双击)括号或者当光标位于表达式内部或括号上时使用键盘命令来选择一个列表。然后,您可以剪切/复制该表达式,并将其粘贴到其他函数的某个位置。下一步是选择另一个函数并重新缩进该函数。完成。没有必要为闭合括号删除或引入新行。只需粘贴和重新缩进即可。它只是适合。否则,您要么浪费时间格式化代码,要么需要使用某些编辑器工具重新格式化代码(以便右括号置于独立的行上)。大多数情况下,这将创建额外的工作并阻碍移动代码。

有一种情况,有经验的Lisper会将右括号写在自己的一行中:

(defvar *persons*
   (list (make-person "fred")
         (make-person "jane")
         (make-person "susan")
         ))

这里表示可以添加新人。将光标直接放在最后一行的第二个右括号之前,按下(打开新行),添加条款并缩进括号以使它们再次对齐。这样可以避免在所有括号都在同一行关闭时找到正确的括号,然后按回车键的“麻烦”。


+1,不过我倾向于使用一个空白而不是换行符。 - starblue
(defvar persons (list (make-person "fred") (make-person "jane") (make-person "susan") ))) - zeitue
@Taylor Bioniks:已更改。谢谢! - Rainer Joswig

17

没有必要对齐闭合括号,这在语义上并没有任何增强。大多数Lisp程序员使用像Emacs这样的好编辑器来匹配括号和关闭括号,因此使任务轻松。

在Python中,根本没有闭合括号或关键字 end ,Python程序员也可以正常工作。


1
Python并不只是好用的语言。正是因为这个问题,复杂的代码很难阅读。 - Erik Aronesty
1
+1 指出了 IDE!好老的 Emacs 配合 paredit 和良好配置的彩虹模式确实使任务变得微不足道。 - fr_andres

9

使用Lisp一段时间后,你不再注意括号,因此你的示例2看起来在结尾有很多不必要的空格。更具体地说,大多数Lisp程序员使用一个能够识别括号并正确关闭表单的编辑器,因此像C语言那样需要匹配开放和关闭的括号。

至于最后一个表单应该是什么

(* n (factorial (- n 1)))

或者

(* n
   (factorial (- n 1)))

这主要取决于个人喜好以及代码中发生的事情有多少(在这种情况下,我更喜欢前者,因为代码中几乎没有什么东西发生)。


7
为什么C程序员会写出这样的代码呢:
((a && b) || (c && d))

替代

((a && b) || (c && d
             )
)

第二种方式不是更易读吗?:)

但是,紧凑的闭合样式也适用于其他语言。

#include <stdlib.h>
#include <stdio.h>

int main(void)
{ int i, j, k;
  for (i = 0; i < 1000; i++)
  { for (j = 0; j < 1000; j++)
    { for (k = 0; k < 1000; k++)
      { if (i * i + j * j == k * k)
        { printf("%d, %d, %d\n", i, j, k); } } } }
  return 0; }

一段时间后,你不再“看到”大括号,只能通过缩进来确定结构。if/else 语句如下:

if (cond)
{ stmt;
  stmt; }
else
{ stmt; }

switch:

switch (expr)
{ case '3':
    stmt;
    break;
  case '4':
  { stmt;
    break; } }

结构体:

struct foo
{ int x;
  struct bar
  { int y; };
  union u
  { int a, b;
    char c; }; };

适用于低复杂度的函数。对于更复杂的函数,很难将远距离缩进对齐,并且在没有匹配括号的情况下知道上下文中的内容。 - Erik Aronesty
没有任何编程语言的格式可以完美地适应任意复杂度的表达式。 - Kaz

6

除了大多数Lisp IDE使用括号匹配外,写任何合理程序所需的空间量都是荒谬的!你会因为不断滚动而得到腕管综合症。如果将所有闭合括号放在自己的一行上,1000行的程序将接近一百万行代码。这在小程序中看起来更漂亮、更易读,但这个想法在大规模应用中并不适用。


5

首先,第一个案例只有4行代码,而第二个案例需要7行代码。

任何了解LISP语法的文本编辑器都可以为您突出显示匹配/不匹配的括号,因此这不是问题。


4

我在使用CakePHP框架和它的许多嵌套array()结构时,也遇到了同样的困境,有时达到5层以上。不久之后,我意识到过分追求括号闭合的完美只会浪费我的宝贵开发时间,并分散了我注意力。缩进是格式化中最有用的方面。


4

因为Lisp程序员看缩进和“形状”,而不是括号。


3

看起来这不是一个真正的问题,只是对Lisp的评价,但答案非常明显(但显然对于非Lisper来说并不明显!):在Lisp中,括号绝对不等同于C语言风格语言中的花括号 - 相反,它们与C语言风格语言中的括号完全相同。 Lisp程序员通常不会写

(foo bar baz
)

出于完全相同的原因,C程序员通常不会编写

foo(bar, baz
   );

原因

(if foo
    (bar)
    (baz))

外观与之前不同

if (foo) {
   bar();
} else {
   baz();
}

这个问题更多与if有关,而非括号!


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