4.7. ファイル名と分割コンパイル

この節では、GHCがどんなファイルを見つけ、どんなファイルを作るか、それらのファイルはどこに置かれるか、この振る舞いに影響を与えるオプションは何か、を解説する。

この節は階層的モジュールを念頭において書かれていることに注意。(7.3.5. 階層的モジュールを見よ) 階層的モジュールはHaskell98への拡張で、モジュール名がドット「.」を含むことができるようにする。したがって、非階層的なモジュールは、モジュール名がドットを含まないという特別の場合になる。

パス名に関する慣習はシステムごとに異なる。特に、ディレクトリの区切りはUnixシステムでは「/」であり、Windowsシステムでは「\」である。以降の節では、一貫して「/」をディレクトリ区切りとして用いる。あなたのシステムにおける適切な文字に置き換えて読んでほしい。

4.7.1. Haskellソースファイル

全てのHaskellソースモジュールは専用のファイルに置かれているべきである。

ふつう、ファイル名は、モジュール名をもとにして、ドットをディレクトリ区切りに置き換えたものにされるべきである。例えば、Unixシステムでは、A.B.Cというモジュールは、なんらかの基底ディレクトリからの相対パスでA/B/C.hsというファイルに置かれるべきである。他のモジュールからインポートされる予定がないモジュールには(例えばMain)、どんな名前をつけても構わない。

GHCはソースファイルがASCIIまたはUTF-8のみだと推定し、他のエンコード方式は認識しない。しかし、UTF-8として不正な並びはコメント内部では無視されるので、Latin-1のような他のエンコード方式を、コメント以外の部分がASCIIのみから成ることを条件に使うことができる。

4.7.2. 出力ファイル

ソースファイルをコンパイルするとき、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オプションで直接指定できる。

4.7.3. 探索パス

プログラム中では、import Fooと言うことによってモジュールFooをインポートする。--makeモードとGHCiでは、GHCはFooのソースファイルを探し、そちらを先にコンパイルするようにする。--makeが与えられなかったときは、Fooのインタフェースファイルを探す。これは、あらかじめFooをコンパイルすることで作られていなければならない。どの場合でも、GHCが適切なファイルを見つけるのに使う戦略は同じである。

戦略とはこうである。GHCは探索パスと呼ばれるディレクトリの一覧を保持している。これらのディレクトリ一つずつに対して、basename.extensionを後置し、そのファイルが存在するか調べる。basenameの値はモジュール名のドットをディレクトリ区切り(システムによって「/」または「\」)で置換したものであり、extensionは、--makeモードとGHCiではソース拡張子(hslhs)であり、その他ではhisufである。

例えば、探索パスにd1d2d3の各ディレクトリがある状況で、--makeモードでA.B.Cというモジュールのソースファイルを探しているとしよう。GHCが探すのはd1/A/B/C.hsd1/A/B/C.lhsd2/A/B/C.hs等々である。

デフォルトでは探索パスにはただ一つのディレクトリ.(つまりカレントディレクトリ)が含まれる。以下のオプションを使って、探索パスの内容を追加したり変更したりできる。

-idirs

dirs(コロン区切りの一覧)を探索パスの後ろに連結する。

-i

探索パスを空にする。

以上のことが全てではない。GHCはコンパイル済みライブラリ(パッケージと呼ばれる)からもモジュールを探す。詳細はパッケージについての節(4.9. パッケージ )を見よ。

4.7.4. コンパイルの出力先を変える

-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.oBar.oBumble.oの各オブジェクトファイルは、実行中の機械のアーキテクチャの名前(x86mipsなど)のサブディレクトリに置かれる。

-odirオプションはインタフェースファイルがどこに置かれるかに影響しないことに注意。それには-hidirオプションを使うこと。上の例では、インタフェースファイルは相変わらずparse/Foo.hiparse/Bar.higurgle/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 exportforeign 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

とすればよい。

4.7.5. 中間ファイルをそのままにする

