Haskell でオブジェクト指向 (3)
続いて、モナドに挑戦の巻。
Haskell に標準でついている State を参考にいろいろやってみましたが、とりあえず State をそのまま使うことにしました。
ちなみに、State の定義の概要はこんな感じ。
newtype State s a = State { runState :: s -> (a, s) } instance Monad (State s) where return a = State $ \s -> (a, s) m >>= k = State $ \s -> let (a, s') = runState m s in runState (k a) s'
ある状態 s から、(何らかの値 v, 新しい状態 s') への遷移関数を「状態」と見なすということでしょうか。
では、Entity モナド。
module EntityM ( EntityM, makeEntityM, updateM, replaceM, runState, IdGen(IdGen) ) where import Control.Monad.State import Entity type EntityM a = State IdGen (Entity a) makeEntityM :: a -> EntityM a makeEntityM x = State $ \g -> makeEntity x g updateM :: (a -> a) -> Entity a -> EntityM a updateM f e = State $ \g -> (update f e, g) replaceM :: a -> Entity a -> EntityM a replaceM newX = updateM (const newX)
makeEntity、update、replace のモナド版をそれぞれ定義しました。
これを使うと、以下のようなメインプログラムを書くことができます。
import EntityM import Person main = let g0 = IdGen 1000 mx = makeEntityM (Person "Foo" 12) >>= (updateM $ setName "Bar") >>= (updateM $ setAge 23) (x, g1) = runState mx g0 my = makeEntityM (Person "Hoge" 34) >>= (replaceM $ Person "Fuga" 45) (y, g2) = runState my g1 in do trace "g0" g0 trace "g1" g1 trace "x " x trace "g2" g2 trace "y " y trace :: Show a => String -> a -> IO() trace s x = putStrLn $ s ++ " => " ++ show x
こんな使い方で良いのかあまり自身がありません。(--;)
不満な点としては、
- Entity a 自体、IdGen の状態を変えないと作成できないので、素直に return 関数が使えないこと。
- >>= ではなく updateM の方に Entity の状態変化が含まれていること。
- >>= の前後で IdGen の状態は変わらないのに、State モナドを使う意味があるのか。
などがあります。
もし良きアドバイス等あればお願いします。
P.S.
前回のエントリへの id:everpeaceくんのコメントにある、
x >>= f = fmap f x
は、
(>>=) :: m a -> (a -> m b) -> m b
だからダメだよ。