続、関数型でのmodule分割

id:ABA:20060627 で循環importができないという話題の続き。

コメント欄でいただいたABAさんからのコメントで気づいたのですが、Listで使うための、ユニオン型だったんですね。たしかにmapってはいってた。

そうであれば、以下のようなのはどうでしょうか。

module TokenT where
class TokenT t where
  update :: t -> t 

-- 以下、中でupdateを使うような公開関数とか

これは同じ

module SubEnemy where
import TokenT

data Enemy = Enemy {x :: Double, y :: Double }
instance TokenT Enemy where
  update enemy = enemy { x = (x emeny) + 1 }
instance Show Enemy where
  show (Enemy x y) = "enemy(" ++ (show x) ++ ", " ++ (show y) ++ ")"

これはパッケージ名を変えただけ。SubBulletも同様

module Token
import TokenT
import SubBullet as SB
import SubEnemy as SE

data Token = Enemy {x :: Double, y :: Double } | Bullet {x :: Double, y :: Double }
instance TokenT Token where
  update (Bullet {x = ix, y = iy}) 
    = let (SB.Bullet {SB.x = ox, SB.y = oy}) = update (SB.Bullet {SB.x = ix, SB.y = iy}) in
      Bullet { x = ox, y = oy}
  update (Enemy {x = ix, y = iy}) 
    = let (SE.Enemy {SE.x = ox, SE.y = oy}) = update (SE.Enemy {SE.x = ix, SE.y = iy}) in
      Enemy { x = ox, y = oy}

単純に変換して渡すだけだが、レコード型なので量がちょっと多い。
パラメータにレコードではなく、タプルとかVertexとか使うのであれば、ここはかなり短くなる。レコードでも順番が同じなら同じ内容でコピーできたりするが:

  update (Bullet vx vy) 
    = let (SB.Bullet nx ny) = update (SB.Bullet vx vy) in
      Bullet nx ny

しかし、パッケージをまたぐ場合、順序に依存させるのはよくないと思ったので、いちいちパターンを使っている。同じ変換が何度も出るなら変換を関数やクラスにする。


という感じでしょうか。実のところSubのほうでTokenTのinstanceにして、同じupdateを使う効果はないですけど。


ただし、Haskellのmoduleも言語上はjavaのpackage同様、公開先を限定できないはずなので、moduleにする場合はやはりjavaでのpackageのような単位で分けたほうがうまくいきそうです。