続、関数型での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のような単位で分けたほうがうまくいきそうです。

変換もclassで

上記で型の変換が多い場合は関数を使う。そして変換型が多くなって関数名がながくなるならclassにしてしまうのがいいかも。

class CastToken t where
  castTo :: Token -> t
  castBack :: t -> Token

instance CastToken SB.Bullet where
  castTo (Bullet {x = ax, y = ay}) = SB.Bullet {SB.x = ax, SB.y = ay}
  castBack (SB.Bullet {SB.x = ax, SB.y = ay}) = Bullet {x = ax, y = ay}
  
instance CastToken SE.Enemy where
  castTo (Enemy {x = ax, y = ay}) = SE.Enemy {SE.x = ax, SE.y = ay}
  castBack (SE.Enemy {SE.x = ax, SE.y = ay}) = Bullet {x = ax, y = ay}

instance TokenT Token where
  update b@(Bullet {}) 
    = castBack $ update ((castTo b) :: SB.Bullet)
  update e@(Enemy {}) 
    = castBack $ update ((castTo e) :: SE.Enemy)

関数型言語のmodule

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

Haskellの場合、classはjavaのクラス的にラベル名での差は意識せず、モジュールのためのメンバーのように考えたほうがいいんじゃないだろうか。なんというか、あるフレームワークへのコネクタインタフェースみたいな感じでとらえるのがいいのだろうか。C++template的というか、xxxというメソッドを持ったなにかみたいな感じでしょうか。

SMLのモジュール構造が直接signature、structure、functorだったのを考えると、classとmodule分割の基本的な基準は、特にそういう抽象データ型的な感じになる思う(MLでも循環importはできなかった(最近はできるのかな))。利用視点からはとくにそうだと思う。C#のpartial typeみたいなのはプリプロセッサ的なコンパイラ処理であっても仕方がないかもしれない。しかし、C#とかと違ってユニオンタイプがあるので、その要求は多いかもしれない(classなどを駆使しして循環しないよう分割していくのが、Haskellerの標準レベル要求なのかもしれないけど)。


ところでABAさんの例ですが、Java的に考えてもそれがなぜ再帰的にmoduleを読む必要があるのか、そもそもユニオンを使う必要があるのかがわからない。

もし自分が設計するなら

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

-- 以下、中でupdateを使うような公開関数とか
module Enemy where
import Token

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

Bulletも同様

module Main where
import Token
import Bullet
import Enemy

main = do
  putStrLn (show ts)
  putStrLn (show (map Token.update ts))

となる。

Javaでたとえるなら以下のような感じかな

  • interface Token { t update(); }
  • (interface Show { String show()})
  • class Enemy implements Token, Show
  • class Bullet implements Token, Show
  • class Main { static void main(..){...}}

もしupdateでBulletがEnemyに、EnemyがBulletに変わるなら...まずはサイクリック回避テクニックをとることになるんでしょうかね。dataだけを分割するとかclassにしてしまうとか。

歴史と記憶、昔と最近

をみて振り返ってみる。


自分がfinalventさんの東京と同じくらいのころのオリンピックはサラエボやロサンゼルスで、確かに記憶にある。ロサンゼルスの開会式はロケットマンとかあって、たしかカール・ルイス一色だったような。あとはマラソンの瀬古や宗兄弟とかなあ。一方でサラエボはあまり覚えてない。橋本聖子黒岩彰は覚えてるけど、ふたりともカルガリーにも出てるし、どっちの記憶かは定かじゃない。

ただ、ロサンゼルスのすぐ前ってモスクワなんだよなあ。5才だけど、こうなるともう記憶にはない。そのせいかロスとモスクワでは、すごい間があるように感じる。これが歴史としての感覚なんだろう。サラエボのユーゴはそのあとで長い内戦にはいったし、ソウルのときだって韓国は軍事政権だったわけで、そう考えると時代的には同じ冷戦期ではあるけれど、個人感覚では明らかに違うんよなあ。モスクワってもうYAWARAでのネタだったとしか(そうそう、柔道の山下もロスにいたいた^^;)。

記憶と歴史いうのは説明しても差は出しにくいけど、自分の中で比較するとなんというかすごく違うんだということに気がついた。言葉に表現できないテレビの枠の周りというかなんというか。単に事件だけじゃなく、同じころのお笑い番組とかベータデッキとかおもちゃとか、そういうものに対して行った自分の行動とか、そういうのがおぼろげではあるけれど、必ず一緒に記憶に入ってるという感じ。


また、86年ていうと小学生で、アニメでいえばΖガンダムΖΖガンダムだし、ドラゴンボールファミコン版のCM(November 27を連呼するやつ)はいまだにすごく覚えてる。小遣いで買ったし。ファミコンΖガンダムも同じころだったはず。小学生である以上、ずっと昔だなあ。なんというかこの昔と今と大きく違うのは、やっぱり物事に対する視点なのかもしれない。いやくだらないことに惹きつけられてたんだなってうらやましく思う。

一方で、一人暮らしをしていて大学に入っていた95年はそんなに昔じゃないと感じる。震災の明け方なんて、自室はゆれで棚は崩れて目が覚め、揺れが収まるまでEpsonのパソコン(クロックは486/33Mhzくらいかな、メモリは5.6Mくらいだし、HDDはあったけど中はDOSゲーとWindows-3.1(winて打って起動)。ネットなんてない)のモニターを抑えてた。モニターは後ろがすごくでかいのに、その下の本体は横置きで結構幅は小さくて不安定だったのよ。そしてその日、後期テストは普通に行われてた。オウムだって91年かなあ、修学旅行で東京に来たときだけど、そこで選挙運動してたのみてる。


以前ドラクエ6について書いたときも感じたことだが、同じバブル後としてあまり昔のようには感じていないけど、細かくみるといろいろ変わってるんだよなあ。自分の場合、断絶がいろいろあったのもあるけれど、その当時のものでいまだに家に残ってるのって、書籍をのぞけば、ミニコンポと整理タンスくらいだ(コンポはもう使ってなくおいてあるだけ)。ただ、ここ数年はめぼしいものは買ってないんで、あと2,3年くらいはPC関係やガジェット、ゲーム機以外はこのまま行きそうな気がする。いまある家電なんてほとんど6年前津田沼に引っ越したときにそろえたものだし。ただそれでも10年後になれば、がらっと変わってるかもしれないという予感はするのだが。