HsLua

From HaskellWiki
Jump to navigation Jump to search

What is Lua? It's a scripting language (like Perl) targeted to be easily integrated into any host application. You can do it in literally 5 minutes. let's try:

Example 1: running Lua scripts

First, you need to download and unpack the HsLua package: http://hackage.haskell.org/package/hslua

As usual, in order to build and install the package, run the usual commands:

cabal configure
cabal install

Note that the lua version that is linked to can be changed via cabal flags: consult the package docs for details.


The simplest work where Lua may be handy is in replacing application config files with scripts. This allows one to define complex data structures and utilize the power of a full-fledged programming language. For example, the following "config" file, written in Lua:

username = os.getenv("USER") or "God"
password = os.getenv("PWD")  or "dontdisturb"

assigns to variables "username" and "password" values read from environment vars USER/PWD, but substitutes default values if they are not defined. The following Haskell program demonstrates how to get your hands on values defined in such an unusual way:

import Foreign.Lua

main = runLua $ do
    openlibs

    dofile "configfile.lua"
    name <- getglobal "username" *> peek (-1)
    pwd <- getglobal "password" *> peek (-1)

    liftIO $ print (name::String, pwd::String)

Voila! Are you finished in 5 minutes? :)


What we are doing here? The command "openlibs" imports built-in Lua functions into this lua instance. Note that fresh Lua instances only have access to a dozen standard (arithmetic) operations. By careful selection of functions provided to the Lua instance, you can turn it into sandbox - e.g. disallow access to file system, network connections and so on, or provide special functions which can access only specific directory/host/whatever.

The next operation, "dofile", runs script from file specified. This script defines the global variables "username" and "password". The function "getglobal" pushes the respective variable to the top of Lua's stack, which is used by Lua to communicate with external programs. We then turn the value at the top of the stack (i.e., at stack index (-1)) into a haskell value by using "peek".

Example 2: calling from Haskell to Lua

Now imagine that we need more complex config capable of providing for each site we have an account in its own user/pwd pair. We can write it either as an associative table, or as a function mapping site name to user/pwd. Let's combine both approaches:

function getuserpwd (site)
  local cookies = { ["www.ibm.com"] = {"joe", "secret"}
                  , ["www.sun.com"] = {"hoe", "another secret"}
                  }
  if cookies[site] then
    return cookies[site]
  elseif site:match("[.]google[.]com$") then
    return {"boss", "boss"}
  else
    return { os.getenv("USER") or "God"
           , os.getenv("PWD")  or "dontdisturb" }
  end
end

and replace in the first Haskell program, the reading of variables with a call to the new function 'getuserpwd':

     (name,pwd) <- callFunc "getuserpwd" "mail.google.com"

Example 3: calling from Lua to Haskell

But that's not the whole story. When Lua is used as a scripting language inside your application, the communication should be two-forked. Imagine, for example, that you develop a game and Lua scripts are used to control game characters. While the Lua script fully defines their behavioural logic, it should command something that will render this behaviour to the player. Just for example:

for i=1,3 do
  move( 1, "forward")
  move( 2, "backward")
end

This script commands our character to go 1 step forward, 2 steps backward repeating this 3 times. The "move" procedure here should be defined on Haskell side to show his behaviour. Let's go:

import qualified Foreign.Lua as Lua

hsMove :: LuaInteger -> String -> Lua ()
hsMove (LuaInteger n) direction = do
    putStrLn $ "going " ++ show n ++ " step(s) " ++ direction

main = runLua $ do
    Lua.registerHaskellFunction "move" hsMove
    Lua.dofile "game.lua"

The only new call here is "registerHaskellFunction" which registers a Haskell function in with Lua instance. Please note that we may call any Lua function/procedure from Haskell (as we've done in previous example) and register any Haskell procedure in Lua as far as their arguments and results has simple enough types (lua numbers, strings, booleans, lists and maps; anything that can be pushed to and received from the Lua stack). In such simple cases HsLua takes in its hands all the conversions of data between Haskell and Lua worlds.

Also note that we omitted here a call to "openlibs", making Lua instance a real sandbox - all the communication with external world that script can do is to move it, move it :)

Exchanging data between Haskell and Lua worlds

Lua variables have dynamic types. When we cast these values from/to Haskell world, they are mapped to the appropriate Haskell types. The type classes FromLuaStack and ToLuaStack cover Haskell types that can be casted from/to Lua values and defines the casting rules. Predefined class instances include LuaInteger, String, Bool, Text, [a], and Data.Map.Map a b for maps where a and b may be any type that's an instance of the respective type class again. Heterogenous data can be passed via hslua-aeson, treating Lua values as JSON-like data.

HsLua includes two operations which just execute operators in context of a given Lua instance: dofile and dostring (executing contents of file and string, respectively). They return OK on success and an error code otherwise:

dofile   :: String -> Lua Status
dostring :: String -> Lua Status


The next important operation is callFunc. It calls a Lua function passing to it arbitrary number of arguments.

-- the actual type is slightly different to simulate a variadic function.
callfunc :: (ToLuaStack x1, x2 ... xn, FromLuaStack a)
         => String -> x1 -> x2 ... xn -> Lua a

Finally, registerHaskellFunction operation registers in the Lua world any Haskell function/procedure that receives and returns values of StackValue types:

registerHaskellFunction :: (FromLuaStack x1, x2 ... xn, ToLuaStack a)
                        => String -> (x1 -> x2 ... xn -> Lua a) -> Lua ()

