3.3. Loading compiled code

When you load a Haskell source module into GHCi, it is normally converted to byte-code and run using the interpreter. However, interpreted code can also run alongside compiled code in GHCi; indeed, normally when GHCi starts, it loads up a compiled copy of package std, which contains the Prelude and standard libraries.

Why should we want to run compiled code? Well, compiled code is roughly 10x faster than interpreted code, but takes about 2x longer to produce (perhaps longer if optimisation is on). So it pays to compile the parts of a program that aren't changing very often, and use the interpreter for the code being actively developed.

When loading up source files with :load, GHCi looks for any corresponding compiled object files, and will use one in preference to interpreting the source if possible. For example, suppose we have a 4-module program consisting of modules A, B, C, and D. Modules B and C both import D only, and A imports both B & C:

      A
     / \
    B   C
     \ /
      D

We can compile D, then load the whole program, like this:

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

In the messages from the compiler, we see that it skipped D, and used the object file D.o. The message Skipping module indicates that compilation for module isn't necessary, because the source and everything it depends on is unchanged since the last compilation.

If we now modify the source of D (or pretend to: using Unix command touch on the source file is handy for this), the compiler will no longer be able to use the object file, because it might be out of date:

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

Note that module D was compiled, but in this instance because its source hadn't really changed, its interface remained the same, and the recompilation checker determined that A, B and C didn't need to be recompiled.

So let's try compiling one of the other modules:

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

We didn't get the compiled version of C! What happened? Well, in GHCi a compiled module may only depend on other compiled modules, and in this case C depends on D, which doesn't have an object file, so GHCi also rejected C's object file. Ok, so let's also compile D:

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

Nothing happened! Here's another lesson: newly compiled modules aren't picked up by :reload, only :load:

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

HINT: since GHCi will only use a compiled object file if it can sure that the compiled version is up-to-date, a good technique when working on a large program is to occasionally run ghc --make to compile the whole project (say before you go for lunch :-), then continue working in the interpreter. As you modify code, the new modules will be interpreted, but the rest of the project will remain compiled.