前回の記事究極のモナド「Idealモナド」を垣間見るではFreeモナドを構成して興奮して終わってしまったが、今回はイデアルモナドの仕組みについてもう少し考えてみる。

data Ideal f a = Pure a | Ideal (f a)

Idealモナドは、純粋な値Pure aか、fというなにかに包まれているIdeal (f a)のどちらかを取る、という仕組みになっている。returnはPureに、何らかの作用を持つアクションはIdealに行く─純粋かそうでないかを分離することが、イデアルモナドの本質を表しているのかもしれない。

例示は理解の試金石*1という言葉を信じて、実際にいくつかモナドを構成してみよう。

Ideal型とIdealizeクラスについて、以下のような定義がなされているものとする。

import Control.Monad import Control.Applicative data Ideal f a = Pure a | Ideal (f a) class Idealize f where ( >>~ ) :: f a -> (a -> Ideal f b) -> f b instance Idealize f => Monad (Ideal f) where return = Pure Pure a >>= k = k a Ideal fa >>= k = Ideal (fa >>~ k)

Identity

data Empty a instance Idealize Empty where ( >>~ ) = undefined type Identity = Ideal Empty runIdentity (Pure a) = a

Maybe, Either

instance Idealize (Const a) where Const a >>~ _ = Const a type Maybe = Ideal (Const ()) nothing = Ideal (Const ()) maybe _ f (Pure a) = f a maybe v _ (Ideal (Const ())) = v type Either a b = Ideal (Const b) a left = Ideal . Const right = Pure either f g (Pure b) = g b either f g (Ideal (Const a)) = f a

Reader

type Reader r = Ideal (( -> ) r) instance Idealize (( -> ) r) where f >>~ k = \ r -> runReader (k $ f r) r ask = Ideal id runReader (Pure a) _ = a runReader (Ideal f) r = f r

二つの計算に同じ環境を与えることがReaderの本質なので、そういった意味ではわかりやすいかもしれない。

Writer

instance Monoid w => Idealize ((,) w) where (w, a) >>~ k = let (b, w') = runWriter (k a) in (mappend w w', b) type Writer w = Ideal ((,) w) tell w = Ideal (w, ()) runWriter (Pure a) = (a, mempty) runWriter (Ideal (w, a)) = (a, w)

State

newtype StateBase s a = StateBase (s -> (a, s)) instance Idealize (StateBase s) where StateBase f >>~ k = StateBase $ \ s -> let (b, s') = f s in runState (k b) s' type State s = Ideal (StateBase s) runState (Pure a) s = (a, s) runState (Ideal (StateBase f)) s = f s get = Ideal $ StateBase ( \ s -> (s, s)) put s = Ideal $ StateBase ( \ _ -> ((), s)) modify f = Ideal $ StateBase ( \ s -> ((), f s))

なるほど、「(>>~)で純粋な場合の挙動とそうでない場合の挙動を記述すればMonadを作ってくれる*2」ということか。普通にMonadを作ったときと複雑さが変わらないような気もする…(FunctorとかApplicativeを自分で宣言しなくてよいのは楽だが)。

次回は、Idealの汎用性についてもう少し考えてみる。