4  Overview

hs-plugins is a library for compiling and loading Haskell code into a program at runtime. It allows you to write Haskell code (which may be spread over multiple modules), and have an application (implemented in any language with a Haskell FFI binding, including Haskell) load and use your code at runtime.

hs-plugins provides 3 major features:

The dynamic loader loads objects into the address space of an application, along with any dependencies the plugin may have. The loader is a binding to the GHC runtime system’s dynamic linker, which does single object loading. GHC also performs the necessary linking of new objects into the running process. On top of the GHC loader is a Haskell layer that arranges for module and package dependencies to be found prior to loading individual modules.

The compilation manager is a make-like system for compiling Haskell source code into a form suitable for loading. While plugins are normally thought of as strictly object code, there are a variety of scenarios where it is desirable to be able to inspect the source code of a plugin, or to be able to recompile a plugin at runtime. The compilation manager fills this role. It is particularly useful in the implementation of eval.

The evaluator, eval, utilizes the loader and compilation manager. When passed a string containing a Haskell expression, it compiles the string to object code, loads the result, and returns a Haskell value representing the compiled string to the caller. It can be considered a Haskell interpreter, implemented as a library, and can be used to embed Haskell evaluation facilities in an application.

5  Dynamic Loader

The interface to the hs-plugins library can be divided into a number of sections representing the functional units of the library. Additionally, depending on the level of trust the application places in the plugins, a variety of additional checks can be made on the plugin as it is loaded. The levels of type safety possible are summarised at the end of Section sec:compilation-manger section. The dynamic loader is available by using -package plugins.

Interface

import System.Plugins

load :: FilePath 
     -> [FilePath] 
     -> [PackageConf] 
     -> Symbol 
     -> IO (LoadStatus a)

load_ :: FilePath 
      -> [FilePath] 
      -> Symbol 
      -> IO (LoadStatus a)

data LoadStatus a
    = LoadSuccess Module a 
    | LoadFailure Errors

Example:
do mv <- load "Plugin.o" ["api"] [] "resource"
   case mv of
	LoadFailure msg -> print msg
	LoadSuccess _ v -> return v

This is the basic interface to the dynamic loader. Load the object file specified by the first argument into the address space (the library will preload any module or package dependencies). The second argument is an include path to any additional objects to load (possibly the API of the plugin). The third argument is a list of paths to any user-defined package.conf files, specifying packages unknown to the GHC package system. Symbol is a string specifying the symbol name you wish to lookup. load returns a LoadStatus value representing failure, or an abstract representation of the module (for calls to unload or reload) with the symbol as a Haskell value. The value returned must be given an explicit type signature, or provided with appropriate type constraints such that GHC can determine the expected type returned by load.

load_ is provided for the common situation where no user-defined package.conf files are required.

dynload :: Typeable a    
        => FilePath  
        -> [FilePath]
        -> [PackageConf]
        -> Symbol
        -> IO (LoadStatus a)

Example:
do mv <- dynload "Plugin.o" ["api"] ["plugins.conf.inplace"] "resource"
   case mv of
	LoadFailure msg -> print msg
	LoadSuccess _ v -> putStrLn v

dynload is a safer form of load. It uses dynamic types to perform a check on the value returned by load at runtime, to ensure that it has the type the application expects it to have.

In order to use dynload, the symbol the plugin exports must be of type AltData.Dynamic:Dynamic. (See the AltData library distributed with hs-plugins, and the hs-plugins examples/dynload directory. References to Typeable and Dynamic refer to the hs-plugins reimplementation of these libraries. AltData.Dynamic is used at the moment, as there is a limitation in the existing Data.Dynamic library in the presence of dynamic loading).

The value wrapped up in the Dynamic must be an instance of AltData.Typeable. If the value exported by the plugin is of type Dynamic, and the value wrapped by the Dynamic does not match the type expected of it by the application, dynload will return Nothing, indicating that the plugin is not typesafe with respect to the application. If the value passes the typecheck, dynload will return LoadSuccess. If the value exported by the plugin is not of type Dynamic, dynload will crash—this is a limitation of the existing Dynamic library, it can only type-check Dynamic values. Additionally, Data.Dynamic is limited to monomorphic types, or must be wrapped inside a rank-N type to hide the polymorphism from the typechecker. This is a bit cumbersome. An alternative typesafe load is available via the pdynload interface, which is able to enforce the type of the plugin using GHC’s type inference mechanism, and is not restricted in its expressiveness (at the cost of greater load times):

pdynload :: FilePath
         -> [FilePath]
         -> [PackageConf]
         -> Type
         -> Symbol
         -> IO (LoadStatus a)

pdynload_ :: FilePath
          -> [FilePath]
          -> [PackageConf]
          -> [Arg]
          -> Type
          -> Symbol
          -> IO (LoadStatus a)

Example:
do v <- pdynload "Plugin.o" ["api"] [] "API.Interface" "resource"
   case v of
	LoadSuccess _ a  -> putStrLn "yay!"
	_                -> putStrLn "type error"

pdynload is a replacement for dynload, which provides a solution to the various problems caused by the existing dynamics library in Haskell. Rather than use normal dynamics, which constrain us to monomorphic types only (or rank-N types), it instead uses GHC’s type inference to unify the plugin’s export value with that provided by the api (via its .hi file). It is a form of staged type inference for module interfaces, allowing plugins to use any type definable in Haskell. pdynload is like dynload, but requires a new Type argument. This can be considered a type annotation on the value the plugin should be constrained to.

Prior to loading the object, pdynload generates a tiny Haskell source file containing, for example:

module APITypeConstraint where
import qualified API
import qualified Plugin

_ = Plugin.resource :: API.Interface

It then calls GHC’s type checker on this file, which runs the full Haskell type inference machinery. If the file typecheckes, then the plugin type is correct, and the plugin is safe to load, otherwise it is an error.

Because we use the full Haskell type checker, we can have a form of dynamic typechecking, on any type expressable in Haskell. A plugin’s value may, for example, have class constraints – something not checkable using the standard Dyanmic type. The cost is that pdynload is roughly 46% slower than an unchecked load.

The type of the plugin’s resource field must be equivalent to the Type. There are some restrictions on the arguments that may be passed to pdynload. Currently, we require:

For example, pdynload "API2.o" ["./"] [] "API.PluginAPI" "doAction" generates:

module <temp-generated-name> where
import qualified API    -- comes from API.PluginAPI argument
import qualified API2   -- comes from API2.o argument
_ = API2.doAction :: API.PluginAPI

unload :: Module -> IO ()

unloadAll :: Module -> IO ()

Unload an object, but not its dependencies from the address space. unloadAll performs cascading unloading of a module and its dependencies.

reload :: Module -> Symbol -> IO (LoadStatus a)

Unload, and then reload a module that must have been previously loaded. Doesn’t reload the dependencies. reload is useful in conjunction with make—a call to reload can be performed if make has recompiled the plugin source.

Additionally, some support is provided to manipulation of libraries of Haskell modules (usually known as packages):

loadPackage     :: String -> IO ()

unloadPackage   :: String -> IO ()

loadPackageWith :: String -> [PackageConf] -> IO ()

loadPackage explcitly pulls in a library (which must be visible in the current package namespace. unloadPackage unloads it. loadPackageWith behaves like loadPackage, but you are able to supply extra package.confs to augment the library search path.

Examples:

do loadPackageWith "yi" ["yi.conf"]
   unloadPackage "yi-0.1"