読者です 読者をやめる 読者になる 読者になる

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の解決を自動化しない事情があるのかもしれないが、何だろう。