なぜLispは「括弧が多い」印象になるのか?

最近、全然Lispネタ書いてないなぁとか思って、書き散らかして完成しないで下書きだけが増えて言ってます。

先に謝っておきますが、なんとなくうだうだ考えたりソース書いたりして、グダグダなエントリ。ごめんなさい。

しかも、ずっと前の話題で、すっげぇ遅れている感。

Lispは括弧が沢山ある?

まぁなんと言いますが、これほど多くの人に指摘されて、これほど多くのLisperが反論して来た問答は無い気がする。Lispを見たこと無い人がまずLispコードを見たらまず出てくる感想がコレでだと思う。

Lispは本当に括弧が多いか?

じゃあ実際にはLispは括弧が多いかと言われたら、このブログが反論になるだろう。

http://e-arrows.sakura.ne.jp/2010/08/is-lisp-really-has-too-many-parenthesis.html

しかし、それでは上記でLispに疎い人が主張する主観と一致しない。それに、実は僕もLisperでありながらLispコードをぼんやりと見ると、多いと思ったりする。ではなんで「括弧が多い」と思うのか?

主観的な「括弧の多さ」

上記のブログで書かれているコードのJavaScriptCommon Lispを比較してみよう。以下のコードをぱっと見てみよう。あくまでも、ぱっと見だ。良く読んではいけない。

var fact = function (n) {
  return n ? n * fact(n - 1) : 1;
};

for (var i=1; i<=(arguments[0]||1); ++i) {
  print(i+"! = "+fact(i));
}
(loop for i from 1 to (or (parse-integer (cadr *posix-argv*)) 1)
      for acc = 1 then (* acc i)
      do (format t "~a! = ~a~%" i acc))

なぜこの二つを選んだのは、何となくです。

さて、どっちが括弧が多いと思う?
ぱっと見ると、Common Lispの方が多く見える気がしません?(僕だけ?)
コレに関して実験心理学的な検証はしたい。だが、面倒なんでだれかやってくれるかなー。

しかし上記のエントリによると、Common Lispが12個で、JavaScriptが16個。実はJSの方が多いのだ。

と何となく考え続けていると、知人から以下の指摘があるんじゃないかという意見を貰った。

  • 括弧の密度
  • Lisp書き方は括弧が目立つ
  • 大括弧、中括弧を書かない影響

括弧の密度

なるほど、如何にソースコードの括弧が少なくても、コード自体が短ければ、括弧が多く見えるのかもしれない。
密度を正確に公平に拘って、議論しようと思えば、延々と議論が出来るが、面倒なので、おおざっぱに括弧/行数が密度だとする。

上記のソースコードは括弧/行数を求める、JavaScriptは16/6=2.66666..., Lispは12/3=4で、JSの方が括弧/行数は小さくなる。

Lispは括弧の密度が高いのだ。

Lispの書き方は括弧が目立つ?

どういうことかといえば、他の言語よりLispは括弧が目立つ一にある場合が多いのじゃないかと思った。多くの場合は先頭に括弧があることが多いし、他のシンボル空白を空けて括弧を書くことが多い。一方、他の言語だと括弧は大体、ソースコードの行の後ろの方にある。それが逆に括弧の「多さ」を強調する結果になっているのかもしれない。

階乗を求める関数で比べてみよう。

function fact(n)(
  if( n == 0 )( 
     return 1;
  )else( return (n * fact(n - 1)); ));

元のコードはJSだが、比較のために中括弧を全部丸括弧にした。あとシンタックスの色づけを切った。

(defun fact (n)
   (if (= n 0) 
       1 
       (* n (fact (- n 1)))))

これはCommon Lispのコードだ。

括弧の数はどちらも同じ14個である。行数で言えばどちらも4行である。どうだろう?Lispの方が多く感じないだろうか?

中括弧と大括弧を書かない影響?

ほとんどのLispは中括弧や大括弧を書かないことが多い。(Gaucheとか大括弧を用いている例はあるが)。それも印象に影響を与えているのでは?と言う話。

さっきのJSの中括弧を全部丸括弧に戻したコードがこちら。

function fact(n){
  if( n == 0 ){ 
     return 1;
  }else{
     return (n * fact(n - 1)); }};

比較のために再掲。

function fact(n)(
  if( n == 0 )( 
     return 1;
  )else(
     return (n * fact(n - 1)); ));

なんか微妙だ。バイアスが掛かっているのかもしれない。あんまり中括弧や大括弧は関係が無いかもしれない。

どちらかというとソースコードの見やすさで言えば、中括弧の方だろう。だが、それはなんか括弧の多さには直接には関係が無い気がして来た。

まとめ

Lispは「括弧が多い」印象になる。

これは、筆者の経験上おそらく言えるだろう。

んで「括弧が多い」理由としてはこちら

Lispは括弧の密度が高いから

Lispは括弧が目立つから

この理由はこれは無いと思った。

中括弧と大括弧を書かない影響はないだろう。

まぁ、しっかりとした検証は、実験心理学の手法を使って調査したいが、めんどくなくなって来た。結局はLispは括弧が多い印象になるのは間違いないことだ。だからと言って、それがLisp自体の良さとは関係がないだろう。


あと、もちろん全部僕の主観です!あしからず。

LisperはなぜLispが読みやすいし書きやすいと思うのか?

まぁ、なんというか、Lispネタです。はい。

括弧のせいで挫折した!

なんかこういう話をよく聞く。ぶっちゃけ、あなたがちゃんと真面目に読んでないからでは?とか思ったりするのだが、まぁそれは置いておこう。

要は、括弧が多過ぎて、カッコとコッカの対応が取れないというあたりかな?挫折する理由としては。何となく思うのが

