はじめに
どうも Haskell 初心者です!
最近『すごいHaskellたのしく学ぼう』を勉強しました。私にとっては初めての関数型言語だったので理解で苦しむところも多くありました。
なかでも、モナドのdo記法のところで混乱したので、私が何に混乱したのかを書いておきます。
誤解のもと
8章の入出力の章で初めてdo記法が出てきたときは、この様なコードが載っていました。
main = do
putStrLn "Hello, what's your name?"
name <- getLine
putStrLn ("Hey " ++ name ++ ", you rock!")
これを見て私は
- do記法の中では、
<-
を使うことでIO()
とかいう良くわからないものを剥がして代入してくれるんだな - do式の最後の行が返り値になるんだな
と思っていました。
認識違いの判明
ところが13章のモナドの章でその認識では説明できないコードが現れました。
-- landLeft :: Int -> (Int, Int) -> Maybe (Int, Int)
-- landRight :: Int -> (Int, Int) -> Maybe (Int, Int)
routine :: Maybe (Int, Int)
routine = do
start <- return (0, 0)
first <- landLeft 2 start
Nothing
second <- landRight 2 first
landLeft 1 second
{-
ghci> routine
Nothing
-}
なぜか途中にあって何にも束縛していないNothing
がdo式の返り値に影響を与えているのです!
ただ、当時は自分の理解と矛盾していることにも気がついておらずスルーしていました。
14章でも
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got number: " ++ show x])
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
tell ["Gonna multiply these two"]
return (a*b)
{-
ghci> runWriter multWithLog
(15, ["Got number: 3", "Got number: 5", "Gonna multiply these two"])
-}
またしてもtell
という、どの変数にも束縛されてない関数がしっかり結果に影響を与えているのです!
これを見て私も自分の認識がおかしいことに気が付きます。
do記法を脱糖してみる
do記法は糖衣構文なので、脱糖して書いてみます。それぞれ
routine :: Maybe Pole
routine = return (0, 0)
>>= landLeft 2
>> Nothing
>>= landRight 2
>>= landLeft 1
runWriter (logNumber2 3 >>= (\x
-> logNumber2 4 >>= (\y
-> tell ["Gonna multiply these two"] >> return(x * y) )))
と同じコードということになります。こう書くと独立していて何もしていないようにみえたNothing
やtell
も一連のモナド適用の連鎖の一部であることがわかります。
おわりに
do記法は手続き型言語のような見た目をしていますが、各行は独立しておらず、滝のように連なっていることがわかりました。
自分と同じ勘違いをしている初心者の助けになれば幸いです。