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

だからダメだよ。