The third post in the series.
Intro
It’s hard to get into writing code that uses GHC API. The API itself is and the number of various functions and options significantly outnumber the amount of tutorials around.
In this series of blog posts I’ll elaborate on some of the peculiar, interesting problems I’ve encountered during my experience writing code that uses GHC API and also provide various tips I find useful.
I have built for myself a small layer of helper functions that helped me with using GHC API for the interactive-diagrams
project. The source can be found on GitHub and I plan on refactoring the code and releasing it separately.
Today I would like to talk about a different ways of bringing contents of Haskell modules into scope, a process that is necessary for evaluating/interpreting bits of code on-the-fly.
Many of the points I make in this post are actually trivial, but nevertheless I made all of the mistakes I mentioned in this, perhaps post due to my naive approach of quickly diving in and experimenting, instead of reading into the documentation and source code. Now I actually realize that this post should been the first in the series, since it probably deals with more basic (and fundamental) stuff than the previous two posts. But anyway, here it is.
Interpreted modules
Imagine the following situation: we have a Haskell source file with code we want to load dynamically and evaluate. That is a basic task in the GHC API terms but nevertheless there are some caveats. We start with the most basics.
Let us have a file ‘test.hs’ containing the code we want to access:
module Test (test) where
test :: Int
test = 123
The basic way to get the ‘test’ data would be to load ‘Test’ as an interpreted module:
import Control.Applicative
import DynFlags
import GHC
import GHC.Paths
import GhcMonad (liftIO) -- from ghc7.7 and up you can use the usual
-- liftIO from Control.Monad.IO.Class
import Unsafe.Coerce
main = defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
runGhc (Just libdir) $ do
-- we have to call 'setSessionDynFlags' before doing
-- everything else
dflags <- getSessionDynFlags
-- If we want to make GHC interpret our code on the fly, we
-- ought to set those two flags, otherwise we
-- wouldn't be able to use 'setContext' below
setSessionDynFlags $ dflags { hscTarget = HscInterpreted
, ghcLink = LinkInMemory
}
setTargets =<< sequence [guessTarget "test.hs" Nothing]
load LoadAllTargets
-- Bringing the module into the context
setContext [IIModule $ mkModuleName "Test"]
-- evaluating and running an action
act <- unsafeCoerce <$> compileExpr "print test"
liftIO act
The reason that we have to use HscInterpreted and LinkInMemory is that otherwise it would compile test.hs in the current directory and leave test.hi and test.o files, which we would not be able to load in the interpreted mode. setContext, however will try to bring the code in those files first, when looking for the module ‘Test’
dan@aquabox
[0] % ghc --make target.hs -package ghc
[1 of 1] Compiling Main ( target.hs, target.o )
Linking target ...
dan@aquabox
[0] % ./target
123
Let’s try something fancier like printing a list of integers, one-by-one.
main = defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
setSessionDynFlags $ dflags { hscTarget = HscInterpreted
, ghcLink = LinkInMemory
}
setTargets =<< sequence [guessTarget "test.hs" Nothing]
load LoadAllTargets
-- Bringing the module into the context
setContext [IIModule $ mkModuleName "Test"]
-- evaluating and running an action
act <- unsafeCoerce <$> compileExpr "forM_ [1,2,test] print"
liftIO act
But when we try to run it..
dan@aquabox
[0] % ./target
target: panic! (the 'impossible' happened)
(GHC version 7.6.3 for x86_64-apple-darwin):
Not in scope: `forM_'
Please report this as a GHC bug:
http://www.haskell.org/ghc/reportabug
Hm, it looks like we need to bring Control.Monad
into the scope.
This brings us to the next section.
Package modules
Naively, we might want to load Control.Monad
in a similar fashion as we did with loading test.hs
main = defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
setSessionDynFlags $ dflags { hscTarget = HscInterpreted
, ghcLink = LinkInMemory
}
setTargets =<< sequence [ guessTarget "test.hs" Nothing
, guessTarget "Control.Monad" Nothing]
load LoadAllTargets
-- Bringing the module into the context
setContext [IIModule $ mkModuleName "Test"]
-- evaluating and running an action
act <- unsafeCoerce <$> compileExpr "forM_ [1,2,test] print"
liftIO act
Our attempt fails:
dan@aquabox
[0] % ./target
target: panic! (the 'impossible' happened)
(GHC version 7.6.3 for x86_64-apple-darwin):
module `Control.Monad' is a package module
Please report this as a GHC bug:
http://www.haskell.org/ghc/reportabug
Huh, what? I thought guessTarget
works on all kinds of modules.
Well, it does. But it doesn’t “load the module”, it merely sets it as the target for compilation, basically it (together with load LoadAllTargets
) does what ghc --make
does. And surely it doesn’t make much sense to ghc --make Control.Monad
when Control.Monad
is a module from the base package. What we need to do instead is to bring the compiled Control.Monad
module into scope. Luckily it’s not very hard to do with the help of the simpleImportDecl :: ModuleName -> ImportDecl name
:
main = defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
setSessionDynFlags $ dflags { hscTarget = HscInterpreted
, ghcLink = LinkInMemory
}
setTargets =<< sequence [ guessTarget "test.hs" Nothing ]
load LoadAllTargets
-- Bringing the module into the context
setContext [ IIModule . mkModuleName $ "Test"
, IIDecl
. simpleImportDecl
. mkModuleName $ "Control.Monad" ]
-- evaluating and running an action
act <- unsafeCoerce <$> compileExpr "forM_ [1,2,test] print"
liftIO act
And we can run it
dan@aquabox
[0] % ./target
1
2
123
Compiled modules
What we have implemented so far corresponds to the :load*
command in GHCi, which gives us the full access to the source code of the program. To illustrate this let’s modify our test file:
module Test (test) where
test :: Int
test = 123
test2 :: String
test2 = "Hi"
Now, if we try to load that file as an interpreted module and evaluate test2
nothing will stop us from doing so.
dan@aquabo
[0] % ./target-interp
(123,"Hi")
To use the compiled module we have to bring Test
into the context the same way we dealt with Control.Monad
main = defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
setSessionDynFlags $ dflags { hscTarget = HscInterpreted
, ghcLink = LinkInMemory
}
setTargets =<< sequence [ guessTarget "Test" Nothing ]
load LoadAllTargets
-- Bringing the module into the context
setContext [ IIDecl $ simpleImportDecl (mkModuleName "Test")
, IIDecl $ simpleImportDecl (mkModuleName "Prelude")
]
printExpr "test"
printExpr "test2"
printExpr :: String -> Ghc ()
printExpr expr = do
liftIO $ putStrLn ("-- Going to print " ++ expr)
act <- unsafeCoerce <$> compileExpr ("print (" ++ expr ++ ")")
liftIO act
Output:
dan@aquabox : ~/snippets/ghcapi
[0] % ./target
-- Going to print test
123
-- Going to print test2
target: panic! (the 'impossible' happened)
(GHC version 7.6.3 for x86_64-apple-darwin):
Not in scope: `test2'
Perhaps you meant `test' (imported from Test)
Please report this as a GHC bug: http://www.haskell.org/ghc/reportabug
Note: I had to bring the Prelude
into context this time, like a regular module. I tried setting the ideclImplicit
option in ImportDecl
, but it didn’t work for some reason. Maybe it actually supposed to do not what I think it supposed to do, but something else.
Outro
So, that is it, we have managed to dynamically load Haskell source code and evaluate it. I can only refer you to the GHC haddocs for specific functions that we used in this post, most of them contain way more options that we used and they might prove to be useful to you.