Dynamic Compilation in Haskell GHC API Error
Asked Answered
W

2

7

I have been trying to get some basic dynamic code compilation working using the GHC API by following a tutorial found here.

This code:

import GHC
import GHC.Paths
import DynFlags
import Unsafe.Coerce

main :: IO ()
main =
    defaultErrorHandler defaultDynFlags $ do
      func <- runGhc (Just libdir) $ do
        dflags <- getSessionDynFlags
        setSessionDynFlags dflags
        target <- guessTarget "Test.hs" Nothing
        addTarget target
        r <- load LoadAllTargets
        case r of
          Failed -> error "Compilation failed"
          Succeeded -> do
            m <- findModule (mkModuleName "Test") Nothing
            setContext [] [m]
            value <- compileExpr ("Test.print")
            do let value' = (unsafeCoerce value) :: String -> IO ()
               return value'
      func "Hello"
      return ()

Should get the print function from another file called Test.hs, load it and run its print function.

I compile the code with ghc version 7.4.1 using the command:

ghc -package ghc --make Api.hs

But receive the following error:

Api.hs:8:25:
    Couldn't match expected type `Severity' with actual type `Settings'
    Expected type: LogAction
      Actual type: Settings -> DynFlags
    In the first argument of `defaultErrorHandler', namely
      `defaultDynFlags'
    In the expression: defaultErrorHandler defaultDynFlags

What am I doing wrong? I have checked the GHC API docs but am not well versed enough in this kind of thing to understand most of it.

Wystand answered 8/10, 2012 at 9:45 Comment(0)
S
6

The tutorial is out of date. In ghc-7.0.* and previous, the type of defaultErorHandler was

defaultErrorHandler :: (ExceptionMonad m, MonadIO m) => DynFlags -> m a -> m a

and defaultDynFlags was just a value.

As of ghc-7.2.*, the type of defaultErrorHandler is

defaultErrorHandler :: (ExceptionMonad m, MonadIO m) => LogAction -> m a -> m a

defaultDynFlags is a function

defaultDynFlags :: Settings -> DynFlags

and LogAction is a synonym

type LogAction = Severity -> SrcSpan -> PprStyle -> Message -> IO ()

In 7.6, it has changed again, we now have

defaultErrorHandler :: (ExceptionMonad m, MonadIO m) => FatalMessager -> FlushOut -> m a -> m a

with

type FatalMessager = String -> IO ()

and FlushOut being a newtype wrapper around IO ().

I'm not very familiar with the GHC Api (a too fast-moving target for me), so I'm not sure how the working code should look like, but for the 7.2 and 7.4 series, the first argument to defaultErrorHandler should probably be defaultLogAction.

Also the type of setContext has changed, I don't know if what I have does what you want, but it compiles (with 7.4.2; but you also need the ghc-paths package in addition to ghc for the GHC.Paths module) - I haven't tried to run it, though.

import GHC
import GHC.Paths
import DynFlags
import Unsafe.Coerce

main :: IO ()
main =
    defaultErrorHandler defaultLogAction $ do
      func <- runGhc (Just libdir) $ do
        dflags <- getSessionDynFlags
        setSessionDynFlags dflags
        target <- guessTarget "Test.hs" Nothing
        addTarget target
        r <- load LoadAllTargets
        case r of
          Failed -> error "Compilation failed"
          Succeeded -> do
            m <- findModule (mkModuleName "Test") Nothing
            setContext [IIModule m]
            value <- compileExpr ("Test.print")
            do let value' = (unsafeCoerce value) :: String -> IO ()
               return value'
      func "Hello"
      return ()
Streit answered 8/10, 2012 at 14:30 Comment(3)
Thanks, this seems to compile fine. However, when I run it, it works fine the first time- It compiles the Test.hs file and runs the print function to print out the word "Hello". However, If I try to run it a second time, I get the error message "mkTopLevEnv: not interpreted main:Test"Wystand
It seems that it doesn't work with compiled modules. If you delete the .hi and .o files between runs, it would probably work multiple times. But that's somewhat inconvenient, isn't it? So you'd need a way that can handle compiled modules. Unfortunately, I don't know the API well enough to be able to tell you how to do that. Could be your next question here.Streit
I just solved this after much trying: Two things helped me: (1) ´do {dflags <- getSessionDynFlags;setSessionDynFlags $ dflags {hscTarget = HscInterpreted,ghcLink = LinkInMemory}´ to make it reload the context each run and (2) Use IIDecl instead of IIModule to make it less error prone and safe. Also, parseImportDecl is nice to get the IIDecl. Ask me for more info if needed, right now I need sleep.Hayton
H
2

Here is a complete example for dynamic loading, also hosted here:

DynLoad.hs

-----------------------------------------------------------------------------
-- | Example for loading Haskell source code dynamically using the GHC api
-- Useful links:
-- GHC api:
-- http://www.haskell.org/ghc/docs/latest/html/libraries/ghc/GHC.html
-- Wiki:
-- http://www.haskell.org/haskellwiki/GHC/As_a_library
-----------------------------------------------------------------------------
module DynLoad where

import GHC
import GhcMonad (liftIO)
import GHC.Paths (libdir)
import Name (getOccString)
import Data.Dynamic (fromDyn)

-- |  List all exports of this module
--    and evaluate a symbol from a module DynTest 
main = 
  runGhc (Just libdir) $ do
    putString ":::Display exports of modules:::"
    modSums <- initSession ["DynLoad","DynTest"]
    let thisModSum = head modSums
    exports <- listExports thisModSum
    mapM_ putString exports

    putString ":::Evaluate a name from module DynTest:::"
    importDecl_RdrName <- parseImportDecl "import DynTest as D"
    setContext [IIDecl importDecl_RdrName]
    dynVal <- dynCompileExpr "D.aString"
    liftIO $ print $ (fromDyn dynVal "nope-nothing")

-- | Init interactive session and load modules
initSession modStrNames = do
  dflags <- getSessionDynFlags
  setSessionDynFlags $ dflags {
    hscTarget = HscInterpreted
    , ghcLink   = LinkInMemory
    }
  targets <- mapM
              (\modStrName -> do
                  putString modStrName
                  target <- guessTarget ("*"++modStrName++".hs") Nothing
                  return target
              ) modStrNames
  setTargets targets
  load LoadAllTargets
  modSums <- mapM
              (\modStrName -> do
                  putString modStrName
                  modSum <- getModSummary $ mkModuleName modStrName
                  return $ ms_mod modSum
              ) modStrNames
  return modSums

-- | List exported names of this or a sibling module
listExports mod = do
  maybeModInfo <- getModuleInfo mod
  case maybeModInfo of
    (Just modInfo) -> do
      let expNames = modInfoExports modInfo
          expStrNames = map getOccString expNames
      return expStrNames
    _ -> return []

-- | Util for printing
putString = liftIO . putStrLn

And here is an example file to load:

DynTest.hs

module DynTest where


aString = "Hello"
Hayton answered 22/2, 2014 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.