モナド
2024/07/17
数学モナドとは
圏論(数学の一分野)におけるモナドは、自己関手 、単位 、乗法 μという3つの要素と、それらが満たすべき結合律・単位律からなる数学的構造であり、モノイドに似た構造を備えた自己関手である。プログラミングにおけるモナドは、圏論におけるモナドを応用し、値を包み込んで付加的な情報を意識せず簡単に変換できるようにするための統一的なインターフェース、あるいはデザインパターンである。圏論におけるモナドにはやや難解な点もあるが、プログラミングにおけるモナドは副作用やエラーといった文脈付きの計算を安全に扱うするための道具(デザインパターン)という理解で問題ない。
圏論におけるモナド
圏論の基礎:対象、射、関手、自然変換
圏論におけるモナドを説明するため、まず圏論の基礎について簡単に説明する。圏とは、対象と射(矢印)の集合であり、射は対象間の関係を表す。ある圏から別の圏への構造を保つ写像を関手と呼び、関手は対象を対象に、射を射に対応させ、恒等射と射の合成を保存する。特に、ある圏からそれ自身への関手を自己関手と呼ぶ。また、関手間を自然に(自然性を満たすように)結ぶ変換を自然変換と呼ぶ。自然変換は2つの関手間に対応する全ての対象に対して射を対応させ、その対応が「整合性条件(可換図式)」を満たすものである。
プログラミングに関連付けてイメージするなら、対象を「型」、射を「関数」と考えると分かりやすい。このとき、関手は型と関数を別の文脈に持ち込む仕組みとして現れる。例えば、リスト内の各要素に関数を適用して新しいリストを返すfmapなどが典型的な自己関手である。また、自然変換は2つの関手をつなぐプログラム上の汎用的な変換として現れる。
圏論におけるモナドの定義
モナドは圏上の自己関手と2つの自然変換との組として定義される。自然変換をモナドの単位元、自然変換をモナドの乗法を呼ぶ。についての簡単な説明を次に示す。

なお、は次に示すモナド則を満たす必要がある。
結合律は複数の計算ステップをどの順序で結合しても結果が一つに定まることが保証し、単位律はがに関して単位元のように振る舞うことを示す。
モナド則は可換図式で表現すると次のようになる。

※補足:と、との違い

(1)はを関手で移したもの
(2)はを一塊とした自然変換の成分、すなわち、のをで置き換えたもの
(3)はを関手で移したもの
(4)はを一塊とした自然変換の成分、すなわち、のをで置き換えたもの
モナドは「自己関手の圏におけるモノイド対象」?
そもそもモノイドとは、集合とその上の結合的な二項演算 、その演算に関する単位元の組のことである。
ここで、ある圏 C 上の自己関手を対象とする圏を考える。この圏の対象は自己関手で、射は自己関手間の自然変換である。この圏では関手の合成が積のように振る舞い、恒等関手が単位元のように振る舞う。この圏の中でモノイドのような構造を持つも対象を考えると、それがモナドになる。自己関手がモノイドの集合に、乗法が二項演算 に、単位が単位元に対応する。このことは、モナド則が自己関手の圏におけるモノイドとしての構造に由来していて、代数学における基本的な構造であるモノイドの法則を関手と自然変換の世界に翻訳したものであることを示唆している。
プログラミングにおけるモナド
プログラミングにおけるモナドの定義
モナドは通常、特定のインターフェースや型クラスとして定義され、共通の操作が提供される。具体的には、モナドの型クラスは次のように定義され、 >>=
と return
の2つの操作をもつ。
class (Applicative m) => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
>>=
と return
は、次のモナド則を満たす。
(return x >>= f) == f x -- 左単位則
(m >>= return) == m -- 右単位則
((m >>= f) >>= g) == (m >>= (\x -> f x >>= g)) -- 結合則
>>=
はバインド(bind)演算と呼ばれる左結合演算子で関数をモナドの文脈に持ち上げる。return
は素の値をモナドに包まれた値にする。演算 >>=
と return
の関係を図にすると次のようになる。

様々なモナド
① Maybeモナド
Maybeモナドは失敗する可能性のある計算をうまく扱えるようにしたモナドで、成功時にはJust x
を、失敗時にはNothing
を返す。これにより、途中で失敗した場合はそれ以降の処理を自動的にスキップできるため、複数の計算を組み合わせた処理もシンプルに記述できる。Maybeモナドのインスタンスは次のように定義される。
instance Monad Maybe where
(Just x) >>= f = f x
Nothing >>= _ = Nothing
return x = Just x
Maybeモナドの使用例を以下に示す。
-- x が 2 で割り切れない場合には失敗する
div2 :: Int -> Maybe Int
div2 x =
if even x
then Just (x `div` 2)
else Nothing
-- x が 8 で割り切れない場合には失敗する
div8 :: Int -> Maybe Int
div8 x = return x >>= div2 >>= div2 >>= div2
main :: IO ()
main = do
print $ div8 32 -- 出力: Just 4
print $ div8 50 -- 出力: Nothing
② Eitherモナド
Eitherモナドはエラー情報を伴う計算をシンプルに扱うためのモナドで、成功時には Right x
を、失敗時には Left e
(e
はエラー情報)を返す。エラー情報を保持しつつ計算を連結できるため、複雑になりがちなエラー処理もシンプルに記述できる。Eitherモナドのインスタンスは次のように定義される。
instance Monad (Either e) where
(Right x) >>= f = f x
(Left e) >>= _ = Left e
return x = Right x
Eitherモナドの使用例を以下に示す。
-- x が 2 で割り切れない場合にはエラーメッセージを返す
div2 :: Int -> Either String Int
div2 x =
if even x
then Right (x `div` 2)
else Left ("Cannot divide " ++ show x ++ " by 2")
-- x が 8 で割り切れない場合には失敗する
div8 :: Int -> Either String Int
div8 x = return x >>= div2 >>= div2 >>= div2
main :: IO ()
main = do
print $ div8 32 -- 出力: Right 4
print $ div8 50 -- 出力: Left "Cannot divide 25 by 2"
③ Listモナド
Listモナドは、非決定的な計算(複数の値を持つ計算)を扱うモナドで、リストの各要素に対して総当たり的に計算を行う。Listモナドのインスタンスは次のように定義される。
instance Monad [] where
xs >>= f = concatMap f xs
return x = [x]
Listモナドの使用例を以下に示す。
-- リストモナドを使って各要素を2倍にする関数
doubleList :: [Int] -> [Int]
doubleList xs = do
x <- xs
return (x * 2)
main :: IO ()
main = print $ doubleList [1,2,3,4] -- 出力: [2,4,6,8]
関数型プログラミングでモナドを使う理由
モナドは、値に付随する“文脈”を、意識せずに安全かつシンプルにつなげるためのデザインパターンである。モナドを利用することで次のような恩恵がある。
>>=
(bind)で組み合わせることを可能にする 。Int → Int
など)と、外部アクセスを伴う処理(IO Int
など)を型レベルで区別でき、安全に組み合わせられる。