do文の意味

きっかけは、こんな雰囲気のコードでした:

main = do
  args <- getArgs
  logXY x y
  where
    logXY x y = putStrLn $ (show x) ++ " = " ++ (show y)
    (x:y:_) = args

もうちょっとましな書き方はあるだろうという話はここでは触れないことにして、これは、コンパイルしないわけです。エラーは:

do.hs:10:12: Not in scope: `args'

つまり、最後の行(x:y:_)=argsで使われているargsの定義がわからないといっているわけです。そんなこといったって一番最初にargs <- getArgsといってるじゃないかといった感じなわけです。それに引き換え、logXYのほうはエラーが出ません。

エラーの感じから言って、where以下のブロックはdo文よりもスコープが外側なようです。

そもそも、do文の「切れ目」ってどこでしょう?do文は各行を>>=や>>でつなげたものですから、上のdo文は、次のように書き換えられるわけです...

main = 
  getArgs >>= \args -> logXY x y
  where
    logXY x y = putStrLn $ (show x) ++ " = " ++ (show y)
    (x:y:_) = args

こう書き換えると、where節からargsが参照できない理由がわかってきますね…さらに、「切れ目」という観点から見ると、切れ目はなさそうです。do文の各行が式の一つ一つの項のようなものに対応しているように見えますが、式としては全部つながってます...あー、なるほどねーと思ってしばらくしてから気がついたのは、一つのdo文の中で書かれているコードはHaskellにとっては一つの「式」として解釈されているんだなーということです。つまり、
main = do
  args <- getArgs
 logXY xy

というのは、
sum = 1 + 2 + 3 + 4 + 5

といったような式とまったく同じものとして扱われている…逆に言えば、どうやら、Haskellではプログラムの表現方式として式以外の形がない。
そうやって考えてみると、Cなどの手続き系の言語のプログラムの表現形式とは、一見小さな、でも内容的には大きな隔たりがある気がしてきます。手続き系の言語では式はどちらかといえばセンテンスの一種で、式とセンテンスでは表現方式が少しずつ違ったりしますね...それをHaskellでは式がプログラムの大統一理論の柱だといわんばかりにセンテンスを切り落としちゃった。まぁ、それをやったのはHaskellが最初ではないんでしょうけど...モナドが定着するまでは、手続きの羅列をうまく表現する方法がなかったのがつらかったのでしょうが、モナドが導入されたことで、遅延評価、関数型(式でしかプログラムを表現できない)のいずれのデザインプリンシプルも曲げることなく手続きの羅列をかなり自然に表現できるようになったということのようです。最初の例のようにちょっと引っかかってしまう人もいるわけですが…

Haskellを批評する人たちの中では「モナドはハックだ」という人がいるようですが、いままでHaskellを勉強してきた感じでは、僕にはハックのようには見えてきません。むしろ、これだけのものが見えた今、式とセンテンスを便宜的に分けてしまっている手続き型言語のほうがエレガントさにかけるような気さえしてきます...モナド=ハック論のかたの意見を伺ってみたいと思います…

ではでは。