hint: A Haskell interpreter built on top of the GHC API

[ bsd3, compilers-interpreters, language, library ] [ Propose Tags ]

This library defines an Interpreter monad. It allows to load Haskell modules, browse them, type-check and evaluate strings with Haskell expressions and even coerce them into values. The library is thread-safe and type-safe (even the coercion of expressions to values). It is, essentially, a huge subset of the GHC API wrapped in a simpler API.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1, 0.2, 0.2.1, 0.2.2, 0.2.4, 0.2.4.1, 0.3.0.0, 0.3.1.0, 0.3.2.0, 0.3.2.1, 0.3.2.2, 0.3.2.3, 0.3.3.0, 0.3.3.1, 0.3.3.2, 0.3.3.3, 0.3.3.4, 0.3.3.5, 0.3.3.6, 0.3.3.7, 0.4.0.0, 0.4.1.0, 0.4.2.0, 0.4.2.1, 0.4.2.2, 0.4.2.3, 0.4.3, 0.5.0, 0.5.1, 0.5.2, 0.6.0, 0.7.0, 0.8.0, 0.9.0, 0.9.0.1, 0.9.0.2, 0.9.0.3, 0.9.0.4, 0.9.0.5, 0.9.0.6, 0.9.0.7, 0.9.0.8 (info)
Change log CHANGELOG.md
Dependencies base (>=4 && <5), containers, directory, exceptions (>=0.10 && <0.11), filepath, ghc (>=8.4 && <9.9), ghc-boot, ghc-paths, random, temporary, transformers, unix (>=2.2.0.0) [details]
License BSD-3-Clause
Author The Hint Authors
Maintainer "Samuel Gélineau" <gelisam@gmail.com>
Category Language, Compilers/Interpreters
Home page https://github.com/haskell-hint/hint
Source repo head: git clone https://github.com/haskell-hint/hint
Uploaded by gelisam at 2023-10-14T18:32:50Z
Distributions Arch:0.9.0.6, Debian:0.9.0.3, Fedora:0.9.0.7, FreeBSD:0.4.2.3, LTSHaskell:0.9.0.8, NixOS:0.9.0.8, Stackage:0.9.0.8
Reverse Dependencies 39 direct, 194 indirect [details]
Downloads 75277 total (211 in the last 30 days)
Rating 2.5 (votes: 4) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for hint-0.9.0.8

[back to package description]

hint Hackage Build Status

This library defines an Interpreter monad within which you can interpret strings like "[1,2] ++ [3]" into values like [1,2,3]. You can easily exchange data between your compiled program and your interpreted program, as long as the data has a Typeable instance.

You can choose which modules should be in scope while evaluating these expressions, you can browse the contents of those modules, and you can ask for the type of the identifiers you're browsing.

Example

{-# LANGUAGE LambdaCase, ScopedTypeVariables, TypeApplications #-}
import Control.Exception (throwIO)
import Control.Monad (when)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Writer (execWriterT, tell)
import Data.Foldable (for_)
import Data.List (isPrefixOf)
import Data.Typeable (Typeable)
import qualified Language.Haskell.Interpreter as Hint

-- |
-- Interpret expressions into values:
--
-- >>> eval @[Int] "[1,2] ++ [3]"
-- Right [1,2,3]
--
-- Send values from your compiled program to your interpreted program by
-- interpreting a function:
--
-- >>> Right f <- eval @(Int -> [Int]) "\\x -> [1..x]"
-- >>> f 5
-- [1,2,3,4,5]
eval :: forall t. Typeable t
     => String -> IO (Either Hint.InterpreterError t)
eval s = Hint.runInterpreter $ do
  Hint.setImports ["Prelude"]
  Hint.interpret s (Hint.as :: t)

-- |
-- >>> :{
-- do Right contents <- browse "Prelude"
--    for_ contents $ \(identifier, tp) -> do
--      when ("put" `isPrefixOf` identifier) $ do
--        putStrLn $ identifier ++ " :: " ++ tp
-- :}
-- putChar :: Char -> IO ()
-- putStr :: String -> IO ()
-- putStrLn :: String -> IO ()
browse :: Hint.ModuleName -> IO (Either Hint.InterpreterError [(String, String)])
browse moduleName = Hint.runInterpreter $ do
  Hint.setImports ["Prelude", "Data.Typeable", moduleName]
  exports <- Hint.getModuleExports moduleName
  execWriterT $ do
    for_ exports $ \case
      Hint.Fun identifier -> do
        tp <- lift $ Hint.typeOf identifier
        tell [(identifier, tp)]
      _ -> pure ()  -- skip datatypes and typeclasses

Check example.hs for a longer example (it must be run from hint's base directory).

Limitations

Importing a module from the current package is not supported. It might look like it works on one day and then segfault the next day. You have been warned.

To work around this limitation, move those modules to a separate package. Now the part of your code which calls hint and the code interpreted by hint can both import that module.

It is not possible to exchange a value whose type involves an implicit kind parameter. This includes type-level lists. To work around this limitation, define a newtype wrapper which wraps the type you want.

It is possible to run the interpreter inside a thread, but on GHC 8.8 and below, you can't run two instances of the interpreter simultaneously.

GHC must be installed on the system on which the compiled executable is running. There is a workaround for this but it's not trivial.

The packages used by the interpreted code must be installed in a package database, and hint needs to be told about that package database at runtime.

The most common use case for package databases is for the interpreted code to have access to the same packages as the compiled code (but not compiled code itself). The easiest way to accomplish this is via a GHC environment file, and the easiest way to generate a GHC environment file is via cabal. Compile your code using cabal build --write-ghc-environment-files=always; this will create a file named .ghc.environment.<something> in the current directory. At runtime, hint will look for that file in the current directory.

For more advanced use cases, you can use unsafeRunInterpreterWithArgs to pass arguments to the underlying ghc library, such as -package-db to specify a path to a package database, or -package-env to specify a path to a GHC environment file.