top page > computer > haskell > web_lecture > for_programmer > monad_class.html
更新日:
文責: 重城良国

型クラス: Monad

(工事中 60%)

はじめに

「モナドである」という性質を表現する型クラスMonadがある。

型クラス宣言

モナドであればアプリカティブなのでApplicativeのクラス制約がある。クラス関数はモナド関数である

return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b

のふたつだ。クラス宣言は

class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b

のようになる。

(ここらへんで演算子(>=>)を紹介する)

Maybeモナド

monad_class.hs

型Maybeは型クラスMonadのインスタンスだ。よって関数mretやmbindを定義する必要はない。

safeDivM :: Int -> Int -> Maybe Int
_ `safeDivM` 0 = Nothing
x `safeDivM` y = Just $ x `div` y

calcM :: Int -> Int -> Int -> Maybe Int
calcM a b c =
a `safeDivM` b >>= \x ->
x `safeDivM` c

Errorモナド

エラーのときにエラーメッセージを返すようにする。

data Try a = Error String | Success a deriving Show

これを型クラスFunctor, Applicative, Monadのインスタンスにする。型クラスFunctorとApplicativeのクラス関数は効率を考えなければ、型クラスMonadのクラス関数から機械的に定義できる。まずは型クラスFunctorのインスタンス宣言からだ。

instance Functor Try where
fmap = (=<<) . (return .)

(=<<)は(>>=)の引数をいれかえたものだ。使いかたによってはこちらのほうが都合がいいので用意されている。定義は

(=<<) :: (a -> m b) -> m a -> m b
(=<<) = flip (>>=)

となるだろう。fmap = (=<<) . (return .)については明示的な引数を追加して変形してみよう。何をしているかわかるはずだ。次に型クラスApplicativeのインスタンス宣言を見てみよう。

instance Applicative Try where
pure = return
tf <*> tx =
tf >>= \f ->
tx >>= \x ->
return $ f x

これらのインスタンス宣言に使われているモナド関数の定義は型クラスMonadのインスタンス宣言のなかで行われる。

instance Monad Try where
return = Success
Error em >>= _ = Error em
Success x >>= f = f x

これで型Tryは型クラスFunctor, Applicative, Monadのインスタンスになった。安全なわり算の例で試してみよう。

safeDivE :: Int -> Int -> Try Int
x `safeDivE` 0 = Error $ show x ++ " is divided by zero\n"
x `safeDivE` y = Success $ x `div` y

a / b / cを安全に計算してみよう。

calcE :: Int -> Int -> Int -> Try Int
calcE a b c =
a `safeDivE` b >>= \x ->
x `safeDivE` c

試してみる。

*Main> :reload
*Main> calcE 84 5 7
Success 2
*Main> calcE 92 0 8
Error "92 is divided by zero\n"
*Main> calcE 71 4 0
Error "17 is divided by zero\n"

Calcモナド

メモリつきの電卓をエミュレートする。型クラスMonadのクラス関数として定義するためにInt -> (a, Int)型をラップしてCalc aという新しい型を作る必要がある。値構築子Calcとフィールド関数runCalcによってInt -> (a, Int)型の関数をCalc a型へと包みこんだり裸にしたりする以外は前の復習となる。

(フィールド関数という言葉は適切か考えること)

newtype Calc a = Calc { runCalc :: Int -> (a, Int) }

型クラスFunctorとApplicativeのインスタンス宣言は型クラスMonadのクラス関数から機械的に導ける。

instance Functor Calc where
fmap = (=<<) . (return .)

instance Applicative Calc where
pure = return
mf <*> mx =
mf >>= \f ->
mx >>= \x ->
return $ f x

型クラスMonadのインスタンス宣言は

instance Monad Calc where
return = Calc . (,)
m >>= f = Calc $ \s ->
let (x, s') = runCalc m s in runCalc (f x) s'

複雑に見えるかもしれない。まずはCalcとrunCalcとを消して考えよう。これはInt -> (a, Int)という構造を型クラスMonadのインスタンスにするためにひとつの型に包みこんだり裸にしたりしているだけにすぎない(このあたりはもしかすると別の例で独立した説明が必要かもしれない)。

モナドの枠組みではないこの型特有の機能としてメモリへの整数値の加算とメモリの値の呼び出しを実装する。まずはソースファイルの先頭に

{-# LANGUAGE TupleSections #-}

を追加する。メモリへの整数値の加算は

mplus :: Int -> Calc ()
mplus x = Calc $ (() ,) . (+ x)

となる。メモリの値の呼び出しは

mrecall :: Calc Int
mrecall = Calc $ \s -> (s, s)

となる。

計算例

例として

(3 * 4 + 2 * 5) * 7

を計算してみよう。

calcS :: Calc Int
calcS =
return (3 * 4) >>=
mplus >>= \_ ->
return (2 * 5) >>=
mplus >>= \_ ->
mrecall >>= \x ->
return (x * 7)

試してみよう。

*Main> :reload
*Main> runCalc calcS 0
(154,22)

runCalcでCalc Int型からInt -> (Int, Int)型の関数に変換し、メモリの初期値0を与えている。

値を次に渡さない演算子(>>=)の変種が用意されている。

(>>) :: m a -> m b -> m b
m >> n = m >>= const n

これを使うと関数calcSはよりすっきりする。ついでに最後もポイントフリースタイルにしておく。

calcS =
return (3 * 4) >>=
mplus >>
return (2 * 5) >>=
mplus >>
mrecall >>=
return . (* 7)

まとめ

モナドであるということを表現する型クラスMonadを紹介した。この型クラスを使うことでそれぞれの型用に別々に定義していた関数retやbindを関数returnと演算子(>>=)とにまとめることができた。また、モナドであるという性質だけを必要とする抽象度の高い関数であれば、いろいろなモナドに対する演算をひとまとめにして定義することもできる。

「型クラス: Applicative」へもどる 「構文: do記法」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics