Hatena::Groupbugrammer

蟲!虫!蟲!

Esehara Profile Site (by Heroku) / Github / bookable.jp (My Service)
過去の記事一覧はこちら

なにかあったら「えせはら あっと Gmail」まで送って頂ければ幸いです。
株式会社マリーチでは、Pythonやdjango、また自然言語処理を使ったお仕事を探しています

 | 

2011-12-03Python Advent Calender(全部俺):: 三日目

[][]オフサイドルールこそが正義である -- インデントブロックについて 00:17

 さて、Pythonを見ての第一印象としてまず思われるのは「インデント、気持ち悪い」という問題です。Lispなら「カッコ気持ち悪い」になるだろうこの問題は、よく語られ、よく議論されてきました。何かをとりあえず手っ取り早く済ませるとき、Pythonを使用する自分としては、むしろインデント・ブロックこそが正義であるという主張をして憚りません(かわいそうな人ですね :) )。では、インデント・ブロックの何が正義なのかという話をしようかと思います。(ちなみに、この記事は同時にNimrod Advent Calendar jp: 2011 : ATNDの記事でもあります。Nimrodについては、また三の倍数の日に改めて書こうと思います :) )

オフサイドルールとは?

 オフサイドルールとは、インデントをブロック構文として取り入れることです。他の言語だと、HaskellやF#、CoffeeScriptなどが採用していますね。

オフサイドルールって最近の流行りってだけでしょ?

 誤解です。

 確かに、C言語JavaPerlの影響力が強いため、どうしてもオフサイドルールを行う言語は無かったような気がします。しかし、実際のところは、Pythonの開発者が、開発に関わっていた教育用言ABCオフサイドルールを採用してますし、またHaskellの元になった言語であるMirandaも、同じようにオフサイドルールを採用しています。

 また、両者とも1980年代の中盤頃に作られていることから、インデント構文というのは、それなりに歴史があります。ただ、ABCMirandaは、JavaPerlと比べるならば、やはりマイナーといえばマイナーに感じますし、のちに、それなりに人気をあつめる言語になるPythonHaskellだって、潜伏期間を必要としました。その辺りの関係で、どうしてもオフサイドルールというのは一般的には感じられない要因の一つになっているのかな、とか思ったりします。

まずはPythonの作者の言葉から

 Pythonの作者は面白いことを言っている。

no title

読みやすいという答えに何かご不満でも? 私は至極もっともな理由だと思うよ。 コードの読みやすさを気にしないだろうか? 正しくインデントされていないコードは嫌では? インデントを文法の一部にすることで、すべてのコードが適切にインデントされることを保証できる。 かっこを用いる場合だが、その置き方にいくつかの流儀がある。 つまり、開きかっこをifと同じ行におくか、それとも次の行にか? 次の行だとして、インデントするか、しないか? 閉じかっこも同様。 もしどれかの流儀に慣れると、他の流儀は読みにくくなり得る。 コードをざっと読む場合、多くの人はいずれにしろインデントを頼りにするので、 これはしばしば次のようなバグを見落とすことにつながる。

 この話は面白くて、インデントだったらコーディングの習慣が一つにまとまるけど、中括弧を採用すると一つにまとまらない、ということを説明している。たとえば、ifだったりすれば、次の三通りがあるわけですよね。

if (true){
}

if(true)
{

}

if(true)
    {

    }

 つまり、この「書き方」の違いは、中括弧というだけで巧妙に隠されてしまっている。

 本当は、中括弧の使い方は各人によって違うわけです。自分であるならば、普段、一番最初のifと閉じ括弧を一緒にするものを使うものだから、2番目の一行に書かないスタイルを見ていると、なんとなく違和感を覚えてしまう。また、たちの悪いことにifでは使わないけど、関数のブロックの始まりだったら改行するという人達もいる。そういう些細なことでいちいち喧嘩するならば、インデントでひとまとめにしてしまったほうが早いわけで、その辺の差異をインデントってだけであたかも一緒のように考えられてしまっているのはまずいよね、という話なわけです。実際には、こんだけスタイルがあって発狂しかねない

 また、中括弧の実装だって、実際はわかったものじゃない。例えばJavaScriptを参考にしてみよう*1

function func() {
    return
    {
        name: "Batman"
    };
}

 ここにおいて、バットマンという変数が入ったオブジェクトが返ってくることが期待されていると思うのですが、しかしJavaScriptは実装上、セミコロンに関して厳格ではないため、行が正しくセミコロンで終了していない場合、かわりにセミコロンを挿入してしまうので、実際はreturnは何も返さない(!!)という解釈になります。つまり、現実的に考えて、中括弧ですら派閥があり、さらには仕様による予期しない挙動すらあります。

 まだ、インデントを採用する理由があります。例えば、サンプルコードでこういうのを書いてみましょう。

