Haskell でオブジェクト指向 (2)

id:aufheben:20080413 で書いた「Haskellオブジェクト指向」ですが、自分の頭ではもう限界なので、顛末だけ書いていったん終了としたいと思います。(^^;)

まず、Person 型と Entity 型、基本的に前回から変わっていませんが、いろいろと試行錯誤がしやすいように、共通部品としてモジュール化しました。
まずは Person モジュールから。

module Person
(
  Person(Person),
  setName,
  setAge
)
  where

data Person =
  Person {
    name :: String,
    age  :: Int
  }
  deriving Show

setName :: String -> Person -> Person
setName newName x = x { name = newName }

setAge :: Int -> Person -> Person
setAge newAge x = x { age = newAge }

続いて、Entity モジュール。

module Entity
(
  Entity,
  makeEntity,
  update,
  replace,
  IdGen(IdGen)
)
  where

data Entity a =
  Entity {
    id      :: Integer,
    version :: Integer,
    state   :: a
  }
  deriving Show

instance Eq (Entity a)
  where
    x == y = Entity.id x == Entity.id y

initialVersion :: Integer
initialVersion = 1

makeEntity :: a -> IdGen -> (Entity a, IdGen)
makeEntity x g =
  let
    (id, g') = nextId g
  in
    (Entity id initialVersion x, g')

update :: (a -> a) -> Entity a -> Entity a
update f (Entity id version x) =
  Entity id (version + 1) (f x)

replace :: a -> Entity a -> Entity a
replace newX = update (const newX)

data IdGen =
  IdGen {
    lastId :: Integer
  }
  deriving Show

nextId :: IdGen -> (Integer, IdGen)
nextId g =
  let
    id = (lastId g) + 1
  in
    (id, IdGen id)

前回 id を eid に変更しましたが、モジュール名を付加すれば良いらしいので、初めの id に戻しました。
また、fmap は、

fmap id  ==  id
fmap (f . g)  ==  fmap f . fmap g

という規則を満たさないといけないらしいので、Functor をインスタンス化するのはやめ、

fmap -> update
updateEntity -> replace

と関数名を変更しています。

メインでは、Entity と Person をインポートし、上記の修正点を反映しました。
また、若干出力を見やすくしてみました。

import Entity
import Person

main =
  let
    g0       = IdGen 1000
    (x1, g1) = makeEntity (Person "Foo" 12) g0
    x2       = update (setName "Bar") x1
    x3       = update (setAge 23) x2
    (y1, g2) = makeEntity (Person "Hoge" 34) g1
    y2       = replace (Person "Fuga" 45) y1
  in do
    trace "g0" g0
    trace "g1" g1
    trace "x1" x1
    trace "x2" x2
    trace "x3" x3
    trace "x1 == x2" $ x1 == x2
    trace "x2 == x3" $ x2 == x3
    trace "g2" g2
    trace "y1" y1
    trace "y2" y2
    trace "x1 == y1" $ x1 == y1
    trace "y1 == y2" $ y1 == y2

trace :: Show a => String -> a -> IO()
trace s x = putStrLn $ s ++ " => " ++ show x

実行結果は以下のとおり。

prompt>runghc entity-example0.hs
g0 => IdGen {lastId = 1000}
g1 => IdGen {lastId = 1001}
x1 => Entity {id = 1001, version = 1, state = Person {name = "Foo", age = 12}}
x2 => Entity {id = 1001, version = 2, state = Person {name = "Bar", age = 12}}
x3 => Entity {id = 1001, version = 3, state = Person {name = "Bar", age = 23}}
x1 == x2 => True
x2 == x3 => True
g2 => IdGen {lastId = 1002}
y1 => Entity {id = 1002, version = 1, state = Person {name = "Hoge", age = 34}}
y2 => Entity {id = 1002, version = 2, state = Person {name = "Fuga", age = 45}}
x1 == y1 => False
y1 == y2 => True