Haskellでmoduleを循環importする方法

http://d.hatena.ne.jp/ABA/20060626
我が愛してやまないゲームを作りまくっているabaさんがHaskellに挑戦して悩んでいるので、ぜひとも応援すべく一緒に悩んでみました。

  • Main.hs
module Main(
            main
)where
import CycleA
main = interact $ ("Haskell is "++).unlines.map (hogeA.read).words
  • CycleA.hs
module CycleA(
              hogeA
)where
import CycleB
hogeA x | x <= 0    = "cool!"  
        | otherwise = "ultra " ++ hogeB (x-1) 
  • CycleB.hs
module CycleB(
                 hogeB
)where
import CycleA
hogeB f x | x<=0      = "great!"
          | otherwise = "super " ++ hogeA (x-1)

を作ってmakeしようとすると

>ghc Main.hs --make
Chasing modules from: Main.hs
Module imports form a cycle for modules:
  CycleA imports: CycleB
  CycleB imports: CycleA

循環があると言われてできない。


で、考えた解法はこう。

module CycleA(
              hogeA
)where
import CycleB
hogeA x | x <= 0    = "cool!"  
        | otherwise = "ultra " ++ hogeB hogeA (x-1) 
module CycleB(
                 hogeB
)where
hogeB f x | x<=0      = "great!"
          | otherwise = "super " ++ f (x-1)

旧版のプログラムだとhogeBのパートナーはhogeAに固定で、しかもそれをimportしてhogeBからグローバルスコープ(?)で見えるように置いとかなきゃ動かなかった。hogeBを、任意の関数を取れるように抽象化しておけば、import循環を起こす必要自体がなくなる。さすが関数型言語


今回もだけど、Haskellで書いてて不自由だなと思ったときは、実は抽象化(?)が足りてなくて、Haskell様の御意向に沿うように書こうと工夫すると、自分が書きたかったものよりも良い書き方がみつかる、ことが何回かあったんだ。Haskellは駄目を出すだけでどこが駄目なのかは生徒に考えさせる名教師だったんだよ!

というのが少ししかないHaskell経験による妄想で、実際にはhs-bootファイルを手で書くという方法で循環importは作れるのであった。
http://www.haskell.org/ghc/docs/latest/html/users_guide/separate-compilation.html#mutual-recursion
しかし余りエレガントじゃないように見える。cのプロトタイプ宣言みたいだ。循環importの解決を自動化しない事情があるのかもしれないが、何だろう。

Haskell Game Programming Minimum

僕が新しいプログラミング言語を学ぶときは、初めて作ったプログラムに覚えた機能を少しづつ足していって、その結果Hello Worldがいつの間にかゲームに化けてる、という方法をとります。
monadiusが実はそれで、試行錯誤の結果が堆積しており肥大化してあまりに読みにくいので、重力レンズシミュレータを作ったときは、また1からやり直して書きました。

その中間産物で

  1. Haskell
  2. openGLを使って
  3. キー入力とマウス入力に応答して描画する

だけのシンプルなプログラムをここに置いときます。
http://www.geocities.jp/takascience/haskell/glKeyMouse.zip
これもあんまり読みやすくないけど・・・