【Haskell】オブジェクトリスト

2008-10-02

何故か書いてしまったduck typing (Haskell,存在型+型クラス) うおおぉぉ!まさにこれだ! ゲームに出てくるオブジェクトを型をあれこれ構わずリストにぶち込んで更新処理や描画処理をぶん回す、みたいな作り方をよくすると思う。C++とかだったら基底型を作って仮想関数を定義してそれを呼び出しという具合で。でも Haskell だとリストには同一の型しか入れられないのでどうしようかと悩んでたところ。 普通に考えられるのは代数的データ型を使って、すべてをひとつのデータ型にしてしまうやり方:

data GameObject = Kuribo | Nokonoko | …

Monadius がこういうやり方。けどそれぞれの型による関数の分岐を書くのに問題があって、関数ごとに処理を1ヶ所に書かないといけないのでソースを分けづらい:

update Kuribo = あれこれ
update Nokonoko = あれこれ


render Kuribo = あれこれ
render Nokonoko = あれこれ

型クラスを使って、型によって関数の呼び出しを変えることもできるけど

class GameObject a where
update :: a -> a
render :: a -> IO ()

data Kuribo = Kuribo
instance GameObject Kuribo where
update self = あれこれ
render self = あれこれ

共通のリストにいれるにはやっぱり代数的データ型でまとめないといけないし、型ごとに呼び出しを書くのがメンドイ。 そこで上の存在型。

{-# OPTIONS_GHC -fglasgow-exts #-}

class GameObject a where
update :: a -> a
render :: a -> IO ()
isDead :: a -> Bool

----
data Kuribo = Kuribo

instance GameObject Kuribo where
update self = self
render self = print "Kuribo"
isDead self = False

----
data Nokonoko = Nokonoko

instance GameObject Nokonoko where
update self = self
render self = print "Nokonoko"
isDead self = True

-- ====
data ObjWrapper = forall a. GameObject a => ObjWrapper a -- 存在型aの動く範囲を型クラスに制限

updateObjList :: [ObjWrapper] -> [ObjWrapper]
updateObjList = map (\(ObjWrapper x) -> ObjWrapper $ update x)

renderObjList :: [ObjWrapper] -> IO ()
renderObjList = mapM_ (\(ObjWrapper x) -> render x)

-- ====
main = do
let objList = [ObjWrapper Kuribo, ObjWrapper Nokonoko]
let objList' = updateObjList objList
let objList'' = filter (\(ObjWrapper a) -> isDead a) objList'
renderObjList objList''
  • forall は GHC(/Hugs)拡張だから、コマンドオプションに-fglasgow-extsをつけるか、ソースに{-# OPTIONS_GHC -fglasgow-exts #-}を書く