モジュールを相互参照する
GHC では、モジュールの相互参照をすることもできるが、そのためには循環参照を回避するために、以下のような手順を踏む必要がある。まず、互いに相互参照しているモジュールがあるとすると、
module A where
import B ( TB(..) )
newtype TA = MkTA Int
f :: TB -> TA
f (MkTB x) = MkTA x
module B where
import {-# SOURCE #-} A ( TA(..) )
data TB = MkTB !Int
g :: TA -> TB
g (MkTA x) = MkTB x
B.hs
で A
モジュールをロードするのに、 import {-# SOURCE -#} A ...
と SOURCE プラグマを使っている点に注目。 SOURCE プラグマを指定することで、循環参照を回避するために A.hs
でなく A.hs-boot
というファイルをロードするようになる。このファイルに B
をロードするために必要な、最小限の A
モジュールの内容を記述しておく、というわけ。
module A where
newtype TA = MkTA Int
これでコンパイルできるようになる。
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.8.3
$ ghc A.hs
[1 of 3] Compiling A[boot] ( A.hs-boot, A.o-boot )
[2 of 3] Compiling B ( B.hs, B.o )
[3 of 3] Compiling A ( A.hs, A.o )
.hs-boot ファイルの制限
.hs-boot
ファイルの制限は、主に以下のような制限がある。
- 対応する .hs
ファイルと同じディレクトリに配置する必要がある。
- .lhs
ファイルの場合、 .lhs-boot
となる。
- .hs-boot
ファイルには、対応する .hs
ファイルのサブセットしか含めることができない。
A hs-boot file need only contain the bare minimum of information needed to get the bootstrapping process started. For example, it doesn't need to contain declarations for everything that module
A
exports, only the things required by the module(s) that importA
recursively.
いうことで、 .hs-boot
ファイルには、 .hs
ファイルと異なる内容を記述することはできない。試しに、上記の A.hs-boot
に、 A.hs
で定義されていない関数定義を追加してみると、
module A where
newtype TA = MkTA Int
hoge :: Int -> Int
A.hs-boot:5:1:
A.hoge is exported by the hs-boot file, but not exported by the module
と、怒られる。
- .hs-boot
ファイルには、変数や関数の型宣言のみを記述でき、定義本体を含めることはできない。
There must be no value declarations, but there can be type signatures for values. For example:
double :: Int -> Int
**- ``.hs-boot`` ファイルの data 宣言では、型定義本体を含めず、抽象型としておくことができる。**
> A data type declaration can either be given in full, exactly as in Haskell, or it can be given abstractly, by omitting the ``=`` sign and everything that follows. For example:
> ```haskell
data T a b
In a source program this would declare
TA
to have no constructors ( a GHC extension: see Data types with no constructors ), but in an hi-boot file it means "I don't know or care what the constructors are". This is the most common form of data type declaration, because it's easy to get right. You can also write out the constructors but, if you do so, you must write it out precisely as in its real definition.
If you do not write out the constructors, you may need to give a kind annotation ( Explicitly-kinded quantification ), to tell GHC the kind of the type variable, if it is not
*
. ( In source files, this is worked out from the way the type variable is used in the constructors. ) For example:
data R (x :: * -> *) y
> You cannot use ``deriving`` on a data type declaration; write an instance declaration instead.
## 参考にしたサイト
- [4.7.9 How to compile mutually recursive modules](http://www.haskell.org/ghc/docs/latest/html/users_guide/separate-compilation.html#mutual-recursion)