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

注:コードスニペットは、「The Joy of Clojure」という本からのものです。


13
パンチカードの時代には、80個の右括弧が追加されたカードでLispプログラムを終了し、プログラム内の開いている括弧がすべて一致することを確認するのが一般的だったと言われています。
アルガー

2
うわー私はそれが好き。私はいつも括弧を嫌っていましたが、あなたの2番目の例は私にとって楽しいようです。Pythonほどではありませんが、改善されています。
エリックウィルソン

5
できるから?
ムアディブ

Clojureの方言がインデントをコード構造に昇格させたらいいと思います。これにより、括弧が完全になくなります(F#など)。もちろん、必要に応じて括弧は引き続き有効です。
イントレ

回答:


28

Algolベースの言語が独自の行にブレースを推奨する理由の1つは、ブレースを移動することなく、区切りブレースの間に行を追加することを奨励することです。つまり、最初に

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

かっこ内に別のステートメントを追加するのは簡単です。

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

元のフォームが

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

次に、2つのブレースを「移動」する必要がありますが、私の例では後者をより重視しています。ここでは、中括弧は、主に副作用のために呼び出される一連のステートメントとなるものを区切っています。

逆に、Lispにはステートメントがありません。すべてのフォームは expressionであり、何らかの値を生成します。まれなケース(Common Lispの場合)であっても、その値は空の(values)フォームを介して「値なし」になるように意図的に選択されます。ネストされた式とは対照的に、式のシーケンスを見つけることはあまり一般的ではありません。「終了デリミタまで一連のステップを開く」という欲求はそれほど頻繁には発生しません。なぜなら、ステートメントがなくなり、戻り値がより一般的な通貨になるにつれて、式の戻り値を無視することがより少なくなるためです。副作用だけで一連の式を評価することはまれです。

Common Lispでは、prognフォームは例外です(兄弟も同様です):

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

ここでprognは、3つの式を順番に評価しますが、最初の2つの戻り値は破棄します。最後の閉じ括弧を独自の行に書くことを想像できますが、ここでは最後の形式が特別であるため(ただし、特別であるというCommon 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

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

2つの閉じブレースは、2つのネストレベルを閉じます。構文は明らかに正しいです。Python構文と同様に、中括弧は、明示的なINDENTおよびDEDENTトークンです。

もちろん、これは「1つの真のブレーススタイル」TMではないかもしれませんが、それは歴史的な事故と習慣に過ぎないと考えています。


2
また、ほとんどすべてのLispエディターは、閉じるときに一致する括弧をマークします(一部は正しい)]またはその逆)ので、インデントレベルをチェックしなくても正しい量を閉じたことがわかります。
コンフィギュレー

6
WRTの「質問」。2番目の例のスタイルで「スローアラウンド」クローズトークンを使用すると、自動照合なしでテキストエディタを使用している場合でも、目で簡単にトークンを並べて、何が閉じるかを確認できます。強調表示機能。
メイソンウィーラー

1
@Mason Wheelerはい、そのとおりです。
カイロン

3
TBH、これが私に言っていることは、LISPの括弧がほとんど冗長であり、インデントが誤解を招く可能性がある(C ++のように)-インデントがあなたが知っている必要があることをコンパイラに伝えなければならない場合HaskellやPythonのように。ところで-C ++のif / switch / while / whateverと同じインデントレベルでブレースを使用すると、インデントが誤解を招く場合を防ぐことができます-LHSを視覚的にスキャンすると、すべてのオープンブレースがクローズブレースに一致することがわかりますそして、そのインデントはそれらの中括弧と一致しています。
Steve314

1
@ Steve314:いいえ、インデントは冗長です。インデントはPythonやHaskellでも誤解を招く可能性があります(1回限りのインデントまたは表形式について考えてください)。これは、パーサーも誤解を招くだけです。括弧はライターとパーサーのツールであり、インデントはライターと(人間の)リーダーのツールだと思います。つまり、インデントはコメントであり、括弧は構文です。
スヴァンテ

11

コードはそれよりはるかにコンパクトです。とにかくエディター内の移動はs-expressionsによるものなので、編集のためにそのスペースは必要ありません。コードは主に構造と文によって読み取られます-区切り文字に従うことではありません。


あなたから答えを得るのはなんと名誉なことでしょう:)ありがとう。
カイロン

S式で動くエディターは何ですか?より具体的には、どうすればそれvimを実現できますか?
長谷さん

2
@HasenJ vimacs.vim プラグインが必要になる場合があります。:P
マークC

5

それによってはhatefully bletcherous Lispers、あなたが知っている、そこにある特定の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
これは、私がこのサイトで見た中で最も面白くて過小評価されている回答の1つです。帽子を傾けます。
-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の開き括弧を強調表示できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.