コンパイル済みコードをロードする

HaskellのソースモジュールをGHCiにロードしたとき、それはふつうバイトコードに変換され、解釈実行器を使って実行される。しかし、解釈実行されるコードをコンパイル済みコードと共に実行することもできる。実際、GHCiは起動するとふつうbaseパッケージのコンパイル済みのものをロードする(そのなかにPureludeがある)。

なぜコンパイル済みのコードを使う必要があるのだろうか。コンパイル済みコードは解釈実行されるコードに比べて大体10倍速いが、一方、生成するのに2倍の時間が掛かる(最適化が有効ならさらに長いかもしれない)のである。そのため、プログラムのあまり変更されない部分をコンパイルしておき、活発に開発されている部分には解釈実行器を使う、ということをするだけの価値がある。

:loadでソースモジュールをロードするとき、GHCiは対応するオブジェクトファイルを探し、可能ならソースコードの解釈実行よりも優先してそれを使う。例えば、A、B、C、Dという四つのモジュールからなるプログラムがあるとしよう。モジュールBとCはどちらもDのみをインポートしていて、AはBとCをインポートしている。

      A
     / \
    B   C
     \ /
      D

Dをコンパイルして、その後プログラム全体をロードすると次のようになる。

Prelude> :! ghc -c D.hs
Prelude> :load A
Compiling B                ( B.hs, interpreted )
Compiling C                ( C.hs, interpreted )
Compiling A                ( A.hs, interpreted )
Ok, modules loaded: A, B, C, D.
*Main>

コンパイラのメッセージ中に、Dについての行がない。これは、ソースファイルとその依存関係が、最後にコンパイルされたときから変更されていないため、コンパイルする必要ないからである。

いつでも、:show modulesコマンドを使って、その時点でロードされているモジュールの一覧を表示することができる。

*Main> :show modules
D                ( D.hs, D.o )
C                ( C.hs, interpreted )
B                ( B.hs, interpreted )
A                ( A.hs, interpreted )
*Main>

ここでDを編集(あるいは編集したふりをする。これにはUnixのtouchコマンドが便利である)すると、コンパイラはオブジェクトファイルを使うことができなくなる。既に古くなっているかもしれないからである。

*Main> :! touch D.hs
*Main> :reload
Compiling D                ( D.hs, interpreted )
Ok, modules loaded: A, B, C, D.
*Main> 

Dがコンパイルされたが、この例ではDのソースは実際には変更されていないので、インタフェースは同じままであり、そのため再コンパイル検査器がA、B、Cを再コンパイルする必要がないと判断したことに注意。

では他のモジュールをコンパイルしてみよう。

*Main> :! ghc -c C.hs
*Main> :load A
Compiling D                ( D.hs, interpreted )
Compiling B                ( B.hs, interpreted )
Compiling C                ( C.hs, interpreted )
Compiling A                ( A.hs, interpreted )
Ok, modules loaded: A, B, C, D.

Cのコンパイル済みの版が使われていない。これはどうしたことかというと、GHCではコンパイル済みのモジュールは別のコンパイル済みのモジュールにしか依存できないのである。この場合は、CがDに依存するが、Dにはオブジェクトファイルがないので、GHCiはCのオブジェクトファイルも利用しなかった。では、Dもコンパイルしてみよう。

*Main> :! ghc -c D.hs
*Main> :reload
Ok, modules loaded: A, B, C, D.

なにも起こらない!これは新たな教訓である。新しくコンパイルされたモジュールは:reloadでは使われない。:loadが必要である。

*Main> :load A
Compiling B                ( B.hs, interpreted )
Compiling A                ( A.hs, interpreted )
Ok, modules loaded: A, B, C, D.

このようなオブジェクトファイルの自動ロードは混乱の原因になることがある。なぜなら、モジュールの最上位の、エクスポートされていない定義をプロンプトの式で使えるのは、そのモジュールが解釈実行されているときだけだからである(2.4.3. プロンプトで実際にスコープにあるのは何かを見よ)。このため、解釈実行器を使ってモジュールをロードするようにGHCiに強制したいと思うことがあるかもしれない。これは、:loadを使うときに、モジュール名またはファイル名の前に*を置くことで実現できる。例を挙げる。

Prelude> :load *A
Compiling A                ( A.hs, interpreted )
*A>

*が使われている場合、GHCiはコンパイル済みオブジェクトコードがあっても無視し、そのモジュールを解釈実行する。既にモジュールをいくつかオブジェクトコードとしてロードしていて、そのうち一つを解釈実行したいなら、全部を再ロードする代わりに、:add *Mを使って、Mが解釈実行されて欲しいことを指定することができる。(これによって別のモジュールも解釈実行されるかもしれないことに注意。これは、コンパイル済みモジュールが解釈実行モジュールに依存できないためである)。

-fobject-codeオプションを使うと、常にあらゆる物をオブジェクトコードにコンパイルし、決してインタプリタを使わないようにすることができる(2.10. GHCi内でオブジェクトコードにコンパイルするを見よ)。

ヒント: GHCはコンパイル済みの版が最新であることが確かな場合しかコンパイル済みオブジェクトファイルを使わないので、大きいプロジェクトを扱っているときは、ときどきghc ––makeを実行してプロジェクト全体をコンパイルし(例えば昼食を食べに行く前にね)、解釈実行器を使って作業を続けるというのが良い方法である。コードを変更すると、変更されたモジュールは解釈実行されるが、プロジェクト中のその他の部分は変わらずコンパイル済みのものが使われる。