モナド (プログラミング)

歴史

プログラミングにおけるモナドの概念は1980年代のプログラム言語Opal (プログラミング言語)に見られるが、このときは「コマンド」と呼ばれ形式化はされなかった。
1991年にEugenio Moggiはプログラムの構造化でモナドを初めて汎用的に使用した。この成果を元に、Philip WadlerやSimon Peyton Jonesといったプログラム言語の研究者(二人ともHaskellの言語仕様に取り組んでいた)により実装が行われた。初期のバージョンのHaskellは問題の多い「遅延リスト」を用いた入出力を使っていたが、Haskell 1.3からは遅延評価により柔軟に合成可能なモナドによる入出力が導入された。
入出力に加えて、プログラム言語研究者とHaskellのライブラリ設計者は構文解析器やインタープリタにモナドを適用することに成功した。Haskellのdo記法に現れる性質から、モナドの概念はapplicative functorとarrowへと一般化された。
長い間、Haskellとその派生だけがプログラミングにおけるモナドの主な利用者であった。しかし、Scheme・Perl・Python・Racket・Clojure・Scalaにおいても定式化されており、新しいMLの標準でもオプションとなっている。最近では、F#がコンピュテーション式または「ワークフロー」と呼ばれる機能を導入した。これは、命令型プログラムしか経験のないプログラマにもなじみやすい構文を使ったモナド的構成を提供しようという試みである。

動機付けと例

プログラム言語Haskellはモナドを多用する関数型の言語であり、簡単にモナドの合成ができるような構文糖衣を持っている。特に指定がない限り、この記事のコード例はHaskellで書かれている。
モナドの紹介として一般的なふたつの例 Maybe モナドと I/O モナドを取り上げる。もちろん、モナドはHaskellに限られたものではないので、JavaScriptでの Writer モナドの例もその次に扱う。

Maybeモナド

オプション型 Maybe a を考えよう。これは型 a の値をひとつ持つか、全く持たないかのふたつの状態を表現する型である。これらを区別するために、二種類の代数的データ型構成子を持つ。Just tはひとつの値tを保持し、Nothingは値を何も保持しない。
data Maybe t = Just t | Nothing
この型を使って検査例外の簡単なものを作ってみたいとする。計算の途中で失敗した場合は、残りの計算は飛ばして結果Nothingを返し、すべての計算が成功した場合は何か値xを保持したJust xを返すことにする。
以下の例では、addはMaybe Int型のふたつの引数を受け取って、同じ型の結果を返す。mxとmyがともにJustである場合は、これらの値の和をJustに入れて返したい。もし、mxとmyのどちらかがNoneだった場合は、Nothingを返したい。こういった振る舞いをする関数を単純に実装しようとしたら、「if Nothing then Nothing else Just xのxに何かする」の形の場合わけを複数ネストすることになり、すぐやっかいなものになる。
add :: Maybe Int -> Maybe Int -> Maybe Intadd mx my =
  case mx of
    Nothing -> Nothing
    Just x  -> case my of
                 Nothing -> Nothing
                 Just y  -> Just (x + y)
これを楽にするための、各計算を繋げる演算子を定義することができる。二項演算子 bind (>>=) は失敗する可能性のある計算の結果を、もうひとつの失敗する可能性のある計算へ連鎖する。最初の引数が Nothing だった場合は、二番目の引数(関数引数)は無視されて、単に計算全体が失敗する。もし、最初の引数が Just x だった場合は、この値 x は二番目の関数引数に渡されて、この関数の結果として新しい Maybe の値が返される。これは Just な値か失敗かのどちらかになる。
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe bNothing  >>= _  =  Nothing    -- 失敗した計算は Nothing を返す(Just x) >>= f  =  f x        -- 値 x に関数 f を適用する
計算の状態に影響を与えずに値を投入する構築子は、Just がすでにそうなっている。
return :: a -> Maybe areturn x = Just x       -- 値 x を包んで、型 (Maybe a) の値として返す
これらを使うことで、上の例は次のように書ける。
add :: Maybe Int -> Maybe Int -> Maybe Intadd mx my =             -- (Maybe Int) 型のふたつの値を足す関数。各入力は Nothing の場合がある
  mx >>= (\x ->         -- mx が Nothing でない場合に、値 x を抽出する
    my >>= (\y ->       -- my が Nothing でない場合に、値 y を抽出する
      return (x + y)))  -- 値(x+y)を包んで、(Maybe Int)型の値として和を返す
