eval, and its siblings, provide a mechanism to compile and run Haskell code at runtime, in the form of a String. The general framework is that the string is used to create a plugin source file, which is compiled and loaded, and type checked against its use. The resulting value is returned to the caller. It resembles a runtime metaprogramming run operator for closed code fragments.
import System.Eval.Haskell eval :: Typeable a => String -> [Import] -> IO (Maybe a) eval_ :: Typeable a => String -- code to compile -> [Import] -- any imports -> [String] -- extra ghc flags -> [FilePath] -- extra package.conf files -> [FilePath] -- include search paths -> IO (Either [String] (Maybe a))
eval takes a string, and a list of import module names, and returns a Maybe value. Nothing means the code did not compile. Just v gives you v, the result of evaluating your code. It is interesting to note that eval has the type of an interpreter. The Typeable constraint is used to type check the evaluated code when it is loaded, using dynload. As usual, eval_ is a version of eval that lets you pass extra flags to ghc and to the dynamic loader.
The existing Data.Dynamic library requires that only monomorphic values are Typeable, so in order to evaluate polymorphic functions you need to wrap them up using rank-N types. Some examples:
import System.Eval.Haskell main = do i <- eval "1 + 6 :: Int" [] :: IO (Maybe Int) if isJust i then putStrLn (show (fromJust i)) else return ()
When executed this program calls eval to compile and load the simple arithmetic expression, returning the result, which is displayed. If the value loaded is not of type Int, dynload will throw an exception.
The following example, due to Manuel Chakravarty, shows how to evaluate a polymorphic function. Polymorphic values are not easily made dynamically typeable, but this example shows how to do it. The module Poly is imported as the second argument, providing the type of the polymorphic function:
And the type of Fn:import Poly import System.Eval.Haskell main = do m_f <- eval "Fn (\\x y -> x == y)" ["Poly"] when (isJust m_f) $ do let (Fn f) = fromJust m_f putStrLn $ show (f True True) putStrLn $ show (f 1 2)
When executed, this program produces:{-# OPTIONS -fglasgow-exts #-} module Poly where import AltData.Typeable data Fn = Fn {fn :: forall t. Eq t => t -> t -> Bool} instance Typeable Fn where typeOf _ = mkAppTy (mkTyCon "Poly.Fn") []
$ ./a.out True False
We thus get dynamically typeable polymorphic functions.
unsafeEval :: String -> [Import] -> IO (Maybe a) unsafeEval_ :: String -> [Import] -> [String] -> [FilePath] -> IO (Either [String] a)
Wrapping up polymorphic values can be annoying, so we provide a unsafeEval function for people who like to live on the edge, which dispenses with dynamic typing, relying instead on the application to provide the correct type annotation on the call to eval. If the type loaded by eval is wrong, unsafeEval will crash. However, its lets us remove some restrictions on what types can be evaluated, which can be useful.
unsafeEval_ lets the application have full control over the import environment and load flags to the eval call, which is useful for applications that wish to script themselves, and require specific modules and packages to be in scope in the eval-generated module.
This example maps a toUpper over a list:
import Eval.Haskell main = do s <- unsafeEval "map toUpper \"haskell\"" ["Data.Char"] when (isJust s) $ putStrLn (fromJust s)
And here we evaluate a lambda abstraction, applying the result to construct a tuple. Note the type information that must be supplied in order for Haskell to type the usage of fn:
import System.Eval.Haskell main = do fn <- unsafeEval "(\\(x::Int) -> (x,x))" [] :: IO (Maybe (Int -> (Int,Int))) when (isJust fn) $ putStrLn $ show $ (fromJust fn) 7
hs-plugins proves the following utilities for use with eval:
mkHsValues is a helper function for converting Data.Maps of names and values into Haskell code. It relies on the assumption that the passed values' Show instances produce valid Haskell literals (this is true for all prelude types). It's type is as follows:
mkHsValues :: (Show a) => Data.Map String a -> String
A preliminary binding to eval has been implemented to allow C (and Objective C) programs access to the evaluator. Foreign bindings to the compilation manager and dynamic loader are yet to be implemented, but shouldn't be too hard. An foreign binding to a Haskell module that wraps up calls to make and load would be fairly trivial.
At the moment we have an ad-hoc binding to eval, so that C programmers who know the type of value that will be returned by Haskell can call the appropriate hook into the evaluator. If they get the type wrong, a nullPtr will be returned (so calling Haskell is still typesafe). The foreign bindings to eval all return NULL if an error occurred, otherwise a pointer to the value is returned.
foreign export ccall hs_eval_b :: CString -> IO (Ptr CInt) foreign export ccall hs_eval_c :: CString -> IO (Ptr CChar) foreign export ccall hs_eval_i :: CString -> IO (Ptr CInt) foreign export ccall hs_eval_s :: CString -> IO CString
An example C program for compiling and evaluating Haskell code at runtime follows. This program calculates a fibonacci number, returning it as a CString to the C program:
#include "EvalHaskell.h" #include <stdio.h> int main(int argc, char *argv[]) { char *p; hs_init(&argc, &argv); p = hs_eval_s("show $ let fibs = 1:1:zipWith (+) fibs (tail fibs) in fibs !! 20"); if (p != NULL) printf("%s\n",p); else printf("error in code\n"); hs_exit(); return 0; }
Be careful if you're calling eval from a forked thread. This can introduce races between the thread and the forked process used by eval to compile its code.
The low level interface is the binding to GHC's Linker.c. Therefore, hs-plugins only works on platforms with a working GHCi. This library is based on code from André Pang's runtime loader. The low level interface is as follows:
initLinker start the linker up
loadObject load a vanilla .o
loadPackage load a GHC library and its cbits
loadShared load a .so object file
resolveObjs and resolve symbols
Additionally, Hi.Parser provides an interface to a GHC .hi file parser. Currently we only parse just the dependency information, import and export information from .hi files, but all the code is there for an application to extract other information from .hi files.