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的括号。