HsLua provides the glue to use Lua from within a Haskell app, or the other way around. It provides foreign function interace (FFI) bindings, helper functions, and as well as utilities.
Lua is a small, well-designed, embeddable scripting language. It has become the de-facto default to make programs extensible and is widely used everywhere from servers over games and desktop applications up to security software and embedded devices. This package provides Haskell bindings to Lua, enable coders to embed the language into their programs, and to thereby make them scriptable.
HsLua ships with batteries included and includes the most recent Lua version (i.e., Lua 5.3.5). Cabal flags make it easy to compile against a system-wide Lua installation.
When to use it
You should give HsLua a try if you
- want a ready-made interface to Lua;
- are looking for a way to use pre-existing Lua libraries with your Haskell program; or
- need to expose complex Haskell functions to Lua.
HsLua exposes most of Lua’s C API via Haskell functions. It offers improved type-safety when compared to the raw C functions, while also translating Lua errors to Haskell exceptions. Furthermore, HsLua provides type-classes which make interacting with Lua straight-forward and safe.
When not to use it
HsLua is a bad for applications requiring raw speed. Combining two garbage collecting language with different error-conventions requires some acrobatics. HsLua is flexible and (relatively) safe, but this comes at the price of reduced performance. This doesn’t matter for most use-cases, but don’t use HsLua just because Haskell is fast. Sometimes, coding in C and importing the external functions via the Haskell FFI is the better way.
Interacting with Lua
HsLua provides the
Lua type to define Lua operations. The operations
are executed by calling
run. A simple “Hello, World” program, using
import Foreign.Lua as Lua main :: IO () main = Lua.run prog where prog :: Lua () prog = do Lua.openlibs -- load Lua libraries so we can use 'print' Lua.callFunc "print" "Hello, World!"
The Lua stack
Lua’s API is stack-centered: most operations involve pushing values to
the stack or receiving items from the stack. E.g., calling a function is
performed by pushing the function onto the stack, followed by the
function arguments in the order they should be passed to the function.
The API function
call then invokes the function with given numbers of
arguments, pops the function and parameters of the stack, and pushes the
,----------. | arg 3 | +----------+ | arg 2 | +----------+ | arg 1 | +----------+ ,----------. | function | call 3 1 | result 1 | +----------+ ===========> +----------+ | | | | | stack | | stack | | | | |
Manually pushing and pulling arguments can become tiresome, so HsLua
makes function calling simple by providing
callFunc. It uses
type-magic to allow different numbers of arguments. Think about it as
having the signature
callFunc :: String -> a1 -> a2 -> … -> res
where the arguments
a1, a2, … must be of a type which can be pushed to
the Lua stack, and the result-type
res must be constructable from a
value on the Lua stack.
Getting values from and to the Lua stack
Conversion between Haskell and Lua values is governed by two type classes:
-- | A value that can be read from the Lua stack. class Peekable a where -- | Check if at index @n@ there is a convertible Lua value and -- if so return it. Throws a @'LuaException'@ otherwise. peek :: StackIndex -> Lua a
-- | A value that can be pushed to the Lua stack. class Pushable a where -- | Pushes a value onto Lua stack, casting it into meaningfully -- nearest Lua type. push :: a -> Lua ()
Many basic data types have instances for these type classes. New
instances can be defined for custom types using the functions in
Foreign.Lua.Core (also exported in
HsLua keeps error handling simple and intuitive for users; all of the below should be abstracted away, and users should not have to concern themselves with the details described below.
Errors and the call stack
The method used for error handling by Lua is build around the
longjmp C functions. This is simple and powerful, but poses some
serious problems when combined with Haskell. The
setjmp function can
jump upwards on the call-stack, possibly skipping frames belonging to
the Haskell runtime system (RTS). This has the potential to confuse the
RTS, resulting in a program crash. Skipping Haskell frames while
unwinding the stack thus has to be avoided at all costs.
We can call Haskell from Lua which calls Lua again etc. At each language boundary we have to check for errors and propagate them properly to the next level in stack. Hslua does this for you, both when returning from Lua to Haskell, and when calling from Haskell into Lua.
HsLua must translate errors
Let’s say we have this call stack: (stack grows upwards)
Haskell function Lua function Haskell program
and we want to report an error in the top-most Haskell function. We
lua_error from the Lua C API, because it uses
which means it skips layers of abstractions, including the Haskell RTS.
There’s no way to prevent this
lua_pcall sets the jump
target, but even with
lua_pcall it’s not safe. Consider this call
Haskell function which calls lua_error Lua function, uses pcall Haskell program
This program jumps to Lua function, skipping Haskell RTS code that would
run before Haskell function returns. For this reason we can use
pcall) only for catching errors from Lua, and even in
that case we need to make sure there are no Haskell calls between the
error-throwing Lua call and our
Error to exceptions (and back)
To be able to catch exceptions from Haskell functions in Lua, we need to
define a custom error protocol. Currently HsLua does this:
the same type as Lua’s
lua_error, but instead of calling
it returns two values: A special error value and an error message. HsLua
exceptions are caught and converted to Lua errors via this function.
All of the above internals should stay hidden most of the time, as all Haskell functions are wrapped such that error values are transformed into Lua errors behind the scenes.
At this point our call stack is like this:
Lua function (Haskell function returned with error, which we caught) Haskell program
If we want to further propagate the error message to the Haskell
program, then we can just use Lua’s standard
error function and use
pcall on the Haskell side. Note that if we use
error on the Lua side
and forget to use
pcall in the calling Haskell function, we would be
starting to skip layers of abstractions and would get a segfault in the
best case. That’s why HsLua wraps all API functions that can potentially
fail in custom C functions. Those functions behave idential to the
functions they wrap, but catch all errors and return error codes
instead. This comes with a serious performance penalty, but using
@error@ within Lua should be safe.
pcall function is not wrapped in additional C code but still safe.
The reason it’s safe is because the
lua_pcall C function is calling
the Lua function using Lua C API, and when the called Lua function calls
lua_pcall C function, without skipping any
layers of abstraction.
lua_pcall then returns to Haskell.
Small to medium sized examples: hslua-examples
Support for aeson: hslua-aeson
Blog posts by @osa1 on earlier versions of HsLua:
Talk given at Haskell in Leipzig (HAL) 2017: https://github.com/tarleb/talks/blob/master/2017-10-26-hal/talk.org