以下のオプションは、ある種の中間ファイルをそのままにしておくのに有用である。通常、これらのファイルはコンパイル終了後に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付きで実行すればどういう一時ファイルが作られているかを見ることができる。

4.7.6. 一時ファイルの場所を変更する

-tmpdir

/tmp(インストールの仕方によっては別の場所かもしれない。とにかく一時ファイルを置くところ)の空き容量が足らなくて困っているなら、-tmpdir <dir>オプションを使って別のディレクトリを指定することができる。例えば、-tmpdir .とすれば、一時ファイルは現在の作業ディレクトリに置かれる。

別の方法として、TMPDIR環境変数を使うこともできる。これを一時ファイルを置くべき場所に設定する。GCCその他のプログラムもTMPDIR変数を尊重する。

さらに良い方法は、GHCをビルドするときにDEFAULT_TMPDIRというmake変数を設定することである。こうすれば、二度とTMPDIRの心配をする必要がない。(ビルド説明書を見よ)

4.7.7. インタフェースファイルに関連するその他のオプション

-ddump-hi

新しいインタフェースを標準出力に表示する。

-ddump-hi-diffs

一般に、新しい.hiが既存のものと同じなら、上書きは行われない。これはmakeとうまく協調する。インタフェースが実際に変更されたとき、これはしばしば知るに値することである。-ddump-hi-diffsが与えられると、GHCは新旧の.hiファイルの差異を報告する。

-ddump-minimal-imports

モジュールをコンパイルするとき、そのモジュールの「最小の」インポート宣言をM.imports(ただしMはモジュール名)というファイルに出力する。.importsファイルが作られるディレクトリは-dumpdirオプションによって制御される。M.hsにあるインポート宣言を、対応する.importsファイルにあるものに置き換えても安全である。こうすることの利点は、(1)「最小の」インポート宣言は全てを明示的にインポートする (2)不要なものは全くインポートしない ことである。この特徴を手で維持するのは非常な苦痛であり得るので、その作業を軽減するためにこのフラグがある。

--show-iface file

インタフェースファイルの中身を人間可読(っぽいよう)な形式で表示する。ただしfileはそのファイルの名前である。4.5. 実行モードを見よ。

4.7.8. 再コンパイル検査器

-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でこれら全てがどうやって動いているかについて読むことができる。

4.7.9. 相互再帰的なモジュールをコンパイルするには

GHCでは相互再帰的なモジュールをコンパイルすることができる。この節ではその方法を説明する。

モジュールのインポート関係が成すグラフにおけるサイクルは全てhs-bootファイルを使って解消しなければならない。A.hsB.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

ここでABをインポートしているが、BA{-# 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. 役割 を見よ。)

4.7.10. makeを使う

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.oFoo.hcFoo.sのどれかついて、最終更新日時がBaz.hiよりも前ならば、そのファイルは古くなっているので更新されなければならない、ということである。更新するということになると、makeはそのためのルールを探す。そして、あらかじめ定義しておいたサフィックスルールのいずれかがうまく働く。これらの依存関係はghcを使って自動的に生成することもできる。4.7.11. 依存関係を生成するを見よ。

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に対して、MXに依存していることを示す行。

    M.o : X.hi
  • 全てのM中のインポート宣言import {-# SOURCE #-} Xに対して、MXに依存していることを示す行。

    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

makefileMakefileの代わりにfileをmakefileとして使う。もしfileが存在しないなら、mkdependHSが作る。我々はしばしば、-dep-makefile .dependとして依存関係を.dependに置き、それをMakefileincludeする、ということを行う。

-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ライブラリも含まれる)。依存関係がパッケージの中身まで再帰的に追跡されることはない。生成されるのは、ホームパッケージのモジュールから、それが直接インポートするパッケージ外のモジュールへの依存性だけである。このオプションは通常、各種のシステムライブラリによってのみ使われる。

4.7.12. 孤立モジュールと孤立インスタンス宣言

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はそれを孤立モジュールだと判断している。

[7]

これは6.2以前の挙動とは異なる。