やさしいHaskell、Haskell 98 レポート

「番外」としたのは、本ではないため。
http://www.smpou.org/haskell/
にある文書。

これが、もうとってもやさしくない。
それは、文書自体の責任というよりは、Haskell自体がやさしくないんだと思
う。でも、もう少し易しいコード例挙げてくれればいいのに。

「Pragmatic Programar」 では、年に1つ言語を覚えなさい、と言ってたし、
www.pragmaticprogrammer.comによると2002年のみんなで学ぼう言語は
haskellだったらしいし、たしかruby-talkでDaveがRubyの次に学ぶべきは、
Haskellだ、と言ってた記憶があるんだが、(とっつきは)RubyやらPythonやら
Schemeより圧倒的に難しいので、Rubyに戻る人が続出したんじゃなかろうか
(それを狙っていたのか? Dave)。

結局プログラミング言語は手を動かさないとわからないものなのか。
ということで cat.hs。この辺は青木さんのgrep.hsをおおいに参考にしてお
ります。

 import System(getArgs)
 import IO

 main :: IO()
 main = do files<-getArgs
           mapM_ catFile files

 catFile :: String -> IO()
 catFile name = do fh <- (openFile name ReadMode)
                   line <- hGetContents fh
                   mapM_ (\l -> putStr (l++"\n")) (lines line)


一応、行単位処理としてみた。その方が応用ききそうだから。標準入力のこと
は無視。インデントは意味がある。
これを作りながら、やっと、IO Monadが理解できた。

関数型言語であるHaskellは、副作用を許さない。関数をズーっと接続するこ
とで、プログラミングしていく、、、schemeからの想像でそう思った。それは
合ってるけど、認識は甘かった。putStr みたいなString->IO() は順序実行が
できない。

 putStr "a"; putStr "b"

ダメ。二つの式の関係がわからない。syntaxエラー。副作用を許さないとい
うことは、そういうことである。プログラムの途中で関係ない操作を埋め込
むのは反則。

それを解決するのがModad。
MonadはおなじMonadの関数を「繋げる」はたらきがある。schemeのbeginと同
じかな、、と思った。しかしそれも甘かった。

do構文がMonadの簡略表記なのだが、mainは、返り値の型が IO() であるから、
IO モナドならば、mainの中のdo構文で繋げることができる。
IOモナドは、IOモナド同志を接続をするものだから、

 do x <- getLine
    print x

はOKだが、

 do x <- 1+1
    print x

は、だめ。1+1は、IOモナドではなく、Numの型を返すので、エラーになる。

 do x <- return (1+1)
    print x

のようにすべき。(return はMonad化する関数、多分 IO Intが返る)。

また、 <- は、letの局所バインドと同じかと一瞬思ったが、同じではない。

 do s <- getLine
    print s

は書けるが、

 print (getLine)

なんてことはできない。getLineの返り値は、IO Stringであって、Stringでは
ない。<- によって、IOの皮が取れて中身のStringが次にわたされる。

この辺までわかった所で、ようやく、Haskellが読める気がしてきた。


mainのような 返り値が IO() 関数にデバッグprintを入れるのはMonad機構を
使えば可能だ。でも、Int -> Int みたいな関数の中で途中の値をprintするよ
うなことは、まさに副作用であるから、できない。たとえ、デバッグ用であっ
ても。(裏技はあるらしい)。
IO値が外に出ちゃう所には、もちろん書けないし、例えば、letは、

 add :: Int -> Int -> Int
 add x y = let tmp1=x*2
               p=print tmp1
            in tmp1+y

print文を入れてもエラーにはならないが、これでは、IOアクションがpにバ
インドされるだけで、アクションは評価されない(遅延評価だし)。うーむ。

ちなみに、(上のprintの行を取って、letもまとめちゃって)

 add :: Int -> Int -> Int
 add x y = x*2+y

と書くと、 add x y で、引数を2つとって、x*2+y の値を返す関数のように
見えるが、本当は(add(x))(y)を返す(カリー化)、

 add 1

は、引数を1つとって、2を加える関数を返す。

 main = let f=add 1
         in print (f 2)

は4を印字する。なお、カリー化が標準なので、printの括弧を外して、

 main = let f=add 1
         in print f 2

とすると、(print (f))(2)と解釈されprintの型(Show a => a -> IO())と一致
しないので、エラーとなる。うーむ。まあ統一的ではある。

さらにちなみに、Haskellは遅延評価するので、上のaddは、値(計算結果)を
返すわけではない。評価が必要になって初めて値にされる。

 main = let f=add undefined
            x=f 2
            y=[0,x]
         in print (head y)

はエラーにならず、0 だけ表示して終わる。([]はList)。

うーむ。楽しくなってきたが、ちょっと疲れた。

ということで、Haskell自身をさらに極めるのは置いておくことにする。次は
リストのMonadとか、Functorとか、型の使い方、作り方とかを勉強すべきな
んだろうけど。

やっぱりどうしても、Monad機構(というかHaskellすべての)の元になってい
る圏論について若干なりとも知識を得たくなってしまったので私の読書スタッ
クに、岩波現代数学の基礎「代数幾何1」がpushされた(といいつつ既に数セ
ミの特集(導来圏)とか噛り読みしているのだが)。 

$Date: 2003/04/14 14:26:34 $ (GMT)