はじめに
最近Haskellをちょっと勉強してみたので、モナドについて解説してみた記事です。
モナドって何?
Haskellはいわゆる「純粋関数型言語」で、IOや状態を変更するなど「副作用」のある処理を許さないという特徴があります。
そのおかげで参照透過性を高められることができるのですが、一般的な手続き型言語で使われるような処理が使わず不便なので、「モナド」と言われる処理を使うことでこの問題を解決しています。
モナドを使った例
まず簡単なモナドの例を、Maybe
と呼ばれるモナドを使って説明してみます。
このモナドは、RustでいうOption
のようなもので、返り値をJust
とNone
の型に包むことでnullableな値のハンドリングをしやすくしています。
例えばこのような感じです。
safeDivide :: Float -> Float -> Maybe Float
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)
safeRoot :: Float -> Maybe Float
safeRoot x
| x < 0 = Nothing
| otherwise = Just (sqrt x)
safeOperation :: Float -> Float -> Maybe Float
safeOperation x y = safeDivide x y >>= \quotient -> safeRoot quotient
これを使うと、たとえば
ghci> safeOperation 4 2
Just 1.4142135
ghci> safeOperation 4 0
Nothing
ghci> safeOperation (-4) 2
Nothing
のように表現できます。
ここで重要なのは>>=
演算子で、これはsafeDivide x y
の計算結果を\quotient -> safeRoot quotient
にbindしています。つまり、
Maybe a
型の値と a -> Maybe b
型の関数がある場合、>>=
演算子を使用してこの関数を Maybe a
型の値に適用し、Maybe b
型の新しい値を得る。
という動作をしていることになります。
>>=
を使うとbindを数珠繋ぎにできるのですが、数珠繋ぎが増えた場合可読性が落ちてしまいます。そのため、Haskellではdo
構文が用意されています。これを使うと、モナドの処理を手続型のように書くことができます。
これを使って、safeOperationを書き直してみましょう。
safeOperation :: Float -> Float -> Maybe Float
safeOperation x y = do
quotient <- safeDivide x y
safeRoot quotient
ここで、bind演算子は<-
を示しています。手続型のように、わかりやすくなったことが見て取れると思います。
モナドを作ってみる
では、Maybe
モナドを例に挙げて、モナドを自分で作ってみましょう。Haskellには型クラス
と呼ばれるinterfaceの様なものが存在しているので、これを使ってモナドを作ることができます。
モナドの型クラスは以下の通りです。
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
(>>) :: m a -> m b -> m b
m >> n = m >>= \_ -> n
fail :: String -> m a
fail = error
ここで重要なのは(>>=)
とreturn
で、(>>=)
がbind演算子を表しています。((>>)
とfail
の説明は重要ではないので省略します。)
return
は値をモナドに包むための処理です。これを使うと、以下の様なことができます。
-- 値を Maybe コンテキストに入れる
example :: Maybe Int
example = return 5
これを実際に実装してみましょう。
instance Monad Maybe where
Nothing >>= f = Nothing
Just x >>= f = f x
return x = Just x
fail _ = Nothing
これをみると分かる通り、Nothing
をbindするとNothing
を返し、Just x
をbindするとモナドの中身を関数の引数に渡すことができます。
また、returnを使うと値をモナドの型に包んでいることがわかりますね。
おわりに
駆け足でモナドの解説をしてみたのですがいかがでしょうか?
かなり端折っているので説明不足なところも多いのですが、なんとなくの感覚は掴めるのではないかと思います。
またモナドにはIOやReader, Writer, State等色々な種類があり、それぞれで違う役割を持っているのでそれぞれ調べてみると面白いと思います。(なんでもできるFreeモナドというものもあるそうなのですが、自分はよくわかっていません)