Lisp圆括号

时间:2010-02-05 12:51:12

标签: coding-style lisp

为什么Lispers会像样本1中所示那样格式化代码,而不是样本2中所示?对我来说(我想,对于大多数来自不同编程背景而不是Lisp的人),样本2中显示的格式将更容易阅读。 Lispers是否更喜欢样本1样式?

样本1

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

样本2

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

13 个答案:

答案 0 :(得分:33)

LISP IDE环境倾向于自动平衡括号并根据嵌套级别管理缩进。样本2在这些情况下没有任何优势。

在C / FORTRAN / Pascal遗产中,您倾向于强调对嵌套的排序(代码解析树更浅更宽)。范围的结束是您的代码中更重要的事件:因此重点已经并且在某种程度上仍然更为重要。

答案 1 :(得分:21)

对于有经验的Lisp用户,嵌套级别比查找右括号更重要。将右括号放在自己的行上对嵌套级别没有任何帮助。

基本思想是括号直接围绕其内容。

(a)

而不是

(a
)

以下是:

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

VS。

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

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

有一次有经验的Lispers有时会在自己的行上写下括号:

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

这表示可以添加新人。将光标直接放在最后一行的第二个右括号之前,按c-o(空行),添加该子句并缩进它们再次对齐的括号。这样可以省去找到正确括号的“麻烦”,然后在所有括号在一行上关闭时按回车键。

答案 2 :(得分:15)

因为没有必要排队关闭parens。它不会在语义上添加任何内容。要知道要关闭多少,大多数Lispers使用一个好的编辑器,例如Emacs,它将parens与关闭parens相匹配,从而使任务变得微不足道。

在Python中,根本没有关闭的parens或end关键字,Pythonistas就可以了。

答案 3 :(得分:9)

在使用Lisp一段时间之后,你不再注意到括号了,所以你的例子中有两个因为在它的末尾有一堆不必要的空格。更具体地说,大多数lispers使用一个知道括号的编辑器并负责正确关闭表单,因此开发人员不需要像在例如中那样匹配开括号和右括号。下进行。

至于最后一个表格是否应

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

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

主要归结为个人偏好以及代码中发生了多少事情(在这种情况下,我更喜欢前者,因为代码中发生的事情很少)。

答案 4 :(得分:5)

除了大多数Lisp IDE使用paren匹配这一事实外,你用来编写任何合理程序的空间量都是荒谬的!你会得到所有滚动的腕管。如果将所有右括号放在自己的行上,那么1,000行程序将接近一百万行代码。它可能看起来更漂亮,更容易在一个小程序中阅读,但这个想法根本无法很好地扩展。

答案 5 :(得分:5)

为什么C程序员写的东西如下:

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

而不是

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

不会#2更容易阅读吗? :)

但是,严肃地说,成熟的结束风格也适用于其他语言。

#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; }

过了一会儿,你不会看到&#34;&#34;大括号,只是缩进给出的结构。 if / else看起来像这样:

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

开关:

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; }; };

答案 6 :(得分:4)

我在PHP中使用CakePHP框架及其许多嵌套array()结构遇到了同样的困境,有时甚至超过5层。过了一会儿,我意识到关于右括号的OCD什么也没做,只是浪费了我宝贵的开发时间,让我分心了最终的目标。缩进是格式化中最有用的方面。

答案 7 :(得分:3)

保存所有空间似乎是主要原因。如果它几乎可读(特别是一旦你习惯了语法)为什么要使用额外的3行。

答案 8 :(得分:3)

第一个也是显而易见的原因:第一个案例中有4个LOC,第二个案例中有7个LOC。

任何了解LISP语法的文本编辑器都会突出显示匹配/不匹配的括号,因此不是问题所在。

答案 9 :(得分:2)

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

答案 10 :(得分:1)

您可能会考虑没有()的Lisp变体。在Genyris看起来像这样:

def factorial (n)
  if (< n 2) 1
    * n
      factorial (- n 1)

答案 11 :(得分:1)

