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

関数型プログラミングにおいて、モナドはプログラムを構造化するための汎用的な抽象概念である。対応したプログラム言語では、ボイラープレート的なコードでもモナドを使って除去することが可能となる。これはモナドが、特定の形をした計算を表すデータ型と、それに関する生成と合成の2つの手続きを提供することによって実現されている。生成は//任意の//基本型の値をモナドに包んでモナド値を生成する手続きであり、合成はモナド値を返す関数たちを合成する手続きである。

広い範囲の問題をモナドを使うことで単純化できる。例えば、Maybeモナドを使えば未定義値への対処が簡単になり、Listモナドを使えばリストに入った値を柔軟に扱うことができる。複雑に組み合わさった関数は、モナドを使えば、補助データの管理や制御構造や副作用を除去した簡単なパイプライン構造に置き換えることができる。

モナドの概念や用語は圏論から来ている。圏論においては、モナドは追加の構造を持つ関手として定義されているプログラミングにおいては複数の自由変数上の関数が使われるため、正確にはここでいうモナドは圏論における強モナドである。}}。1980年代の後半に研究され始め、1990年代前半には、一見無関係な計算機科学上の問題がモナドによって統一的で関数的にモデル化できることが分かってきた。圏論はモナドの満たすべき形式的な条件を与え、これはモナド則と呼ばれている。モナド則はモナド的なコードの形式的検証にも使用可能である。

ある種の計算では、その計算の意味をモナドで明確に表現できることを利用して、プログラム言語の便利機能の実装にモナドを使うことができる。Haskellのように、標準ライブラリですでに汎用のモナド構造といくつかのインスタンスを定義している言語もある。

1. 歴史
-----

プログラミングにおけるモナドの概念は1980年代のプログラム言語Opal に見られるが、このときは「コマンド」と呼ばれ形式化はされなかったdate=June 2013}}。

1991年にEugenio Moggiはプログラムの構造化でモナドを初めて汎用的に使用した。この成果を元に、Philip WadlerやSimon Peyton Jonesといったプログラム言語の研究者により実装が行われた。初期のバージョンのHaskellは問題の多い「遅延リスト」を用いた入出力を使っていたが、Haskell 1.3からは遅延評価により柔軟に合成可能なモナドによる入出力が導入された。

入出力に加えて、プログラム言語研究者とHaskellのライブラリ設計者は構文解析器やインタープリタにモナドを適用することに成功した。Haskellのdo記法に現れる性質から、モナドの概念はapplicative functorとarrowへと一般化された。

長い間、Haskellとその派生だけがプログラミングにおけるモナドの主な利用者であった。最近では、F#がコンピュテーション式または「ワークフロー」と呼ばれる機能を導入した。これは、命令型プログラムしか経験のないプログラマにもなじみやすい構文を使ったモナド的構成を提供しようという試みである。

2. 動機付けと例
---------

プログラム言語Haskellはモナドを多用する関数型の言語であり、簡単にモナドの合成ができるような構文糖衣を持っている。特に指定がない限り、この記事のコード例はHaskellで書かれている。

モナドの紹介として一般的なふたつの例 //Maybe// モナドと //I/O// モナドを取り上げる。もちろん、モナドはHaskellに限られたものではないので、JavaScriptでの //Writer// モナドの例もその次に扱う。

2.1. 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 Int add mx my = case mx of Nothing -> Nothing Just x -> case my of Nothing -> Nothing Just y -> Just  これを楽にするための、各計算を繋げる演算子を定義することができる。二項演算子 //bind//  は失敗する可能性のある計算の結果を、もうひとつの失敗する可能性のある計算へ連鎖する。最初の引数が Nothing だった場合は、二番目の引数は無視されて、単に計算全体が失敗する。もし、最初の引数が Just x だった場合は、この値 x は二番目の関数引数に渡されて、この関数の結果として新しい //Maybe// の値が返される。これは Just な値か失敗かのどちらかになる。  :: Maybe a ->  -> Maybe b Nothing >>= _ = Nothing -- 失敗した計算は Nothing を返す  >>= f = f x -- 値 x に関数 f を適用する 計算の状態に影響を与えずに値を投入する構築子は、Just がすでにそうなっている。 return :: a -> Maybe a return x = Just x -- 値 x を包んで、型  の値として返す これらを使うことで、上の例は次のように書ける。 add :: Maybe Int -> Maybe Int -> Maybe Int add mx my = --  型のふたつの値を足す関数。各入力は
Nothing の場合がある mx >>= )) -- 値を包んで、型の値として和を返す

さらにdo記法という構文糖衣を使うことで、次のように書くことができる。 add :: Maybe Int -> Maybe Int -> Maybe Int add mx my = do x <- mx y <- my return 

この種の操作は頻出なので、Haskellの標準関数が用意されている。これはふたつのモナド的な値と、その中身を組み合わせる関数を受け取る関数であり、上の例は次のように書ける。 add :: Maybe Int -> Maybe Int -> Maybe Int add = liftM2 



2.2. I/Oモナド
-----------

Haskellのような純粋関数型言語では関数が外部から観測可能な作用をもつことは、関数の意味から外れることになるため許されない。しかし、関数が直接副作用を起こすのではなく、求める副作用を//記述する//値を構築して返し、呼び出した側が適当な時に実行することは可能である。Haskellの記法では、型 //IO a// の値がこのようなアクションを表現していて、実行されたときには、型 //a// の何らかの値を生成する。

