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))) ) ) 

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

在C / FORTRAN / Pascal遗产中,您倾向于强调嵌套sorting(代码parsing树更浅更宽)。 范围的终点在你的代码中是一个更重要的事件:因此强调已经在某种程度上更重要。

对于有经验的Lisp用户来说,嵌套级别比查找括号更重要。 在自己的行上放置圆括号并不能真正帮助嵌套级别。

基本的想法是括号直接在他们的内容附近。

 (a) 

并不是

 (a ) 

接下来是这样的:

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

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

编辑Lisp文本的基本思想之一是,你可以通过(双击)括号来select一个列表(或者当光标位于expression式中或者在括号中时使用键盘命令)。 然后,您可以剪切/复制expression式并将其粘贴到其他函数的另一个位置。 下一步是select其他function并重新缩进function。 完成。 没有必要删除或引入用于closures括号的新行。 只需粘贴并重新缩进。 它恰好适合,否则你会浪费时间来格式化代码,或者你需要用一些编辑器工具来重新格式化代码(这样左边的括号就可以单独使用了,大多数情况下会创build额外的工作并妨碍移动代码。

有一次,经验丰富的Lispers有时会自己写一些括号:

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

这里表示可以增加新的人员。 将光标放在最后一行的第二个右括号之前,按下co(空行),添加子句并缩进再次alignment的括号。 这样可以节省“麻烦”,find正确的圆括号,然后按回车键,当所有括号在一行上closures时。

因为没有必要排队closures的人。 它不会添加任何语义。 要知道有多less人closures,大多数Lispers使用一个很好的编辑器,比如Emacs,匹配parens来closuresparens,从而使任务变得微不足道。

在Python中,根本没有end关键字或end关键字,而Pythonistas生活得很好。

经过一段时间的Lisp,你不会再注意到括号,所以你的例子2在它的末尾有一堆不必要的空白。 更具体地说,大多数Lispers使用一个知道括号的编辑器,并正确地closures表单,所以作为开发人员,您不需要像在C中那样匹配开合圆括号。

至于最后一张表格是否应该是

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

要么

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

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

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

为什么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; } 

过了一段时间,你不会“看”大括号,只是由缩进给出的结构。 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; }; }; 

我在PHP中使用CakePHP框架和它的许多嵌套的array()结构经历了相同的困境,有时甚至超过5层以上。 过了一会儿,我意识到,关于右括号的强迫症除了浪费宝贵的开发时间之外别无select,只能把我从最终目标上分散开来。 缩进是格式化最有用的方面。

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

首先是明显的原因:第一种情况下4 LOC,第二种情况下7 LOC。

任何知道LISP语法的文本编辑器都会突出显示匹配/不匹配的圆括号,所以这不是问题。

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

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

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

似乎这不是一个真正的问题,只是对Lisp的一个判断,但是答案是非常明显的(但对非Lispers来说显然不是):Lisp中的圆括号与C语言中的括号没有任何区别 – 相反,它们与C语言中的圆括号完全相同。 Lispers通常不写

 (foo bar baz ) 

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

 foo(bar, baz ); 

原因

 (if foo (bar) (baz)) 

看起来不同于

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

if比括号更有用​​的if

我有一个解释,C程序员在Lispers把剩下的最后一个括号放在最后的时候有问题。 在我看来,括号的含义可能是原因。 这个解释重叠并扩展了一些其他的答案。

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

但是实际上,Lisp括号的含义与C中的相同(以及使用括号的类似语言)非常接近。 考虑

 defun f () nil 

这定义了一个函数f,它完全没有,无。 然后我按照Lisp Listener那样input那行,它只​​是响应“F”。 换句话说,它接受它,没有围绕整个事物的开始和结束括号。

当你将其input到Listener中时,我认为会发生什么是Listener将你input的内容传递给Eval。 传递给Eval可以表示为:

 Eval( Listener() ) 

Listener接受我的input并返回我input的内容。 而且你会考虑在expression式中replace“Listener()”,以便它变成

 Eval (defun f () nil) 

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

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

在这个例子中,我们理解Listen(),Eval()和Print()是在其他地方定义的,或者是构build在语言中的。

听者让我input

 setf a 5 

也让我打字

 (setf a 5) 

但是当我input时抱怨

 ((setf a 5)) 

如果括号与C语言大括号相同,那么听者会接受它。 在我的C \ C ++编译器中,我可以input

 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中,圆括号标出一个列表; 也许是一个传递给函数的参数列表。

在Lisp中,似乎开头的圆括号“(”表示我们要开始一个新的列表,CONS构造的CAR侧将指向这个列表,然后列出要包含的项目或可选地指向CONS,CDR侧指向零(列表结束)或下一个CONS。右括号“)”表示以零结束列表,并返回以继续上一级列表。 所以,C语言不做CONS的。 所以,C的括号和Lisp之间有一点区别。 即便如此,似乎非常接近,甚至可能是几乎相同的事情。

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

 (setf a 5) 

 setf(a 5) 

但实际情况是,Eval要求列表中的第一项是Eval需要调用的函数。 这更像是将一个C函数传递给一个函数的指针。 那么,究竟是什么呢?

 eval(setf a 5) 

这看起来更像C.你甚至可以在听众那里input它,没有听众或eval的抱怨。 你只是说,eval应该调用eval,然后调用setf。 列表的其余部分是setf的参数。

无论如何,这正是我想我所看到的。

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

我认为这个解释提供了一个理解,为什么C程序员最初认为Lispers应该在他们closures的开头括号之下alignment左括号。 C程序员错误地认为Lisp的括号对应于C的括号。 他们没有。 Lisp的括号对应于C的括号。