LoginSignup
5
5

More than 5 years have passed since last update.

再・モナドについて勉強したことの整理

Last updated at Posted at 2015-03-20

モナド・・

そもそもすごいHaskellたのしく学ぼう!を読む動機となったキーワード。関数プログラミング実践入門を読むだけではまだまだ概念を捉えきれず、理解できなかった。

学ぶうちにモナドは型クラスであり、関連する型クラスとして FunctorApplicative があることを知った。全体として、それぞれの型クラスができることとの差異を整理することでモナドの必要性やその位置付けの整理に役立てたいと考えている。

モナドを使う動機

Haskell では、ある型 a をとって、 a を返す関数 は、その関数を続けざまに使用することができる。

Intを2つとって、Intを返す関数の例
func :: Int -> Int -> Int
func 3 (func 4 (func 5 ・・・ といったように続けざまに使用できる

また、もし結果が失敗するかもしれないような計算をする関数の場合は返り値の型を Maybe a のようにしておきたい。

例えば、あるリストに要素 x が含まれているかどうかを調べ、含まれていればその要素を、含まれていなければ Nothing を返すような関数のこと。単純にBool値 を返す関数よりも利用できる範囲が広がるだろう。

この考えは自然なので、最初のfunc にこの文脈を取り入れることにする。すると、a をとって型 Maybe a を返す関数となり、続けざまに使用することはできなくなってしまう。

func :: Int -> Int -> Maybe Int
func 3 (func 4 (func 5 ・・・ といったように続けざまに使用できない!

有効な値がある以上は続けざまに使えると便利な関数なので、引数と返り値の型はちょっと違うけれどなんとか実現できないか

これに答えてくれるのがモナドということになる。

答えてくれた結果
(Just 3) >>= func 4 >>= func 5 >>= ・・・ bind演算子を使って実現できた!

モナドの一般的な概要

モナドは、計算する上で生まれる特定の文脈を効率的に扱うための仕掛け を実現するためにある。一般的な概要については次のリンクにまとめています。

bind 演算子

先述した要求に応えてくれる演算子として、>>= ( bind 演算子 )がある。

引数と返り値の型はちょっと違うけれどなんとか実現できないか?という要求

Maybe モナドの実装を例に見てみると、

instance  Monad Maybe  where
    (Just x) >>= k      = k x
    Nothing  >>= _      = Nothing

    (Just _) >>  k      = k
    Nothing  >>  _      = Nothing

    return              = Just
    fail _              = Nothing

有効な値を引数にとって、第2引数( 右辺 )の関数を適用している。また、第1引数( 左辺 )が無効値だったら、結果も無効な値としている。
bind 演算子の型は(>>=) :: m a -> (a -> m b) -> m b なので、次のように書ける。

Just 3 >>= \x -> return (x + 5) >>= \x -> return (x * x) -- Just 64

ちなみに、Functor 型クラスや Applicative 型クラスのインスタンスではこのように内部の値同士が相互作用するような操作は難しい。

Functor/Applicative/Monad

文脈を持った計算を行うために、FunctorApplicativeMonad という型クラスがある。より柔軟にこの計算を行える(≒強化版)という概念を< で表すと、Functor < Applicative < Monadということになる。

それぞれの型クラスについての概要は次のページにまとめています。

これらの型クラスを、特徴、できること、できないことの3つで比較したマトリクスが下表となる。

型クラス 特徴 できること できないこと
Functor 文脈付きの値に普通の関数を適用する fmap で普通の関数を文脈付きの関数に lifting する1 fmap された関数を文脈付きの値に簡単に適用する方法がない
Applicative 文脈付きの値に普通の関数(または普通の関数を文脈付きの関数に持ち上げた関数)を適用する アプリカティブ・スタイルを使って文脈を維持しながら普通の関数を適用する2 アプリカティブ・スタイルでは、ある文脈付きの値が他の文脈付きの値に依存するような操作の記述が難しい
Monad 文脈付きの値に、普通の値をとって、文脈付きの値を返す関数を適用する
bind演算子をサポートしたアプリカティブ・ファンクター
bind演算子で文脈付きの値同士が相互作用するような操作を行う ---

普通の関数: 普通の値をとって、普通の値を返す関数(普通でない ≒ リストやMaybe などの文脈付きの値 )

モナド型クラスのインスタンス

主要なインスタンスについては、
* モナドについて勉強したことの整理(個別モナド) に整理しています。

MonadPlusとguard関数

まず、MonadPlus 型クラス定義には次にようにある。

-- The MonadPlus class definition

-- | Monads that also support choice and failure.
class Monad m => MonadPlus m where
   -- | the identity of 'mplus'.  It should also satisfy the equations
   --
   -- > mzero >>= f  =  mzero
   -- > v >> mzero   =  mzero
   --
   mzero :: m a 
   -- | an associative operation
   mplus :: m a -> m a -> m a

モナドは、選別と失敗もサポートしていて、 MonadPlus のインスタンスになればその恩恵を受けられるということだろう。

MonadPlus は、モノイドの性質を併せ持つモナドを表す型クラス。リスト[]MonadPlus のインスタンスになれる。

instance MonadPlus [] where
   mzero = []
   mplus = (++)

instance MonadPlus Maybe where
   mzero = Nothing

   Nothing `mplus` ys  = ys
   xs      `mplus` _ys = xs

また、guard 関数がある。これは Bool 値をとって、空タプルを最小限の文脈に包むか、mzero を返す。

-- | @'guard' b@ is @'return' ()@ if @b@ is 'True',
-- and 'mzero' if @b@ is 'False'.
guard           :: (MonadPlus m) => Bool -> m ()
guard True      =  return ()
guard False     =  mzero

False が渡ってくると mzero を返している。つまり、mzero失敗を意味するモナド値 となる。また、引数には通常、条件式が渡され、その真偽値によって結果が 選別されることになる

guard 関数は、モナド値を扱った計算なのでdo記法で記述することができる。

sevensOnly :: [Int]
sevensOnly = do
    x <- [0..50]
    guard ('7' `elem` show x) -- 要素に文字7が含まれているかどうか
    return x --[7,17,27,37,47]

このとき、最後の return x を忘れてしまうと guard 関数の返り値 [()] が結果となってしまうので注意する。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5