エディタが良くない

多くの場合コレだと思う。あと

書き方が良くない

まぁコレもあるよね。地味に全然インデントしてなかったりする。そりゃあ読みにくいですよ。

大学のセンセの教え方が良くない!

知るか!

Lisper 「Lispは読みやすい!」

Lisperは多分みんな、「Lispの読みやすい」と思うだろう。@valvallowさんあたり、いつも叫んでいる。

末期症状?

”Lisperが「Lispが読みやすい」と言うのは、Lisp病の末期症状である!(?) "とのこと。知らんがな。

まぁ、Lisp病には健康的な被害は全くなさそうだし、だったらあなたもLisp病に掛かってみればよい!つまり

みんなでLisp病に掛かればいいんだ!





Lispが読みやすい理由、書きやすい理由

そもそもなんでみんなキモイと言うのか?

だいたい、Lispに疎い人に、Lispがキモイ理由を聞けば以下の2つの理由を挙げてくるんだ。

  • 「括弧が多い」から
  • 前置記法だから


しかし、これには僕は反論する。むしろ、「括弧が多い」「前置記法」はむしろ、Lispの利点なのだ!



中値記法はよみにくい!

僕は、「中置記法」は読みにくいと思う。なぜか?中置記法こそが曖昧にする原因だからだ!

そもそも、「中置記法」が実装されたのは数学の加減乗除によるところが多い。普段数学では中置記法で演算する。だからプログラムでも同様に表現したい欲求があって、中置記法を用いたと思う。

1 + 2 * 3 + 4 - 5 / 6

しかし、どうだろう?数学的に表現したいから、という目的のためだけに中置記法を用意するのか?と思う。ぶっちゃけ、加減乗除の表現だけが読みやすくなっただけのように思う。


一方、中置記法は構文木からは遠い表現である。前置記法と比べて、計算式を見て、構文木を構築するのは大変だと思う。

以下のコードを見てほしい。rubyで実行すると3が返ってくる

1 + 2 * 3 + 4 == 1 + 2 / 5 && 10 <= 10 + 20 || 1 + 2

これは非常に読みにくいと思う。なぜ読みにくいのだろうか?
構文木の根が分かり辛いからだ。

前置記法的に書き換えてみよう

|| && == + + 1 * 2 3 4 + 1 / 2 5 <= 10 + 10 20 + 1 2

これも、大分キモイが、少なくとも、一番左の演算が構文木の根の演算と言うことは理解できる。

これにはもう一つ重要な点がある。前置記法は演算の優先順位なんて必要がないのだ!逆にいえば、中置記法は演算の優先順位を考えなければならない。これは、実装する側にとっても、利用する側にとっても大きな負担になる。実装する側は自然な優先順位を考えないといけないし、利用者は優先順位を覚えないといけないからだ!



括弧はより読みやすくするために存在する

これは中値記法であっても、前置記法であっても同様だと思う。

((1 + 2 * 3 + 4) == (1 + 2 / 5) && (10 <= 10 + 20)) || (1 + 2)

一瞬で木が見えるようになったのではないだろうか?

さっきの、前置記法に括弧を付けてみよう

(|| (&& (== (+ (+ 1 (* 2 3)) 4) (+ 1 (/ 2 5)) (<= 10 (+ 10 20) ) (+ 1 2))

キモイ。だが、これに前置記法にインデントを付けたらどうだろうか?

(||  (&& (== (+ (+ 1 (* 2 3)) 4)
                (+ 1 (/ 2 5))
         (<= 10 (+ 10 20))
     (+ 1 2))


見やすくなったのではなかろうか?
Pythonまたはそれに類する言語以外で、Lispほどインデントが重要な言語は無い。もし、LIsperがLispコードが読みにくいときは、ほとんどの場合、インデントされてない時だ!



中置記法の括弧は書き辛い!

以下の例を考えよう。これは、実際に書いてもらいたい。

((((1 + 2) * ((2 + 3) / (3 - 4))) + (5 / 6))

これは非常に書き辛い。なぜ書き辛いのだろうか?
それは、

式を書く時、構文木の深さが分からないため、最初にどのぐらい括弧を書けばいいか分からないからである。

この式を書く時、どれだけの人が最初に(((が書けるのだろうか?

では、前置記法はどうだろうか?

(+ (* (/ (+ 2 3)
         (- 3 4))
      (+ 1 2))
   (/ 5 6))

すごく書きやすい。なぜなら、構文木の根から書くため、構文木の深さが分からなくても、式が書けるからである。最後に括弧の帳尻合わせをすればいい。

つまり、Lispは自然と括弧が書きやすいように出来ているのだ。


まとめ

Lispは読みやすくて書きやすい。なぜなら、それは、「前置記法と括弧」が構文木を根から明確にし、また、自然と括弧が書きやすい様になっているからである。


中置記法は、構文を曖昧にし、演算子の優先順位、右結合性、左結合性などを考えなければならず、より自然になるどころか、より言語として複雑になる可能性を秘めている。





少なくとも、「Lispが読みやすくて書きやすい」と言うのは、ただのLispキチガイではなく、むしろ自然なことなのだ! <- ココ大事。



追記 : 中置記法を糞みたいに言ったが、もちろん、中置記法の方が数学的な記述は自然だ。コレだけは言っておく。しかし、それを実現するために、言語をより複雑にするのはどうだろうか?


追記2: 言われると思ったがやっぱり指摘されたので、補足しておくと、Smalltalkとかの中置記法は、演算(というか全部メッセージなので)の優先順位はなく、すべて等しいですが、数学的な演算の順番とは異なるので、あえて言いませんでした。(このことについては後のブログに書き散らかします)