背景
新くりかえしモナドドリル を解いてみました。これは Haskell のモナドと do 記法に関する小さな問題集です。タイトル通り、夏休みの宿題みたいな見た目をしています。

do 記法を理解する
本書では、主に do 記法の desugaring の知識が問われているように思いました。以下の背景知識があれば、バッチリ解けると思います。
let の展開
do 記法の中では let 式を文のように書くことができます:
let x = do
let y = 10
let z = 20
y + z
これは let .. in .. 式に展開されます:
let x =
let y = 10
in let z = 20
in y + z
なお例の通り、式全体がモナドではない場合にも
do記法を使うことができます。
操作の連結 (>>)
do 記法の中でモナド値 (monadic action) を連結した場合、 (>>) 演算子によって接続されます:
let x = do
Just 2
Just 3
Just 4
let x = ((Just 2 >> Just 3) >> Just 4)
(>>) は (>>=) を使って定義されており、左辺の出力を捨てて右辺を評価します:
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \_ -> k
右辺の評価について
モナドの値に応じて、 (>>=) 内で右辺の値が捨てられる場合もあります:
-- | @since base-4.4.0.0
instance Monad (Either e) where
Left l >>= _ = Left l
Right r >>= k = k r
したがって Left や Nothing が見つかった時点で、 do 記法内の後の式は評価されないことが確定します。
両辺の型について
(>>) や (>>=) の両辺の間は、あくまで文脈 (モナド) の型が合っていれば良く、異なる型の値を引数に取ることができます。たとえば次の式はコンパイル可能です:
Right "42" >> Right (42 :: Int)
ただ Either に関しては、 Either e に対して Monad が実装されており、失敗にあたる部分まで型が一致しなければ >>, >>= を適用できません。だから Either a b は Left a の方が失敗を表すのですね。
Left "42" >> Left (42 :: Int)
Bind (<-) の展開
最後はお馴染みの bind (<-) を使った do 記法です:
let x = do
y <- Just (1 :: Int)
z <- Just (2 :: Int)
pure $ y + z
これはもちろん、ネストした (>>=) に展開されます:
let x =
Just (1 :: Int)
>>= ( \y ->
Just (2 :: Int)
>>= ( \z ->
pure $ y + z
)
)
(>>)は単純な展開でしたが、(>>=)はネストした式になる点は要注意です。
(>>=) の中では、短絡評価を実施したり、引数を状態とみなしたり、 concatMap するなど、モナド毎に様々なトリックが実装されます。
MonadFail
Bind の左辺でパタンマッチに失敗した場合は fail 関数 の値に fallback します:
let x = do
Just y <- Nothing
pure 42
-- x == Nothing になる
これも do 記法においては desugaring されます:
let x =
let f (Just y) = pure y
f _ = fail "<compiler-generated message>"
in f Nothing
Maybeにおいてはfail _ = Nothingです:
instance MonadFail Maybe where fail _ = Nothing
まとめ
モナドドリルを通し、 Haskell の do 記法がどのように解釈されるか再確認できました。操作の連結 (a; b; c) と bind (<-) の展開において、異なる desugaring が実施されることが明確に認識できました。また Monad が Either e に対して実装されている点が面白かったです。