まず基本的な症状を確認。
module Main where import Sub main = do return ()
module Sub where import Main
という2つのモジュールを用意して'ghc --make Main.hs'とすると
Module imports form a cycle for modules: Main imports: Sub Sub imports: Main
となる。要するに互いを参照するimportを書くだけでエラーなのね。これはこれでけっこうひどい挙動じゃないか?少しはモジュールの中身も見てくれないかね。
で、私がやりたいモジュール分割は
module Main where import Enemy import Bullet class TokenController t where update :: t -> t data Token = Enemy {x :: Double, y :: Double} | Bullet {x :: Double, y :: Double} instance TokenController Token where update enemy@Enemy{} = Enemy.update enemy update bullet@Bullet{} = Bullet.update bullet instance Show Token where show (Enemy x y) = "enemy(" ++ (show x) ++ ", " ++ (show y) ++ ")" show (Bullet x y) = "bullet(" ++ (show x) ++ ", " ++ (show y) ++ ")" ts = [Enemy 0 0, Bullet 0 0] main = do putStrLn (show ts) putStrLn (show (map Main.update ts))
としておいて、モジュールEnemyおよびBulletにおいて
module Enemy where import Main update enemy = enemy {x = (x enemy) + 1}
のように更新を行いたいというもの。これらは互いにimportし合っているので当然ビルドできない。
そこでこれだ。
- Haskellでmoduleを循環importする方法(id:nushio:20060627#p1)
- How to compile mutually recursive modules(http://tinyurl.com/f7c96)
{-# SOURCE #-} pragmaとhs-bootというきわめて怪しげなメカニズムを用いる。まず以下のMain.hs-bootを用意した上で
module Main where data Token = Enemy {x :: Double, y :: Double} | Bullet {x :: Double, y :: Double}
Enemy.hsおよびBullet.hsを
module Bullet where import {-# SOURCE #-} Main update bullet = bullet {y = (y bullet) + 1}
のようにする。すると
% ghc --make Main.hs Chasing modules from: Main.hs Compiling Main[boot] ( Main.hs-boot, Main.o-boot ) Compiling Bullet ( ./Bullet.hs, ./Bullet.o ) Compiling Enemy ( ./Enemy.hs, ./Enemy.o ) Compiling Main ( Main.hs, Main.o ) Linking ... % ./main.exe [enemy(0.0, 0.0),bullet(0.0, 0.0)] [enemy(1.0, 0.0),bullet(0.0, 1.0)]
という具合にビルドできる!素晴らし……いか?このほにゃらら-bootって要するにヘッダファイルみたいなもんだと思えばいいのかしらん。微妙に泥臭い。あと.hsと.hs-bootの両方に同じ定義が必要になるのがいただけないな。
SOURCE pragmaを使わない形に書き直せればいいんだけど……思いつかない。まだまだ経験不足ですな。