IO型の値は「現在の世界の状態を引数に取り、関数の返り値として変化した状態を持つ新しい世界を返す」という意味でアクションとみなすことができる。例えば、関数 doesFileExist と removeFile は次の型を持つ関数である。 doesFileExist :: FilePath -> IO Bool removeFile :: FilePath -> IO 

removeFile は関数としては、FilePath を引数にとり、ある IOアクションを返す。このアクションが//実行される//と世界を、この場合はファイルシステムを FilePath という名前のファイルの存在しないものに変更する。この場合は、IO の持っている値は  型の値なので、なにも結果は生成されず、呼び出し側はこれを気にする必要はない。一方で、doesFileExist の場合は、関数の返す IOアクションはブール型の値、True または Falseを包んでいて、概念的には、アクションが実行されたときのファイルシステムに FilePath という名前のファイルが存在したかどうかを呼び出し側が知っているという世界の状態を表現している。アクションからアクションへと世界の状態を渡して管理するこの方法により、決まった順序で状態を変化させていくアクションの列を定めることが可能となる。この過程は時相論理において宣言的な命題のみで時間の経過を表現する手法と似ている。以下の例ではプログラム内のアクションを連鎖する方法の詳細を再び Haskell を使って明らかにする。

基本的な種類の入出力操作、標準出力に文字列を書く、標準入力から文字列を読む、ファイルの読み書き、ネットワーク越しのデータ送信等をすべて記述できるようにしたい。さらに、これらのプリミティブを組み合わせて大きなプログラムを作れる必要もある。例えば次のように書けることが望ましい。 main :: IO  main = do putStrLn "What is your name?" name <- getLine putStrLn  この直観的な記法はどのように形式化できるだろうか？このためには各入出力アクションに対して、いくつかの基本的な操作を実行できる必要がある。

ふたつの入出力アクションをひとつにできなければならない。Haskellでは、中置演算子 >> を用いて、putStrLn "abc" >> putStrLn "def" と書き、コンソールに二行の文字列を表示するひとつのアクションである。演算子 >> の型は //IO   a → IO   b → IO   b// であり、ふたつの入出力アクションを引数に取り、順番に実行するひとつのアクションにまとめて返す。
//何もしない//という入出力アクションも必要である。これは値を返すがなんの副作用も持たない。Haskellではこのアクションは return で構築され、この型は //a → IO   a// である。
さらに、最初のアクションの結果に基づいて次に実行するアクションを決める必要がある場合がある。Haskellでは、演算子 >>= を使い、型は //IO a →  → IO   b// である。左側のオペランドは型 a の値を返す入出力アクションであり、右側のオペランドは左側のアクションの生成した値に応じて次に実行する入出力アクションを選択する関数である。結果は、最初のアクションを実行しその値を関数に渡した評価結果をアクションとして実行し、最後にこのアクションの結果を返す合成されたひとつのアクションとなる。Haskellのこの演算子を使った例は getLine >>= putStrLn であり、これは標準入力から一行文字列を読み込み、それをそのまま標準出力に書き出す。最初の演算子 >> は単にこの演算子の特別な場合である。最初のアクションの結果は無視され、二番目のアクションは常に同じである。これらの3つの演算子をプリミティブな入出力操作として「いかなるプログラムでも」書けるかというのは、データ変換やif式やループを含めたとしても自明であるとは言いがたい。上の例は長い式を使って次のように書ける。 main = putStrLn "What is your name?" >> getLine >>= ame -> putStrLn //bind// 演算子の持つパイプライン構造により、//getLine// と //putStrLn// の命令はそれぞれ一度だけ、書いた順序で評価され、入力ストリームから文字列を取り出して、出力ストリームに出力する副作用は関数型パイプラインの中でも正しく実行される。これは言語が関数をアウト・オブ・オーダー実行や遅延評価する場合でも成立する。

明らかに入出力の定義とMaybeの定義は共通の構造を持っているが、異なっている部分も多い。上述した構造や他のよくある似たような多くの構造の抽象化である。モナドの一般化された概念は、計算の一部が副作用を発生させるにもかかわらず、純粋関数的な計算として実行したくなるようなあらゆる状況を包含している。

3. 形式的定義
--------

モナドは、元となる型システムが与えられたとき、その内部に対応する型システムを埋め込む構成である。このモナド的型システムは元の型システムの重要な点は全て保存するが、モナドに固有の特徴を追加する技術的には、モナドは元となる型を保存する必要はない。例えば、全ての演算子がただひとつの多相な値を返すような自明なモナドもモナドの公理を満たす。逆に、モナドは新しい構造を追加する必要はない。元の型を変更せずそのままとする恒等モナドはモナドの公理を満たし、モナド変換子の再帰の基点として有用である。}}。

モナドは複数の方法で定義できる。そのうちの1つは「モナドとは、ある法則を満たす三つ組みである」 で表される定義である。すなわち圏論のクライスリ圏におけるクライスリトリプルである。

3.1. 三つ組
--------

三つ組は以下の要素からなる。



3.1.1. //型構築子M//
----------------

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

3.1.2. //unit 関数//
------------------

//unit//関数は型 x の引数を型 M x の返り値にうつす関数である。すなわち//unit// 関数は型 x の値をモナド型 M x の値へうつす多相な関数である。引数値変換の有無は定義されない