n ≥ 0, i.e. register/callfunc/callproc may also register/call functions having no arguments.

About Lua

A few words about Lua as a language. It's rather close to Perl/Python/Ruby, the main difference is Lua's compactness. In particular, HsLua will add only about 200kb to your program. The Lua syntax definition is just about 40 lines long and omits many modern features such as exception handling or OOP. It provides simple means for doing simple tasks that made it perfect tool for non-professional programmers

But the first impression fools you. Lua is a wolf in sheep's hide. As its author said, "Lua provides Modula syntax but Scheme semantics". The main distinctive feature of Lua is its extensibility. It provides semantical base for implementation of exceptions, OOP, data hiding, metaprogramming, persistency, functional programming, lazy evaluation, concurrency/coroutines/iterators. But special syntax for advanced features is almost non-existent and in many cases Lua gives you only a base on which you have to build yourself. For example, OOP inheritance isn't implemented but there are libraries implementing single and multiple inheritance just by manipulating metatables. So while you can learn Lua basics in a few minutes, I recommend you to spend a few days before making a final decision.


More about Lua

If you want to know more about Lua, you can jump to the online book written by main developer of Lua and describing Lua 5.0 at http://www.lua.org/pil/ or buy new version of this book describing Lua 5.1 (current version) from Amazon [2]

The official Lua 5.1 manual is at http://www.lua.org/manual/5.1/ - it's clearly written but definitely not the best place to start

If you need to perform more complex tasks using HsLua library, you should study C API for Lua at http://www.lua.org/pil/index.html#24 - the low-level part of HsLua mainly duplicates this API

For me the most interesting part of Lua site is the list of Lua libraries, IDEs and other tools at http://lua-users.org/wiki/LuaAddons

I want to shortly introduce things there that were most exciting for me:

  • LuaBinaryModules - compiling C library for Lua into self-described dll/so
  • LuaJIT - increase execution speed up to 7 times
  • wxLua - binding to wxWidgets and IDE with debugger and C bindings generator
  • MetaLua - alternative Lua frontend with FP features and extensible syntax


[LuaBinaryModules]

If a user of your application wants to use some Lua library written in Lua itself, that's easy - just put the source file into program directory. But what about libraries containing C parts? LuaBinaryModules solves this problem - it allows to compile any library for Lua to a dll/so file which may be imported by any Lua instance to get full library functionality. Technically speaking, LBM just adds one more function which registers all library functions and this function is called automatically by LuaBinaryModules loader

As a result, LuaBinaryModules allows users of your application to install any libraries they need just by copying their dll/so files into application directory.

LuaJIT

While Lua bytecode interpreter being register-based VM is fastest among all dynamic languages, sometimes you need even more speed. LuaJIT compiles bytecode instructions into x86 code, improving speed up to 7 times (as tested on Great Language Shootout examples). One of its interesting features is automatic function specialization to the types of actual parameters

I've quickly tested it on simple i=i+1 statements and found that with JIT computer runs at 1/10th of its full speed while without JIT it runs at 1/30th

wxLua

wxLua is a cross-platform (Windows, Linux, OSX) binding to wxWindows library, plus IDE written using this binding that supports debugging of Lua code and generation of Lua bindings to C libraries. So, you get a full-featured Lua IDE extensible in Lua itself.

It is even more interesting that you can add the wxLua to your Lua-enabled application, providing user-written code means to build full-fledged GUI. Just imagine that now you can split your program into two parts - real work is done in Haskell part which registers its functions for Lua part and all the funny dialogs and editors are written in Lua using wxWindows widgets. The Lua part can be easily created/extended by less experienced users meaning that you may spend more time on implementing core algorithms. Isn't it beautiful?

MetaLua

We already mentioned that Lua semantics is rather close, may be a bit better than of other scripting languages, but its syntax is terse. But this simplicity opens up sudden and very exciting possibility - it's easy to reimplement Lua and add any other features we want without losing bytecode compatibility with official implementation

MetaLua does that, reimplementing Lua compiler in Lua itself. But it goes much further: Lua syntax is described in GG, ParseC-like parsing combinators library. Unlike ParseC, it allows to change parsing rules on the fly, making it ideal basis for language with extensible syntax

MetaLua adds to Lua a lot of FP-styled and other features - ADT, pattern matching, terse syntax for anonymous lambdas, list comprehensions, familiar syntax for exception handling, RAII brackets, statements inside expressions, ternary ?: operator, type/domain checking. All these implemented with usual Lua modules and as everything in Lua, they are very terse - the largest module, implementing pattern matching, is just 200 lines long. You can add new syntax just by dropping new modules into MetaLua directory. Extending MetaLua is very like to programming in Template Haskell with all its pitfalls solved and extending-syntax features added. I will be very pleased if GHC provided front-end like the MetaLua


Just imagine all these features combined together: blazing fast dynamic FP language that may be easily extended by means of new libraries and new syntax just by dropping their files into program's directory and supports creation of GUIs - seamlessly integrated with your Haskell code. I think it will be invaluable tool that supplements Haskell's power with easiness and flexibility.


Further reading

[1] http://hackage.haskell.org/packages/archive/hslua/

[2] http://www.inf.puc-rio.br/~roberto/pil2

[3] http://www.lua.org/uses.html

[4] http://lua-users.org/wiki/ThreadsTutorial

[5] http://lua-users.org/wiki/GenericInputAlgorithms