if (i == 0){
    console.log("hoge");
    if(j == 0){
        console.log("fuga");
}else{
    console.log("poko");
}

 これはエラーが出る例ですが、この場合、どちらのifにelseを入れるべきのかわかりません(インデントから考えて、二つ目のifを閉じるべきなのでしょうけれど)。このように、ifがどっちのelseにかかるべきなのか、という問題がでてきます。Pythonだと明確です。

if i == 0:
    print "hoge"
    if j == 0:
        print "fuga"
else:
    print "poko"

 インデントの構造を取ることによって、閉じ忘れによるelseの曖昧さがありませんし、インデントと、本当にかかるべきelseが曖昧ということもないかな、と。(本当はインデントと一致するべきなのでしょう。ただ、ドキュメントと違うコードなんてたまにでくわしますね。)

 また、WikipediaPythonの項では、次のような例も紹介されています。

if (x > 10)
    x = 10;
    y = 0;

 もちろん、この中括弧を眺めていて、インデントだったら安心!といえば、そうではなく、実際はタブやスペースを混ぜたりすることがあるので、少々危険だったりするんですが。

 他にも、インデントを使うべき理由はあります。

初心者にやさしいという説がある

 例えば、Rubyist Magazine - Rubyist のための他言語探訪 【第 1 回】 Pythonには、こういう話が取り上げられています。

このインデントによるブロックは Python専売特許ではありません。 たとえば、Occum や Haskell といった言語でも同じルールが採用されています。 Python 自身はこのルールを ABC から受け継いでいます。 ABC では文法に関してさまざまな研究が行われたようで、インデントを使うのが初心者にとってもっとも間違いにくいブロックの表現なのだそうです。

 これが本当かはわかりませんが、初心者にやさしいというのは、ちょっと面白い話ですね。

ネストが深いならコードを見直すべき

 さて、インデント構文を採用すると、どうしてもネストが深くなりそうで嫌だ、というのがあるかもしれません。しかし、そのような心配は無用です。というのも、ネストが深いというのは、何処かでロジックを洗練させるべき必要がある、という予兆を作ってくれるからです。逆に言うならば、そういうのを「嫌だなー」と思い始めたら、関数を用意したり、あるいは不必要な分岐をしていないかどうかを見直すことが出来たりします。要するに、悪いコードは外から見てもわかるソースというわけですね。逆にネストを下げることで、その嫌さを誤魔化すとするならば、それは何かを誤魔化している証拠でもある、とも言えるわけですね :) 。

読みやすくなる

 コードの可読性の問題を考えると、「読みやすい」というのは各人にとってそれぞれです。一行に凝縮したものを読みやすいと感じる人もいれば、複数行に分かれた行を読みやすいと感じる人もいます。中括弧なら、一行に凝縮して書くことが可能です。ですが、そのようなコードは往々にして他の人をちんぷんかんぷんにすることがあります。オフサイドルールにも抜け道はありますが、原則としてオフサイドルールを守ることは、誰にとっても「それなりには読みやすい」という最低限のラインを、構文的に作ることが可能になります。

自分の好みとして

 これは自分の好みなのですが、例えば次のようなコードを考えましょう。

if (hoge === "foobar"){
  //<-- "Enter"
} //<-- ブロックを閉じるさいは、"Shiht + ]"を押してから"Enter"をしなければならない。
def hoge():
    pass # <-- "Enter"
#<-- "Enter"

 中括弧だと、ブロックを閉じるときには"Shiht + ]"が出てきて、ちょっと面倒くさいなーと感じます。逆に、インデント構文だと、単にEnterを押すだけです。なので、最後の分を入力し終わったときに、"Enter" -> "Shiht + ]" -> "Enter"というよりも、"Enter" -> "Enter"のほうが、スムーズに書けるので、ちょっと嬉しいです。

インデントの悪い点

 じゃあ、インデントの悪いところは下のようになるかなと

自分の気に入ったように書けない

 これは逆説的なことなのですが、インデントの独裁制によって「そこそこの読みやすさ」を確保しているのはいいのですが、逆に書き手になると困ったことになります。考えてもみればわかるように、各人にとってお気に入りのスタイルはそれぞれ違うからです。選択肢があるということは、その中から自分の好きな書き方を選ぶということが出来ます。

 「そんなことか!」と思いますが、これは些細で重要な(どっち?)点だと思うんです。

 「やり方はひとつではない」というのは、人の自主性を育みます。中括弧をどうするか、あるいはインデントをどうするかは、プログラミングにおいて、スタイルというか、文体を作り出します。しかし、インデントが作られると、その魅力の一つが失われてしまいます。

これはタブなの?スペースなの?

 もう一つの問題があります。インデントは単なる字下げです。ですが、字下げの方法には二つあります。それはスペースとタブです。この二つは混ぜることが可能ですし、表現上は一緒に見えるときがありますが、実際は違うものです。この性質を利用したのが、ジョーク言語・Whitespaceだったりします。

 この空白とタブという問題はつきまとっていて、Pythonの2005年のエイプリルフールではむしろ「採用してくれ」と言われているのが、ログとして残っています。そのように、インデントにおいて、スペースとタブを混ぜることは、インデントの良さを破壊しかねない要因ですし、またコードによっても、タブを使っているのか、スペースを使っているのか安心できないわけですね。そういう意味においては、エディタの力もある程度必要になってくるわけで、「エディタオフサイドールに設定する必要がある」というのはちょっと敷居が上がるのかな、と思ったりします。

インデントを嫌わないで!!

 そんなわけで、今回はオフサイドルールについて説明をしました。「インデントがキモい」と言わずに、愛してあげてくださいね。

*1:『JavaScriptパターン』参照

 |