Haskell-モナド
モナド(Monad)について
モナドとは
モナドとは、制御構造を導入するための「データに付ける付加情報」です。
モナドは抽象的な概念であり、応用範囲が広いので理解しにくいですが、デザインパターンの一種と考えることができます。
以下の場合においてモナドが適用されています。
- 基本的には入出力処理を実現する仕組み(汎用的なモナド)
- 成功か失敗かいずれか一方の値を保持するデータ型(Maybeモナド)
- 複数の値を保持するデータ型(リストモナド)
モナドと入出力について
Haskellは純粋関数型言語であり、本来、画面出力やネットワークなどの外部入出力の副作用を認めない言語です。
しかしながら、実用的なプログラムとして機能するためには、なんらかの仕組みを用意して入出力を実現する必要があります。
Haskellは「モナド」という仕組みを利用することで、外部入出力を実現します。
モナドと遅延評価について
Haskellは遅延評価方式を採用している言語です。
そのため、処理の実行順を明示的に指定することができず、意図した順序通りに入出力処理を行うことに不都合が生じます。
適切な入出力を実現するためには、入出力を実行する仕組みに加えて、式の実行順序を保証する仕組みも必要になります。
モナドは遅延評価における実行順序の整合性をとる仕組みを提供します。
汎用的なモナド
型クラスとしてのモナド(モナド型)
モナド型は、内部に値を保持できる汎用的な容れ物となるテンプレートです。
用途に応じて、具体的なモナド型を型クラスのMonadから定義します。
型クラスのMonadでは、モナドとしての基本的な性質を表す関数の仕様が定義されています。
HaskellにおけるMonadクラスの定義は 以下の通りです。
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
連結関数(>=)
連結関数は「>=」という名前の関数です。(bindと呼びます)
第1引数にモナド型を受け取り、第2引数にはモナド型を返す関数を受け取って、両者を連結する役割を果たします。
この連結関数では、第1引数で渡されたモナドから値を取り出して、第2引数の関数に渡す役目を果たします。
これにより、実行順序(左を実行してから右を実行)を決めることが可能になります。
注入関数(return)
return関数はモナドに値を格納する(値をモナドに返す)機能を果たします。
命令型言語のreturn命令とは全く異なる役割となります。
モナド則
モナドの概念は圏論とよばれる数学の分野に由来します。
モナドを利用するには、数学的な形式に従う必要があります。
正しいモナドであるためには、以下の3つの法則をみたさなければなりません。
(return x) >>= f == f x
m >>= return == m
(m >>= f) >>= g == m >>= (\x -> f x >>= g)
一番目の規則は「return」が「>>=」に関して左単位元に なっていることを要請しています。
二番目の規則は「return」が「>>=」に関して右単位元になっていることを要請しています。 三番目の規則は「>>=」に関する一種の結合法則です。
モナドのカタログ
モナドには以下のような種類があります。
Maybeモナド
Maybeモナドは、先行処理の正否を判定して後続処理を連鎖する論理構造を簡潔に記述する仕組みです。
Maybeモナド型を用意し、成功(Just x)か失敗(Nothing)かに関わらず、処理結果をその中に保持できるようにします。
戻り値にMaybeモナドを返すことで、Maybeモナドを利用する側では処理結果として成功/失敗を意識する必要がなくなります。
Maybeモナドの定義は、以下のようになっています。
data Maybe a = Nothing | Just a
instance Monad Maybe where
return = Just
fail = Nothing
Nothing >>= f = Nothing
(Just x) >>= f = f x
IOモナド
IOモナドは、外部との入出力処理を実現するためのモナドです。
実際の入出力処理をIOモナドで隠蔽することで、関数型言語の純粋さと遅延評価方式を保つことができます。
instance Monad IO where
(>>=) = 実装部分...
return = 実装部分...
fail s = ioError (userError s)
- IOモナド型
- 外部から入力した値や外部に出力する値をモナド内部に保持します。
- IOモナドを評価すると、実際の入出力処理が行われます。
- 連結関数(>=)
- 第1引数にモナド型を受け取り、第2引数にはモナド型を返す関数を受け取って、両者を連結する役割を果たします。
- 第1引数を先に評価することを保証しているため、入出力処理の実行順序が保証されます。
- 注入関数(return)
- 外部に出力する値をIOモナドに格納するための関数です。
HelloWorldの表示を以下のように記述することができます。
return "Hello, World" >>= putStr
その他モナド
- Identityモナド
- N/A(モナド変換子)で利用します。
- Listモナド
- ゼロ個以上の可能な結果を返す計算です。
- Error
- 失敗あるいは例外を投げられる計算です。
- State
- 状態を保持する計算です。
- Reader
- 共有された環境から読み込みを行う計算です。
- Writer
- 計算する値とは別にデータを書き出す計算です。