Haskell が理解できる気がしてこない。諦めたい気分になってくる (実際書いてるコードは理解しなくても書ける範囲でしか書いてない)。いやもちろん誰かに「やれ」なんていわれているわけじゃない。理解できなくてくやしい。lift が全くわからない。「モナドの全て」の説明を読んでもわかった気にさえなれない。頭が悪い。本当に悪い。頭悪いのを実感できる分野は苦手だ。数学とかまさに。Haskell は数学に近い。ただプログラミングということになっているから耐えられる。いややめるかも。頭のいいわるいなんて生まれつきだから、そんなのいまさらどうしよもない。理解できる人には理解できるんだろうなぁ。
IO モナドが副作用を閉じこめているのは (そういう説明がされているから) わかった気になる。それを扱うとき、そのモナドの中で (この表現が正しいかよくわからない。つまり do ブロックの中といいたい) 処理をして、モナドをはずす (この表現が正しいかよくわからない。つまり IO String を <- をつかってなかの String をとりだすといいたい) ことで、副作用を一旦どっかに置いとくことができるようなことはわかった (ほんとに?)。
この do ブロックの中でなにがおこっているのかわかってない。>> で各行が連結されている? >> はなにをする? 連結。連結ってなに。>>= の = がないバージョン。return がなんなのかわかってない。こいつはなにをするんだ。
return は return :: a -> m a すなわち a をとってモナドにくるまれた a をかえす。モナドのインスタンスをつくる。でもこれ引数に値しかわたしてないけど、どのモナドにくるまれるんだ? コンテキストモナド? コンテキストモナドとかてきとうにいったけど、よくわかってない。それが存在するかすらわかってない。
(>>=) :: m a -> (a -> m b) -> m b バインドとかいうらしい。モナドインスタンス m a と関数を一個とって新しいモナドをかえす。わけわかんない。モナドの中の値を処理してまた新しくモナドインスタンスをつくってる。なんでこんなことする必要があるんだろう。計算を合成するってどういう意味だ。Maybe があるなし一緒にして計算を継続できるようになってるのはいいことなのか。合成ってなんなんだ。関数合成と計算の合成って何がちがうんだ。
そういえば関数合成 . と $ のつかいわけもよくわからない。全部 $ でかけるような。合成のほうが気分的にすっきりするだけ?
aaa >> bbb aaa >>= (\x -> bbb)
みたいなときに x には何が入ってるんだ。aaa の返り値か。まてまてモナドはどこいった。もうすこし実用に近いところでみてみよう
import Network.CGI cgiMain :: CGI CGIResult cgiMain = setHeader "Content-Type" "text/plain" >>= \x -> output "Hello" main :: IO () main = runCGI $ handleErrors cgiMain
もちろん >>= は >> にして \x -> は消しても動く。それはいい。\x には何が入っているんだ。setHeader の返り値のはずだ。
setHeader :: MonadCGI m => String -> String -> m ()
m () ってなんだ。() がなんだかわからない。null ? 空リスト? いやリストは [] だ。こいつはなんだ。Haskell だと print デバッグができない。\x になにが入っているか直接調べることができない。学習のさまたげだ。print デバッグは偉大だ。
この () とかいうやつは :type しても () そのままうちこんでも () のままだ。どこに定義が書いてあるんだ。探しにくい。これだから記号がいやだ。
まぁいいやもっと簡単にしていこう。こんどはちゃんと前の計算の値をつかうモナドだ。よくあるやつになった
main :: IO () main = readFile "Rakefile" >>= (\x -> putStr x)
readFile は IO String をかえす。>>= で x に IO String のなかの String がバインドされる。そして計算が実行される。
実際は putStr 自体が (\x -> fun x) の形になっているからこう書く必要はなく
main :: IO () main = readFile "Rakefile" >>= putStr
でいい。putStr をよばずに、関数として >>= 関数にわたしてやっているだけだ。ん? なんか全く普通だ。関数の定義通りの実行されているにすぎない。モナドが見えない。putStr の返り値は IO () だから main の定義ともあっている。これはまったくもってわからないところがない。あれ?
main :: IO () main = readFile "Rakefile" >> putStr ""
たしかになにもでない。あたりまえだ。
もう一回 CGI の例を定義とてらしあわせてみるけど、こっちはやっぱりわからない?
class Monad m => MonadCGI m
data CGIT m a
type CGI a = CGIT IO a
runCGI :: MonadIO m => CGIT m CGIResult -> m ()
setHeader :: MonadCGI m => String -> String -> m ()
output :: MonadCGI m => String -> m CGIResult
--
import Network.CGI
cgiMain :: CGI CGIResult
cgiMain =
setHeader "Content-Type" "text/plain" >>
output "Hello"
main :: IO ()
main = runCGI cgiMain
最終的には IO () を runCGI が返すはずだ。だけど runCGI は m () をかえしている。ここでの m は MonadIO だ IO は MonadIO のインスタンス? だとするとここはあってる。runCGI は CGIT m CGIResult を引数にとる。cgiMain は CGI CGIResult をかえしてる。あれ m は? とおもったら CGIT m か。CGI CGIResult は type を展開すると CGIT IO CGIResult になる。うむ CGIT m CGIResult とマッチする。
setHeader は String -> String をとって MonadCGI をかえしてる。すなわちここでは setHeader が MonadCGI の最初のインスタンスをつくっている (たぶんこのインスタンスが setHeader した結果を保持しているんだろう)。そして、output は m CGIResult をかえしている。m は MonadCGI のインスタンスだ。あれ m CGIResult と CGI CGIResult があってなくないか。data 構築子 CGIT は m a と定義されている。m は MonadCGI。ということは m CGIResult になるな。なるほど。
import Network.CGI -- 1: cgiMain :: CGI CGIResult -- 2: cgiMain :: CGIT IO CGIResult -- 3: cgiMain :: MonadCGI m => m CGIResult cgiMain :: MonadCGI m => m CGIResult cgiMain = setHeader "Content-Type" "text/plain" >> output "Hello" main :: IO () main = runCGI cgiMain
これでもうごく。ちゃんと整合性とれてる。data CGIT m a のよみかたがわかった。data CGIT は m a と一緒ってことだな。たぶん。
とりあえず型のうごきをみていくと >> のところでモナドインスタンスがうけわたされて処理されているっぽいのがなんとなくわかった。前回の計算結果は捨てているけど、背後でうごめくモナドインスタンスは引き継がれている。ベルトコンベアの比喩も若干わかった。文章にするのはわからないところがわかってそれを理解しようとする試みができて偉大だな。
しかしモナドインスタンスがどうやって計算を保持しているかわからないな。モナドによって保持方法が違うのは想像できるけど、実装はどうなっているんだろう。
まぁそれはおいといて、return もすこし理解できた。上ではでてきていないけど、>>= の右側の関数は m a をかえさないといけないわけだ。でも内部処理した結果がただの String だったりしたときは、モナドでつつみなおさないといけない。そのときん return をつかうとそれをつつんで、次にわたせるってことかな? よしやってみよう。
import Network.CGI
cgiMain :: CGI CGIResult
cgiMain =
cgiMain =
setHeader "Content-Type" "text/plain" >>
return "hogehoge" >>=
output
main :: IO ()
main = runCGI cgiMain
こうすれば return でつつまれて次の関数に連結され、output に hogehoge がわたされるはずだ。うまくいった。うれしい。
とりあえずつかれた。ここまでやるのに時間かけすぎだ。リストモナドがなんでモナドなのか理解していない。lift は理解できそうもない。Haskell は if も case も let も全く理解していない。まだまだだ……