さらにdo記法という構文糖衣を使うことで、次のように書くことができる。
add :: Maybe Int -> Maybe Int -> Maybe Intadd mx my = do
  x <- mx
  y <- my
  return (x + y)
この種の操作は頻出なので、Haskellの標準関数(liftM2)が用意されている。これはふたつのモナド的な値(ここでは、ふたつのMaybe)と、その中身(ふたつの数値)を組み合わせる関数(加算)を受け取る関数であり、上の例は次のように書ける。
add :: Maybe Int -> Maybe Int -> Maybe Intadd = liftM2 (+)
(上記のdo記法を使ってliftM2の定義を書いてみよう)

形式的定義

モナドは、元となる型システムが与えられたとき、その内部に対応する型システム(モナド的型システムと呼ぶ)を埋め込む構成である(つまり、各モナド的型は元のシステムの型でもある)。このモナド的型システムは元の型システムの重要な点は全て保存するが、モナドに固有の特徴を追加する。
モナドは複数の方法で定義できる。そのうちの1つは「モナドとは、ある法則（モナド則）を満たす三つ組みである」で表される定義である。すなわち圏論のクライスリ圏におけるクライスリトリプルである。

型構築子M

class Monad M where...value :: M x
型構築子Mは、ある型 x から派生した型を得る方法を定める。得られる型はしばしば M x で表記される。

unit 関数

unit関数（ return とも）は型 x の引数を型 M x の返り値にうつす関数である。すなわちunit 関数は型 x の値をモナド型 M x の値へうつす多相な関数である。引数値変換の有無は定義されない（一般には値を変換せずに保持する（例:入力 1 =>出力 Maybe(1)）。

bind 関数

bind関数（ >>= とも）は「モナド的値」と「値をモナド的値へうつす関数」を引数にとり、第1引数のモナド的値を第2引数関数の値域へうつす。

fmap と join

Haskellではモナドを return と bind 関数を用いて定義するが、return にふたつの演算子 join と fmap を加えることでも定義可能である。この定式化は圏論における元のモナドの定義により近くなる。fmap 演算子は型 (t→u) → M t→M u を持ち、ふたつの型間の関数を受け取って、モナドの中の値に「同じこと」をする関数を返す。join 演算子は型 M (M t)→M t を持ち、二層になったモナド的な情報を平らにしてひとつにする。
ふたつの定式化は以下の関係を持つ。
fmap f m = m >>= (return . f)join n = n >>= idm >>= g   ≡   join (fmap g m)
ここで、m は M t、n は M (M r)、f は t → u、g は t → M v の型を持つ。t、r、u、v は元の型である。
モナドでなくても、型と関数からなる圏では fmap 関数は任意の函手に対して定義できる。函手は次の法則を満たす。
fmap id   ≡   idfmap (f . g)   ≡   (fmap f) . (fmap g)
同じ圏で、return は基点付き函手を特徴付ける。これは値を函手の中に「持ち上げる」ことによる。満たすべき法則は次の通りである。
return . f ≡ fmap f . return
さらに、join によってモナドが特徴付けられる。
join . fmap join     ≡ join . joinjoin . fmap return   ≡ join . return = idjoin . fmap (fmap f) ≡ fmap f . join

加法的モナド

加法的モナドは、モノイド則を満たす二項演算子 mplusとその単位元としてモナド的な値 mzero を備えたモナドである。演算子 mplus の型は M t → M t → M t であり(ここで、M はモナドの構築子であり t は元の型である)、結合律と左右からの単位元律を満たす。
(a `mplus` b) `mplus` c   =   a `mplus` (b `mplus` c)m `mplus` mzero           ≡   mzero `mplus` m           ≡    m
このことから、加法的モナドはモノイドでもある。>>=に対しては、mzeroはヌル要素として振舞う。ゼロを掛けるとゼロになるように、mzero と関数を bind すると結果はゼロとなる。
mzero >>= f               ≡   mzero
同様に、モナド的な値 m とつねにゼロを返す関数を bind すると、結果はゼロである。
m >>= (\x -> mzero)       ≡   mzero
直観的には、モナド的値としてのゼロはモナドに関連した構造だけを持ち元の型の値を持たない。Maybe モナドでは、「Nothing」がゼロである。リストモナドでは、空リストがゼロである。

構文糖衣: do記法

