前回の記事究極のモナド「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の汎用性についてもう少し考えてみる。