読者です 読者をやめる 読者になる 読者になる

究極のモナド「Idealモナド」を垣間見る(続/その0)

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

*1:結城浩著「数学ガール」より

*2:>>~は私が前回定義した、イデアルモナドを構成するのに最低限必要な演算