bind 演算子 >>= を直接使ってプログラムを書くのがよい場合もあるが、典型的にはdo記法 (OCamlでは perform記法、F#ではコンピュテーション式)を使用し、命令型言語のような見た目にできる。コンパイラはdo記法の式を >>= を使ったものに変換する。例えば
a = do x <- [3..4]
       [1..2]
       return (x, 42)
を変換した結果は以下となる。
a = [3..4] >>= (\x -> [1..2] >>= (\_ -> return (x, 42)))
リストモナドの実装を見てみよう。リストにマップを行い結合を行う(平らにする)この関数は concatMap とも呼ばれる。
instance Monad [] where
  m >>= f  = concat (map f m)
  return x = [x]
  fail s   = []
よって、以下のような変形が行われて、それぞれの式はすべて等価である。
a = [3..4] >>= (\x -> [1..2] >>= (\_ -> return (x, 42)))a = [3..4] >>= (\x -> concatMap (\_ -> return (x, 42)) [1..2] )a = [3..4] >>= (\x -> [(x,42),(x,42)] )a = concatMap (\x -> [(x,42),(x,42)] ) [3..4]a = [(3,42),(3,42),(4,42),(4,42)]
リスト[1..2]は使われないことに注意しよう。左矢印をつけないことで、連鎖した関数は引数を無視するように変換されて、値ではなくモナド的な構造だけが問題になることを示すことができる。例えば、状態モナドで値を生成せずに状態だけを変化させるようなときに使われる。do記法は >>= の単なる構文糖衣であるので、どんなモナドに対しても使うことができる。
Maybeモナドの値を安全に割り算する以下の定義も等価である。
x // y = do
  a <- x  -- xとyの中に値がある場合は取り出す
  b <- y
  if b == 0 then Nothing else Just (a / b)x // y = x >>= (\a -> y >>= (\b -> if b == 0 then Nothing else Just (a / b)))
同様にF#ではコンピュテーション式を使うことができる。
let readNum () =
  let s = Console.ReadLine()
  let succ,v = Int32.TryParse(s)
  if (succ) then Some(v) else Nonelet secure_div = 
  maybe { 
    let! x = readNum()
    let! y = readNum()
    if (y = 0) 
    then None
    else return (x / y)
  }
このmaybeブロックは構文糖衣であり、以下の式に変換される。
maybe.Delay(fun () ->
  maybe.Bind(readNum(), fun x ->
    maybe.Bind(readNum(), fun y ->
      if (y=0) then None else maybe.Return(x / y))))

恒等モナド

もっとも単純なモナドは恒等モナドであり、値になんの情報も付加しない。
Id t = treturn x = xx >>= f = f x
このモナドのdoブロックは変数の代入を実行する。例えば、do {x <- 2; return (3*x)} の結果は6である。
圏論的な見方をすると、恒等モナドは恒等函手の間の随伴から得られる。

自由モナド

自由モナドは自由モノイドと似ていて、直観的には、使う型に依存せずにモナド則(またはモノイド則)を満たすようにできる汎用の構造である。
型 t に対して、t の自由モノイドは [t] であり、結合的な二項演算子として ++、単位元として [] を選ぶ。Haskellでは以下のように書ける。
instance Functor [] where
   fmap _ [] = []
   fmap fun (x:xs) = fun x : fmap fun xsinstance Monoid [t] where
   mappend xs ys = xs ++ ys
   mempty = []
具体的なモノイドとでは、値 t1,t2,...,tn を二項演算で足すことができるが、[] では単に、結合 [t1,t2,...,tn] になるだけであり、単に「一緒に集めた」だけである。ここで、「一緒に集めた」結果がどうなるべきかは定めない。
自由モナドも同じ考えに基づいて定義される。上の定義 List t = Nil | Cons t (List t) に函手を挿入することで、次の定義を得る。
data Free f a = Return a | Bind (f (Free f a))instance Functor f => Functor (Free f) where
   fmap fun (Return x) = Return (fun x)
   fmap fun (Bind x) = Bind (fmap (fmap fun) x)instance Functor f => Monad (Free f) where
   return x = Return x
   x >>= fun = Bind (fmap (>>= fun) x)
List が値のリストを保存するのと違って、Free はドメインの値を決めた函手のリストを保存する。Free の Functor と Monad インスタンスの定義によると、受け取った関数を fmap でリストに落としているだけしかしていないことが分かる。

恒等コモナド

恒等コモナドはもっとも単純なコモナドであり、型 T をそれ自身にうつす。extractは恒等関数であり、extendは関数適用である。