この節では、GHCがどんなファイルを見つけ、どんなファイルを作るか、それらのファイルはどこに置かれるか、この振る舞いに影響を与えるオプションは何か、を解説する。
この節は階層的モジュールを念頭において書かれていることに注意。(7.3.5. 階層的モジュールを見よ) 階層的モジュールはHaskell98への拡張で、モジュール名がドット「.」を含むことができるようにする。したがって、非階層的なモジュールは、モジュール名がドットを含まないという特別の場合になる。
パス名に関する慣習はシステムごとに異なる。特に、ディレクトリの区切りはUnixシステムでは「/
」であり、Windowsシステムでは「\
」である。以降の節では、一貫して「/
」をディレクトリ区切りとして用いる。あなたのシステムにおける適切な文字に置き換えて読んでほしい。
全てのHaskellソースモジュールは専用のファイルに置かれているべきである。
ふつう、ファイル名は、モジュール名をもとにして、ドットをディレクトリ区切りに置き換えたものにされるべきである。例えば、Unixシステムでは、A.B.C
というモジュールは、なんらかの基底ディレクトリからの相対パスでA/B/C.hs
というファイルに置かれるべきである。他のモジュールからインポートされる予定がないモジュールには(例えばMain
)、どんな名前をつけても構わない。
GHCはソースファイルがASCIIまたはUTF-8のみだと推定し、他のエンコード方式は認識しない。しかし、UTF-8として不正な並びはコメント内部では無視されるので、Latin-1のような他のエンコード方式を、コメント以外の部分がASCIIのみから成ることを条件に使うことができる。
ソースファイルをコンパイルするとき、GHCはふつう二つのファイルを出力する。オブジェクトファイルとインタフェースファイルである。
オブジェクトファイルは通常.o
で終わり、そのモジュールのコンパイル済みコードを含む。
インタフェースファイルはふつう.hi
で終わり、そのモジュールに依存するモジュールをコンパイルするときに必要な情報を含む。エクスポートされた関数の型や、データ型の定義といったものがこれに含まれる。バイナリ形式なので、読もうとしないように。代わりに—-show-iface
オプションを使うことができる。(4.7.7. インタフェースファイルに関連するその他のオプションを見よ)
オブジェクトファイルとインタフェースファイルは対になっていると考えるべきである。なぜなら、インタフェースファイルとは、ある意味で、対応するオブジェクトファイルの説明を、コンパイラ可読の形にしたものだからである。なんらかの理由でインタフェースファイルとオブジェクトファイルの間に不整合ができると、コンパイラがオブジェクトファイルについて誤った仮定を置くことになるかもしれない。こうなると、まず間違いなく厄介なことになる。このため、オブジェクトファイルとインタフェースファイルは同じ場所に置くことを推奨する。(GHCはデフォルトでこれを行うが、すぐ下で説明するようにこれを変更することもできる)
全てのモジュールには、ソースコード中で定義(module A.B.C where ...
)されたモジュール名がある。
GHCが生成するオブジェクトファイルの名前は次の規則によって決められる。ただしosuf
はオブジェクトファイルの接尾辞(これは-osuf
で変えられる)である。
-odir
オプションが指定されていないとき(デフォルト)は、オブジェクトファイルの名前は、ソースファイルの名前(モジュール名は無視される)をもとに、接尾辞をosuf
に付け替えることで得られる。
-odir
dir
が指定されているときは、オブジェクトファイルの名前はdir
/mod
.osuf
になる。ここでmod
はモジュールの名前のドットをスラッシュで置き換えたものである。必要なディレクトリ構造がdir
の下にないなら、GHCは黙ってそれを作る。
インタフェースファイルの名前も同じ規則で得られるが、osuf
の代わりにhisuf
(デフォルトは.hi
)が使われ、オプションは-odir
と-osuf
ではなく-hidir
と-hisuf
である。
例えば、GHCがsrc/A/B/C.hs
にあるA.B.C
モジュールを、-odir
および-hidir
フラグなしにコンパイルすると、インタフェースファイルはsrc/A/B/C.hi
に置かれ、オブジェクトファイルはsrc/A/B/C.o
に置かれる。
全てのインポートされたモジュールについて、インポート文におけるモジュールの名前は、4.7.3. 探索パスで説明されている方法で見付かったインタフェースファイル(またはソースファイル)に書かれているモジュールの名前と一致しなければならない。このため、大部分のモジュールでは、ソースファイル名とモジュール名は一致している必要がある。
Main
モジュールをfoo.hs
という名前のファイルに置くことは問題ない。ただし、これはGHCがMain
モジュールのインタフェースファイルを探すことがない(Main
モジュールはインポートされないので)からうまくゆくに過ぎないということに注意。これのおかげで、単一のディレクトリに複数のMain
モジュールを持つことができ、GHCがこれによって混乱するということもない。
一括コンパイルモードでは、出力ファイルの名前を-o
で指定することができるし、インタフェースファイルの名前も-ohi
オプションで直接指定できる。
プログラム中では、import Foo
と言うことによってモジュールFoo
をインポートする。--make
モードとGHCiでは、GHCはFoo
のソースファイルを探し、そちらを先にコンパイルするようにする。--make
が与えられなかったときは、Foo
のインタフェースファイルを探す。これは、あらかじめFoo
をコンパイルすることで作られていなければならない。どの場合でも、GHCが適切なファイルを見つけるのに使う戦略は同じである。
戦略とはこうである。GHCは探索パスと呼ばれるディレクトリの一覧を保持している。これらのディレクトリ一つずつに対して、basename
.
extension
を後置し、そのファイルが存在するか調べる。basename
の値はモジュール名のドットをディレクトリ区切り(システムによって「/」または「\」)で置換したものであり、extension
は、--make
モードとGHCiではソース拡張子(hs
、lhs
)であり、その他ではhisuf
である。
例えば、探索パスにd1
、d2
、d3
の各ディレクトリがある状況で、--make
モードでA.B.C
というモジュールのソースファイルを探しているとしよう。GHCが探すのはd1/A/B/C.hs
、d1/A/B/C.lhs
、d2/A/B/C.hs
等々である。
デフォルトでは探索パスにはただ一つのディレクトリ「.」(つまりカレントディレクトリ)が含まれる。以下のオプションを使って、探索パスの内容を追加したり変更したりできる。
-idirs
dirs
(コロン区切りの一覧)を探索パスの後ろに連結する。
-i
探索パスを空にする。
以上のことが全てではない。GHCはコンパイル済みライブラリ(パッケージと呼ばれる)からもモジュールを探す。詳細はパッケージについての節(4.9. パッケージ )を見よ。
-o
file
通常、GHCでのコンパイル結果は、.hc
、.o
などのファイルに出力される。どれが選ばれるかは、最後に走ったコンパイル段階に依る。-o
というオプションは、その最終段階の出力先をfile
file
に変更する。
注意: この「仕様」は直観に反することがある。ghc -C -o foo.o foo.hsでは、中間のCコードがファイルfoo.o
に(名前に反して)出力される。
このオプションがもっともよく使われるのは、実行ファイルを作るときに、その名前を指定するためである。例えば、
ghc -o prog --make Main
はMain
モジュールからはじまるプログラムをコンパイルし、実行ファイルをprog
に置く。
注意: Windowsでは、結果が実行ファイルのとき、指定されたファイル名に拡張子「.exe
」がなければ、追加される。つまり、
ghc -o foo Main.hs
はMain.hs
というモジュールをコンパイル・リンクし、生成された実行ファイルをfoo.exe
(foo
ではなく)に置く。
ghc --makeを-o
なしに使うと、実行ファイルの名前はMain
モジュールを含むファイルの名前を元にして決まる。GHCではMain
モジュールがMain.hs
に置かれている必要はないということに注意せよ。よって、
ghc --make Prog
と
ghc --make Prog.hs
はどちらもProg
(あるいはWindowsならProg.exe
)を生成する。
-odir
dir
オブジェクトファイルの出力先をdir
に変更する。例えば以下のように。
$ ghc -c parse/Foo.hs parse/Bar.hs gurgle/Bumble.hs -odir `uname -m`
こうすると、Foo.o
、Bar.o
、Bumble.o
の各オブジェクトファイルは、実行中の機械のアーキテクチャの名前(x86
、mips
など)のサブディレクトリに置かれる。
-odir
オプションはインタフェースファイルがどこに置かれるかに影響しないことに注意。それには-hidir
オプションを使うこと。上の例では、インタフェースファイルは相変わらずparse/Foo.hi
、parse/Bar.hi
、gurgle/Bumble.hi
に置かれる。
-ohi
file
インタフェースの出力先をbar2/Wurble.iface
にするには、-ohi bar2/Wurble.iface
を使えば良い。(非推奨)
警告: インタフェースファイルの出力先を変更して、GHCがそれを見付けられない場合、再コンパイル検査器が混乱するかもしれない(少なくとも、再コンパイルを避けることはできなくなる)。代わりに、可能なら-hidir
と-hisuf
オプションを組み合わせることを推奨する。
そもそもインタフェースファイルを出力させたくない場合、このオプションを使ってインタフェースファイルの出力先にビットバケツを指定することができる。例えば、-ohi /dev/null
のように。
-hidir
dir
生成されるインタフェースファイルの出力先をdir
に変更する。
-stubdir
dir
生成されるFFIのスタブファイルの出力先をdir
に変更する。スタブファイルは、Haskellソースがforeign export
またはforeign import "&wrapper"
という宣言を含むときに生成される(8.2.1. GHCでforeign export
とforeign import ccall "wrapper"
を使うを見よ)。階層的モジュール名に関しては、-stubdir
オプションは-odir
や-hidir
と全く同様に振る舞う。
-dumpdir
dir
全てのdumpファイルの出力先をdir
に変更する。dumpファイルは、-ddump-to-file
が他の-ddump-*
フラグと共に使われたときに生成される。
-outputdir
dir
-outputdir
オプションは-odir
、-hidir
、-stubdir
、-dumpdir
の組み合わせの略記である。
-osuf
suffix
,
-hisuf
suffix
,
-hcsuf
suffix
-osuf
suffix
はオブジェクトファイルの接尾辞を.o
から指定された物に変える。我々は、ライブラリをコンパイルするとき、プロファイル版のオブジェクトファイルが通常版のものを破壊するのを防ぐためにこれを使っている。
同様に、-hisuf
suffix
は非システムのインタフェースファイルの.hi
接尾辞を別のものに変える。(4.7.7. インタフェースファイルに関連するその他のオプションを見よ)
最後に、-hcsuf
suffix
オプションは、コンパイラに生成される中間Cファイルの.hc
接尾辞を変更する。
-hisuf
/-osuf
は、あるプログラムについて、プロファイルの有効なものと無効なものの両方を一つのディレクトリでコンパイルしたいときに特に有用である。通常版を得るには
ghc ...
とすればよく、プロファイル版を得るには
ghc ... -osuf prof.o -hisuf prof.hi -prof -auto-all
とすればよい。
以下のオプションは、ある種の中間ファイルをそのままにしておくのに有用である。通常、これらのファイルはコンパイル終了後にGHCによって破棄される。
-keep-hc-file
-keep-hc-files
.hs
から.o
へのコンパイルをCを介して行うとき、中間の.hc
ファイルを消さない。(注意: 非レジスタ化コンパイラによってしか.hc
ファイルは生成されない。)
-keep-llvm-file
, -keep-llvm-files
.hs
から.o
へのコンパイルをLLVMを介して行うに際して、中間の.ll
ファイルを消さない。(注意: ネイティブコード生成器が使われているときは.ll
ファイルは生成されない。生成されるようにするのに-fllvm
を使う必要があるかもしれない)
-keep-s-file
-keep-s-files
中間の.s
ファイルを消さない。
-keep-tmp-files
GHCドライバに対して、一時ファイルを削除しないように指示する。一時ファイルはふつう/tmp
に置かれる(別の場所かもしれない。4.7.6. 一時ファイルの場所を変更するを見よ)。GHCを-v
付きで実行すればどういう一時ファイルが作られているかを見ることができる。
-tmpdir
/tmp
(インストールの仕方によっては別の場所かもしれない。とにかく一時ファイルを置くところ)の空き容量が足らなくて困っているなら、-tmpdir <dir>
オプションを使って別のディレクトリを指定することができる。例えば、-tmpdir .
とすれば、一時ファイルは現在の作業ディレクトリに置かれる。
別の方法として、TMPDIR
環境変数を使うこともできる。これを一時ファイルを置くべき場所に設定する。GCCその他のプログラムもTMPDIR
変数を尊重する。
さらに良い方法は、GHCをビルドするときにDEFAULT_TMPDIR
というmake変数を設定することである。こうすれば、二度とTMPDIR
の心配をする必要がない。(ビルド説明書を見よ)
-ddump-hi
新しいインタフェースを標準出力に表示する。
-ddump-hi-diffs
一般に、新しい.hi
が既存のものと同じなら、上書きは行われない。これはmakeとうまく協調する。インタフェースが実際に変更されたとき、これはしばしば知るに値することである。-ddump-hi-diffs
が与えられると、GHCは新旧の.hi
ファイルの差異を報告する。
-ddump-minimal-imports
モジュールをコンパイルするとき、そのモジュールの「最小の」インポート宣言を
(ただしM
.importsM
はモジュール名)というファイルに出力する。.imports
ファイルが作られるディレクトリは-dumpdir
オプションによって制御される。
にあるインポート宣言を、対応するM
.hs.imports
ファイルにあるものに置き換えても安全である。こうすることの利点は、(1)「最小の」インポート宣言は全てを明示的にインポートする (2)不要なものは全くインポートしない ことである。この特徴を手で維持するのは非常な苦痛であり得るので、その作業を軽減するためにこのフラグがある。
--show-iface
file
インタフェースファイルの中身を人間可読(っぽいよう)な形式で表示する。ただしfile
はそのファイルの名前である。4.5. 実行モードを見よ。
-fforce-recomp
再コンパイル検査を無効にする。(デフォルトでは有効である)。再コンパイル検査が有効だと、あるモジュールが再コンパイルの必要無しと判断された場合、コンパイルは早期に停止し、既存の.o
はそのまま残される。
昔、GHCは新しく生成された.hi
を前の版と比較し、それらが同一なら、前の版をそのままにして、変更日時を変えることもしなかった。この結果、.hi
が変更されていないモジュールをインポートしたモジュールは再コンパイルされなかった。
これはもはやうまく行かない。モジュールC
がモジュールB
をインポートし、B
がモジュールA
をインポートしているとしよう。このとき、モジュールA
が変更されたなら、モジュールC
を再コンパイルしなければならないかもしれない。したがって、A.hi
が変更されたときはC
が再コンパイルされるべきか確かめねばならない。しかし、C
の依存関係一覧に載るのはB.hi
だけであって、A.hi
ではない。そのため、A
になんらかの変更(例えば、ある関数の定義が変更され、B
がエクスポートする関数のインライン展開結果にその関数が現れる場合)が加えられてもB.hi
には一切の変化がないということも考えられる。そういうわけで…
GHCは、インタフェースファイルそれぞれ、およびその中の宣言それぞれについて指紋(実はMD5ハッシュ)を計算する。さらに、全てのインタフェースファイルに、最後にコンパイルされたときに使われたもの全ての指紋一覧を記録しておく。ソースファイルの変更日時が.o
よりも古く(つまり最後にコンパイルされたときからソースが変更されていない)、再コンパイル検査が有効なら、GHCは賢い振る舞いをする。今回必要なものの指紋一覧と前回必要とされたものの指紋一覧(これはコンパイルされているモジュールのインタフェースファイルから拾ってくる)を比較し、全て同じなら「Compilation IS NOT required」と言ってコンパイルをかなり早い段階で中断するのである。何と美しい眺めであろうか!
GHC commentaryでこれら全てがどうやって動いているかについて読むことができる。
GHCでは相互再帰的なモジュールをコンパイルすることができる。この節ではその方法を説明する。
モジュールのインポート関係が成すグラフにおけるサイクルは全てhs-boot
ファイルを使って解消しなければならない。A.hs
とB.hs
がHaskellのソースファイルだとしよう。つまり、
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
ここでA
はB
をインポートしているが、B
はA
を{-# SOURCE #-}
プラグマ付きでインポートしているので、環状の依存性が解消されている。モジュールのインポートグラフに存在する全てのループは{-# SOURCE #-}
付きインポートで解消されていなければいけない。言い換えると、{-# SOURCE #-}
付きインポートを無視した場合にモジュールのインポートグラフに閉路がなくなるようになっていなければならない。
このように{-# SOURCE #-}
付でインポートされる全てのモジュールA.hs
に対して、A.hs-boot
というソースファイルが存在しなければならない。このファイルはA.hs
の縮訳版を含む。つまり、
module A where newtype TA = MkTA Int
これらの三つのファイルをコンパイルするには、次のコマンドを発行すれば良い。
ghc -c A.hs-boot -- A.hi-bootとA.o-bootを作る ghc -c B.hs -- A.hi-bootを使ってB.hiとB.oを作る ghc -c A.hs -- B.hiを使ってA.hiとA.oを作る ghc -o foo A.o B.o -- プログラムをリンクする
ここで、いくつか注意すべきことがある。
A.hs-boot
はプログラマが書くソースファイルである。これは、親ソースファイルであるa.hs
と同じディレクトリになければいけない。現在のところ、A.lhs
という文芸形式のソースファイルを使うなら、ブートファイルもA.lhs-boot
と文芸形式でなければならない。逆も同様である。
hs-boot
は、hs
ファイルと同じようにGHCでコンパイルされる。
ghc -c A.hs-boot
A.hs-boot
というhs-bootファイルがコンパイルされると、通用範囲と型に関するエラーがないかどうか検査される。親モジュールであるA.hs
がコンパイルされるとき、この二つが比較され、不整合があればエラーが報告される。
A.hs
をコンパイルするとA.hi
というインタフェースファイルとA.o
というオブジェクトファイルが生成されるが、これと同様にA.hs-boot
をコンパイルするとA.hi-boot
というインタフェースファイルとA.o-boot
という疑似オブジェクトファイルが生成される。
疑似オブジェクトファイルA.o-boot
は空である(リンクしないように!)が、Makefileを使っているときにはとても便利である。これを使って、いつA.hi-boot
が最新の状態を反映するようになったかが記録できるからである。(4.7.10. makeを使うを見よ)
hs-boot
ファイルをコンパイルすることで得られるhi-boot
は、他のGHCのインタフェースファイル(例えばB.hi
)と同じ、機械生成のバイナリ形式である。中身はghc --show-ifaceで表示できる。インタフェースファイルのディレクトリを指定した場合(-ohidir
フラグ)、hi-boot
ファイルにも効果がある。
hs-bootファイルは親ファイルとは別のものであると考え、{-# SOURCE #-}
付きのインポートはhs-bootファイルを対象としていると考えたとき、モジュールのインポート関係が成すグラフにサイクルがあってはならない。ghc -Mコマンドはサイクルを見つけるとエラーを報告する。
モジュールM
が{-# SOURCE #-}
付でインポートされているなら、ふつう、プログラムの他の場所から通常の形式でもインポートされているはずである。そうでないとき、ghc --makeは自動的にコンパイル・リンクの対象モジュールにM
を含める。これは、M
の実装が最終的なプログラムに含まれるようにするためである。
hs-bootファイルにはブートストラップのための最小限の情報があれば良い。例えば、A
がエクスポートするもの全ての宣言を含んでいる必要はなく、A
を再帰的にインポートするモジュールが必要とするものだけで良い。
hs-bootファイルはHaskellのサブセットで書かれる。
モジュールヘッダ(エクスポート一覧を含む)、インポート文、有効範囲規則は、Haskellと全く同じである。したがって、プレリュード以外で定義された型やクラスに言及するには、それをインポートしなければならない。
値の宣言があってはならない。しかし、値の型シグネチャはあっても良い。例えば、
double :: Int -> Int
結合性宣言はHaskellと同じである。
通常の型シノニムの宣言はHaskellと同じである。
開型族の宣言、およびデータ族の宣言はHaskellと同じである。
閉型族は、以下の例のように等式を省略しても良い。
type family ClosedFam a where ..
ここでの..
は文字通りの意味で使われている。つまり、ファイル中に二個のドットを書くのである。閉型族を開型族と区別するために、where
節は相変わらず必要である。閉型族に等式を一つでも与えるなら、全ての等式を与えなければならず、それらは対応するHaskellファイルに現われるのと同じ順序でなければならない。
データ型の宣言はHaskellと同じように完全な形で与えても良いし、「=」記号以下を省略して抽象的に与えても良い。例えば、
data T a b
ソースプログラムにおいては、これはTAが構築子を持たないという宣言である(GHC拡張。7.4.1. 構築子のないデータ型を見よ)が、hi-bootファイルでは「どんな構築子があるか知らない・気にしない」という意味である。この方式は間違えにくいので、よく使われる。構築子を書くことも確かにできるが、その時は、本物の定義と全く同じように書かなければならない。
構築子をいちいち書かないときは、型変数の種が「*」でない場合、種注釈(7.13.5. 明示的に種付けされた量化)を与えてその旨をGHCに伝えないといけないかもしれない。(ソースファイルでは、その型変数が構築子中でどのように使われているかを基にして推測される)。例えば、
data R (x :: * -> *) y
deriving
をデータ型宣言で使うことはできない。代わりにinstance
宣言を使うこと。
クラス宣言はHaskellと同じであるが、メソッドのデフォルト宣言を置いてはならない。全てのスーパークラスとクラスメソッドを完全に省略することもできる。ただし、全て省略するか全く省略しないかのいずれかでなければならない。
Haskellと同じようにインスタンス宣言を含めることができる。ただし「where」部は含めないこと。
クラスおよびデータ型のデフォルトの役割はrepresentationalになった。その他の役割を得るには、役割注釈を使うこと。(7.25. 役割 を見よ。)
GHCと併用するためのMakefile
を用意するのは単純である。ただし、ソースファイル名とモジュール名が一致していることが前提である。
HC = ghc HC_OPTS = -cpp $(EXTRA_HC_OPTS) SRCS = Main.lhs Foo.lhs Bar.lhs OBJS = Main.o Foo.o Bar.o .SUFFIXES : .o .hs .hi .lhs .hc .s cool_pgm : $(OBJS) rm -f $@ $(HC) -o $@ $(HC_OPTS) $(OBJS) # 標準的なサフィックスルール .o.hi: @: .lhs.o: $(HC) -c $< $(HC_OPTS) .hs.o: $(HC) -c $< $(HC_OPTS) .o-boot.hi-boot: @: .lhs-boot.o-boot: $(HC) -c $< $(HC_OPTS) .hs-boot.o-boot: $(HC) -c $< $(HC_OPTS) # モジュール間の依存関係 Foo.o Foo.hc Foo.s : Baz.hi # FooはBazをインポートしている Main.o Main.hc Main.s : Foo.hi Baz.hi # MainはFooとBazをインポートしている
(洗練されたmakeなら上記のことがもっと優雅にできるかもしれない。特に、gmakeのパターンルールを使えば、以下のようにより判り易く書くことができる。
%.o : %.lhs $(HC) -c $< $(HC_OPTS)
上記の方法はどのmakeでもうまく行くはずである。
嘘臭い.o.hi
ルールに注意: これはインタフェース(.hi
)ファイルがソースに依存していることを記している。このルールは、.hi
ファイルが.o
ファイルから「何もしない」ことで生成されると述べている。これは実際その通りである。
サフィックスルールはHaskellソースファイルに一回、hs-boot
ファイル(4.7.9. 相互再帰的なモジュールをコンパイルするにはを見よ)に一回で計二回繰り返されている。
Makefileの最後にモジュール間の依存関係が書かれているが、これは次のような形をしている。
Foo.o Foo.hc Foo.s : Baz.hi # Foo imports Baz
これが言っているのは、もしFoo.o
かFoo.hc
かFoo.s
のどれかついて、最終更新日時がBaz.hi
よりも前ならば、そのファイルは古くなっているので更新されなければならない、ということである。更新するということになると、make
はそのためのルールを探す。そして、あらかじめ定義しておいたサフィックスルールのいずれかがうまく働く。これらの依存関係はghcを使って自動的に生成することもできる。4.7.11. 依存関係を生成するを見よ。
Foo.o : Bar.hi
のようなモジュール間の依存関係を手でMakefile
に書き込むのはかなり間違いやすい作業である。しかし安心してほしい。GHCは再ビルドの依存関係を自動的に生成することができる。これには、まず、以下のものをMakefile
に加える。
depend : ghc -M $(HC_OPTS) $(SRCS)
次に、初めてコンパイルする前、およびプログラムのimport
を変更したときは、make cool_pgmする前にmake dependする。これで、ghc -Mが必要な依存関係をMakefile
の末尾に加える。
一般に、ghc -M Fooは次のことをする。Foo
の各要素、およびFoo
の要素から直接・間接にインポートされている各モジュールに対して(このモジュールをM
と呼ぶ)、以下のものをMakefileに加える。
オブジェクトファイルがソースファイルに依存していることを示す行。
M.o : M.hs
(または、M.lhs
を使っているならこちら)
全てのM
中のインポート宣言import X
に対して、M
がX
に依存していることを示す行。
M.o : X.hi
全てのM
中のインポート宣言import {-# SOURCE #-} X
に対して、M
がX
に依存していることを示す行。
M.o : X.hi-boot
(hi-boot
様式のインタフェースファイルについて詳しくは4.7.9. 相互再帰的なモジュールをコンパイルするにはを見よ。)
M
が複数のモジュールをインポートしているなら、M.o
をターゲットとする行が複数できることになる。
ghc -Mコマンドの引数として全てのモジュールを列挙する必要はない。ghcが依存性を追跡する。これはghc --makeの場合と同様である。(GHC 6.4の新機能)
ghc -M
が依存性グラフのそれぞれのモジュールのソースコードを見つける必要があることに注意せよ。そうでないと、インポート宣言をパースして依存関係を追跡することができない。したがって、ソースファイルのないコンパイル済みモジュールは全てパッケージに属していなければならない[7]。
デフォルトでは、ghc -Mは全ての依存関係を生成し、それをmakefile
(存在しなければMakefile
)の末尾に連結する。この時、追加部分は「# DO NOT DELETE: Beginning of Haskell dependencies
」と「# DO NOT DELETE: End of Haskell dependencies
」の二行で囲われる。これらの行がすでに存在するときは、先に古い依存関係が消去される。
コンパイル時に使うのと同じ-package
オプションをghc -M
にも渡すのを忘れないように。こうすることで、依存関係生成器がパッケージ由来の被インポートモジュールの場所を把握できるようになる。ただし、パッケージモジュールは生成される依存関係には含まれない。(しかし下記の--include-pkg-deps
オプションを見よ)
GHCの依存関係生成の段階にいくつかのオプションを渡すことができるので、活用すると便利かもしれない。依存関係生成器に影響を与えるオプションは以下のものである。
-ddump-mod-cycles
モジュールグラフにおけるサイクルの一覧を表示する。こういうサイクルを排除しようとしているときに便利である。
-v2
モジュールの依存関係の完全な一覧を標準出力に印字する。(これは通常の多弁さフラグなので、この一覧は-v3
や-v4
でも表示される。4.6. 多弁さに関するオプション参照)
-dep-makefile
file
makefile
やMakefile
の代わりにfile
をmakefileとして使う。もしfile
が存在しないなら、mkdependHSが作る。我々はしばしば、-dep-makefile .depend
として依存関係を.depend
に置き、それをMakefile
にincludeする、ということを行う。
-dep-suffix <suf>
追加の依存関係として、接尾辞.<suf>_<osuf>
のファイルから接尾辞.<suf>_hi
のファイルまたは({-# SOURCE #-}
インポートなら).hi-boot
への依存性を宣言する。複数の-dep-suffix
フラグを指定しても良い。例えば、-dep-suffix a -dep-suffix b
とすると、.hs
からhi
へ、.a_hs
から.a_hi
へ、.b_hs
から.b_hi
への依存性が作られる。(これはNoFibの「way」を使うときに便利である)
--exclude-module=<file>
<file>
が「安定して」いるとみなす。つまり、このファイルへの依存性を含めない。
--include-pkg-deps
パッケージからインポートされているモジュールを不安定だとみなす。つまり、インポートされたパッケージモジュールへの依存性を全て生成する。(これにはPrelude
や、その他の標準Haskellライブラリも含まれる)。依存関係がパッケージの中身まで再帰的に追跡されることはない。生成されるのは、ホームパッケージのモジュールから、それが直接インポートするパッケージ外のモジュールへの依存性だけである。このオプションは通常、各種のシステムライブラリによってのみ使われる。
Haskellの規定によると、モジュールMをコンパイルするとき、M「以下」の全てのモジュールの全てのインスタンス宣言が可視である。(モジュールAがM「以下」であるとは、Aが直接Mにインポートされているか、または直接MにインポートされているモジュールBが存在して、AがB以下であることである)。したがって、理論上は、GHCはM以下の全てのモジュールのインタフェースファイルを読まなければならない。それらにMに関係するインスタンス宣言が含まれているかもしれないからである。しかし実際的にはこれは最悪なので、GHCはより賢く振る舞おうとする。
特に、あるインスタンス宣言について、その宣言の頭部(“=>
”より後の部分のこと。7.6.3.3. インスタンス文脈に関する規則の緩和を見よ)で言及されている型やクラスのいずれかが、そのインスタンス宣言があるのと同じモジュールで定義されているなら、GHCはいずれにせよそのインタフェースファイルを訪れねばならない。例えば、以下のような場合である。
module A where instance C a => D (T a) where ... data T a = ...
このインスタンス宣言は型Tが使われているときのみ関係する。そして、もしTが使われているなら、GHCはTの定義を見つけるためにAのインタフェースファイルを訪れているはずである。
唯一問題になるのは、あるモジュールにインスタンス宣言があって、しかもGHCがそのモジュールを訪れる理由が他にない場合である。例えば、
module Orphan where instance C a => D (T a) where ... class C a where ...
ここでは、DもTもOrphanモジュールで宣言されていない。このようなモジュールは「孤立モジュール」と呼ばれる。GHCは孤立モジュールを認識し、モジュールをコンパイルするときにはそのモジュール以下の全ての孤立モジュールのインタフェースファイルを訪れる。この作業は大抵無駄になるが、回避する方法はない。したがって、孤立モジュールの数をなるべく減らすように努力するべきである。
関数従属によって話がややこしくなる。次のものがあったとしよう。
module B where instance E T Int where ... data T = ...
これは孤立モジュールだろうか。T
が同じモジュールで定義されているので、そうでないように見える。しかし、E
に関数従属性があったとしよう。
module Lib where class E x y | y -> x where ...
すると、あるモジュールMがこれをインポートしたとして、(E a Int)
という制約はa = T
とすることで「改善」されねばならない。MでT
が明示的に言及されていないにも関わらず、である。
孤立モジュールは、一つ以上の孤立インスタンスまたは一つ以上の孤立規則を含む。
モジュールMにあるインスタンス宣言が孤立インスタンスなのは、
そのインスタンス宣言のクラスがMで宣言されたものでなく、かつ
そのクラスに関数従属がなくそのインスタンスの頭部にある型構築子がどれもMで宣言されていない、または、ある関数従属があって、それについて、インスタンス頭部のうち決定されない部分で言及される型構築子が、どれもMで定義されていない場合である。
考慮されるのはインスタンスの頭部だけである。上記の例では、Cの宣言がモジュールAにあるが、これでは十分でない。DかTの宣言でなければならない。
モジュールMにある書き換え規則が孤立規則なのは、規則の左辺に自由変項として現れる変数、型構築子、クラスがどれもMで宣言されていないときである。
-fwarn-orphans
フラグを使っているなら、あなたが孤立モジュールを作ったときにGHCがそれを警告する。他の警告と同様、-fno-warn-orphans
とすればこの警告を無効にすることができ、-Werror
とすればこの警告が発生したときにコンパイルが失敗するようにできる。
孤立モジュールをそれと知るには、--show-iface
モードを使ってそのインタフェースファイルM.hi
を見れば良い。最初の行に[orphan module]
があれば、GHCはそれを孤立モジュールだと判断している。
これは6.2以前の挙動とは異なる。