似乎这不是一个真正的问题,只是对Lisp的判断,但答案非常明显(但显然不是非Lispers!):Lisp中的括号绝不等同于C-中的括号和语言一样 - 相反,它们恰好等同于C语言中的括号。 Lispers通常不会写

(foo bar baz
)

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

foo(bar, baz
   );

原因

(if foo
    (bar)
    (baz))

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

if有关,而不是括号!

答案 12 :(得分:0)

我有一个解释,关于C程序员有问题,Lispers将所有剩下的结束括号放在最后。在我看来,这种做法可能是括号的含义。这个解释重叠并扩展了其他一些答案。

对于C程序员(或其他使用{braces}的语言,如C),Lisp看起来像是使用#|评论|#而不是/ *评论* /。看起来像Lisp括号()代替C括号{}。

但实际上,Lisp括号与C(和使用括号的类似语言)非常接近。考虑

defun f () nil

这定义了一个绝对没有的函数f,nil。我完全按照Lisp Listener的那样输入该行,它只是响应&#34; F&#34;。换句话说,它接受它,没有围绕整个事物的开始和结束括号。

当您在Listener中键入它时,我认为会发生什么?Listener会将您键入的内容传递给Eval。将其传递给Eval可以表示为:

Eval( Listener() )

Listener接受我的输入并返回我输入的内容。你会考虑&#34; Listener()&#34;在表达式中替换,使其变为

Eval (defun f () nil)

REPL(read,eval,print,loop)可以在概念上表示为递归

/* REPL expressed in C */
Loop()
{
    Print ( Eval ( Listen () ) );
    Loop();
}

在示例中理解的是,Listen(),Eval()和Print()在别处定义或内置于语言中。

听众让我输入

setf a 5

还允许我输入

(setf a 5)

但是当我输入

时会抱怨
((setf a 5))

如果括号等同于C语言括号,则侦听器会接受它。在我的C \ C ++编译器中,我可以输入

void a_fun(void)
{{
}}
没有抱怨。我甚至可以输入

void a_fun(void)
{{{
}}}

没有来自C / C ++编译器的抱怨。

但如果我敢打字

void a_fun((void))
{
}

我的C / C ++编译器抱怨 - 就像Lisp监听器抱怨一样!

对于编写嵌套函数调用的C程序员来说,编写

似乎更自然
fun_d( fun_c( fun_b( fun_a())));

而不是写

fun_d( fun_c( fun_b( fun_a()
                   )
             )
      );

在C中,大括号划分了一段代码。代码块是函数定义的一部分。 Lisp括号不会划分块。它们有点像C&C的括号。在C中,括号标出一个列表;也许是传递给函数的参数列表。

在Lisp中,似乎是开头的括号&#34;(&#34;意味着我们将开始一个新的列表,CONS构造的CAR侧将指向该列表。然后我们列出要包含的项目或可选择CAR的CAR侧指向,CDR侧指向nil(列表末尾)或下一个CONS。右括号&#34;)&#34;表示以nil终止列表,然后返回继续上一级列表。所以,C语言不做CONS。所以,C&C的括号和Lisp之间有一些区别。即便如此,似乎非常接近,甚至可能实际上是相同的事情。

有些人写道,如果你将函数移到括号之外,那么Lisp会变得更像C语言。例如,

(setf a 5)

变为

setf(a 5)

但实际上,Eval要求列表中的第一项是Eval需要调用的函数。它更像是将C函数传递给函数的指针。那么,它到底是什么

eval(setf a 5)

看起来更像是C.你甚至可以在听众中输入它,而不会受到听众或评论的抱怨。你只是说eval应该调用eval,然后调用setf。列表的其余部分是setf的参数。

无论如何,这就是我认为我所看到的。

只是Eval需要知道要调用哪个处理器。

我认为这个解释提供了一个理解为什么C程序员最初认为Lispers应该在他们关闭的开括号下对齐括号。 C程序员错误地认为Lisp的括号对应于C&C的大括号。他们没有。 Lisp的括号对应于C&C的括号。