7  Eval.Haskell

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.

Interface

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:

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)

And the type of Fn:
{-# 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") []

When executed, this program produces:
$ ./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

7.1  Utilities for use with eval

hs-plugins proves the following utilities for use with eval:

7.2  Foreign Eval

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;
}

7.3  Notes

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.

8  RTS Binding

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:

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.