背景
新くりかえしモナドドリル を解いてみました。これは 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
に対して実装されている点が面白かったです。