为什么Lisp社区更喜欢在函数末尾累积所有括号?


26

为什么Lisp社区更喜欢在函数末尾累积所有括号:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)))

为什么不采用C或Java这样的约定?
好吧,Lisp比这些语言要古老得多,但是我说的是当代的Lispers。

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

注意:代码段摘自《 Clojure的喜悦》一书。


13
有人告诉我,在打孔卡的时代,通常的做法是结束一个Lisp程序并在其上加一张带有80个右括号的额外卡,以确保程序中所有的未用括号都足够匹配。
阿尔及尔

2
哇〜我喜欢。我一直讨厌括号,但是您的第二个例子对我来说似乎很令人满意。不如Python好,但有所改进。
埃里克·威尔逊

5
因为他们可以?
Muad'Dib 2011年

如果Clojure的方言将缩进提升为代码结构,那就太好了。这将完全消除括号(例如F#等)。当然,如果需要,括号仍然是合法的。
intrepidis 2013年

Answers:


28

基于Algol的语言鼓励使用大括号的原因之一是鼓励在定界大括号之间添加更多行而不必移动大括号。也就是说,如果以

if (pred)
{
  printf("yes");
}

很容易出现并在花括号内添加另一个语句

if (pred)
{
  printf("yes");
  ++yes_votes;
}

原来的表格是

if (pred)
{ printf("yes"); }

那么我们就不得不“移动” 两个大括号,但是我的示例更关心后者。在这里,花括号界定了打算作为语句序列的对象,这些语句主要是出于副作用而调用的。

相反,Lisp缺乏陈述;每种形式都是表达式,产生一些值-即使在极少数情况下(例如Common Lisp),也有意通过空(values)形式将该值选择为“无值” 。与嵌套表达式相反,查找表达式序列不太常见。不再经常出现“打开一系列步骤直到结束定界符”的愿望,因为随着语句消失并且返回值变得更常见,因此忽略表达式的返回值变得更加罕见,因此更多很少评估仅针对副作用的表达序列。

在Common Lisp中,progn表单是一个例外(及其同级):

(progn
  (exp-ignored-return-1)
  (exp-ignored-return-2)
  (exp-taken-return))

在此,progn按顺序计算三个表达式,但丢弃前两个表达式的返回值。您可以想象在其最后一行上写上最后一个右括号,但要再次注意,由于最后一种形式在这里是特殊的(尽管不是普通Lisp的特殊含义),并且经过独特的处理,很可能会添加新的表达式中间的表达式,而不仅仅是“在末尾添加另一个”,因为调用者不仅会受到任何新的副作用的影响,而且还会受到返回值可能发生变化的影响。

简而言之,Lisp程序的大多数部分中的括号是分隔传递给函数的参数(就像使用C语言一样),而不是分隔语句块。出于相同的原因,我们倾向于使包围C中函数调用的括号保持在参数附近,因此在Lisp中也是如此,而没有动力偏离该紧密分组。

括号的关闭远不如括号的打开形式重要。随着时间的流逝,人们学会了忽略括号并按形状进行读写,就像Python程序员所做的那样。但是,不要让这种类比导致您认为完全删除括号是值得的。不,这是最适合的辩论comp.lang.lisp


2
我认为结尾处的添加并不罕见,例如(let ((var1 expr1) more-bindings-to-be-added) ...),或(list element1 more-elements-to-be-added)
Alexey 2013年

14

因为它没有帮助。我们使用缩进来显示代码结构。如果要分隔代码块,则使用真正的空行。

由于Lisp语法是如此一致,因此括号是程序员和编辑者缩进的权威指南。

(对我来说,问题是,为什么C和Java程序员喜欢花大括号。)

只是为了演示,假设这些运算符可用类似C的语言提供:

Foo defer_expensive (Thunk cheap, Thunk expensive) {
    if (Foo good_enough = force (cheap)) {
        return good_enough; }
    else {
        return force (expensive); }}

两个闭合括号关闭两个嵌套层。语法显然是正确的。与Python语法类似,花括号只是显式的INDENT和DEDENT标记。

当然,这可能不是“一个真正的支撑风格” TM,但是我相信这只是历史性的偶然和习惯。


2
另外,几乎所有的lisp编辑器在关闭时都会标记匹配的括号(有些也可以纠正)]反之亦然),因此即使您不检查缩进级别,也知道您已关闭了正确的金额。
配置器

6
WRT是“问题”,因为采用第二个示例的样式“抛出”关闭标记可以使您轻松地将它们对准眼睛,并看到关闭了哪些标记,即使您只是在文本编辑器中也没有自动匹配/突出功能。
梅森惠勒

1
@梅森惠勒是的,是的。
Chiron

3
TBH,这对我说的是LISP中的括号大部分都是多余的,可能(如C ++),缩进可能会误导人-如果缩进告诉您您需要知道什么,它也应该告诉编译器同样的事情,就像Haskell和Python中一样。BTW-大括号与C ++中的if / switch / while / whatever具有相同的缩进级别,有助于防止缩进产生误导的情况-LHS的可视化扫描告诉您每个开括号都与一个大括号匹配缩进与那些花括号一致。
Steve314,2011年

1
@ Steve314:不,缩进是多余的。缩进在Python和Haskell中也可能产生误导(考虑一次性缩进或表格),只是解析器也会误导。我认为括号是编写者和解析器的工具,而缩进是编写者和(人类)读者的工具。换句话说,缩进是注释,括号是语法。
Svante

11

然后,代码要紧凑得多。无论如何,编辑器中的移动都是通过s表达式进行的,因此您不需要该空间来进行编辑。代码主要是通过结构和句子来读取的,而不是通过遵循定界符来读取的。


收到您的答复是多么荣幸:)谢谢。
凯龙

哪个编辑器通过s表达式移动?更具体地说,我该怎么做vim
哈森

2
@HasenJ您可能需要vimacs.vim 插件。:P
Mark C

5

Lispers,你知道的,憎恨bletcherous因为它可能是,还有就是一定JE NE最高审计机关quoi第二个例子:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

起初,我不能指望这种魅力的来源,然后我意识到它只是缺少一个触发因素:

(defn defer-expensive [cheap expensive]      
  (if-let [good-enough (force cheap)]
    good-enough   ;   )
    (force expensive) 
  )
)

瞧!

我现在要练习Lisp匪徒的握力!


2
这是我在本网站上看到的最有趣,最被低估的答案之一。我给我戴帽子。
byxor

0

就我而言,我发现专用于定界符的行浪费了屏幕空间,并且当您用C编写代码时,也存在

if (pred) {
   printf("yes");
   ++yes_votes;
}

人们为什么要把开括号放在“ if”的同一行中以节省空间,并且因为当“ if”已经有自己的行时看起来有多余的行看起来显得多余。

通过将括号放在最后可以达到相同的结果。在C语言中看起来很奇怪,因为语句以分号结尾,而括号对没有像这样打开

{if (pred)
    printf("yes");
}

就像末端的封闭支架看起来不合时宜。它像这样打开

if (pred) {
    printf("yes");
}

通过“ {”和“}”让您清楚地了解该区块及其限制

在像vim一样通过突出显示括号来匹配括号的编辑器的帮助下,您可以转到所有括号被打包的末尾,并轻松地在它们之间移动并匹配所有开头的括号,并查看嵌套的lisp形式

(defn defer-expensive [cheap expensive]
    _(if-let [good-enough (force cheap)]
    good-enough
    (force expensive)_))

您可以将光标放在中间的结束括号中,并突出显示if-let的开始括号。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.