何故か書いてしまった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 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 #-}
を書く