HsLua

From HaskellWiki
Revision as of 09:37, 9 May 2008 by Bulatz (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

(this page isn't yet finished)

what is Lua? it's the 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 brilliant HsLua package [1] written by Gracjan Polak: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hslua-0.2

as usual, in order to build and install Cabalized package, run commands:

runhaskell Setup.hs configure
runhaskell Setup.hs build
runhaskell Setup.hs install

(assuming that you have ghc 6.6 or 6.8 installed)


The simplest work where Lua may be handy is replacing application config files with scripts. This allows to define complex data structures and utilize power of full-fledged programing 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 into your hands values defined in such unusual way:

import qualified Scripting.Lua as Lua
main = do
    l <- Lua.newstate
    Lua.openlibs l

    Lua.dofile l "configfile.lua"
    Right name <- Lua.dostring l "return username"
    Right pwd  <- Lua.dostring l "return password"

    print (name::String, pwd::String)
    Lua.close l

voila! are you finished in 5 minutes? :)


What we are doing here? First, we create new instance of Lua interpreter using "newstate". Lua implementation doesn't use global vars that means that you can create as much Lua instances as you need and run them all simultaneously

Then, "openlibs" imports builtin Lua functions into this instance. Note that fresh Lua instance have access only to a dozen of standard (arithmetic) operations. By careful selection of functions provided to Lua instance, you can turn it into sandbox - i.e. disallow access to file system, network connections and so on, or provide special functions which can access only specific directory/host/whatever

Next operation, "dofile", runs script from file specified. This script defines variables "username" and "password" that we then read by "eval". Finally, "close" destructs Lua instance, freeing memory it has grabbed


Example 2: calling from Haskell to Lua

Now imagine that we need more complex config capable to provide for each site we are log in its own user/pwd pair. we can write it either as 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 Haskell program reading of variables with call to function:

     [name,pwd] <- Lua.callfunc l "getuserpwd" "mail.google.com"


Example 3: calling from Lua to Haskell

But that's not the whole story. When Lua used as 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 Lua script fully defines their behavior logic, it should command something that will render this behavior 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 behavior. let's go:

import qualified Scripting.Lua as Lua

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

main = do
    l <- Lua.newstate

    Lua.register l "move" hsMove
    Lua.dofile l "game.lua"

    Lua.close l

The only new call here is "register" which registers Haskell function in 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 (numbers, strings, booleans, lists and maps). 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 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 values. When we cast these values from/to Haskell world, they are mapped to the appropriate Haskell types. Type class StackValue covers Haskell types that can be casted from/to Lua values and defines the casting rules. Predefined class instances include Int, Double, String, Bool, [a] for lists and [(a,b)] for maps where a and b may be any StackValue types again. There is also existential class wrapper XXX that allows to pass heterogeneous collections (lists/maps) from Haskell to Lua:

Lua.callproc "foo" [XXX True, XXX "str", XXX [1,2,3]]
Lua.callproc "bar" [(XXX 1, XXX True), (XXX 2, XXX "str"), (XXX "tag", XXX 3.14])]

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

dofile   :: LuaState -> String -> IO Int
dostring :: LuaState -> String -> IO Int

Two other operations return result of executing string/file: "eval" and "evalfile", respectively. "eval" may also be used to evaluate expressions, as it was done in our first script. These operations return (Right a) on success, where "a" may be any type belonging to StackValue class. On error they return (Left Int) where Int will be either errcode or 0 if casting was unsuccessful:

eval     :: (StackValue a) => LuaState -> String -> Either Int a
evalfile :: (StackValue a) => LuaState -> String -> Either Int a

The next operations pair is "callfunc" and "callproc". They both call Lua function passing to it arbitrary number of arguments. The only difference is that "callfunc" returns and "callproc" omits return value of Lua function called:

callfunc :: (StackValue x1, x2 ... xn, a) =>
            LuaState -> String -> x1 -> x2 ... xn -> Either Int a
callproc :: (StackValue x1, x2 ... xn) =>
            LuaState -> String -> x1 -> x2 ... xn -> IO Int

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

register :: (StackValue x1, x2 ... xn, a) =>
            LuaState -> String -> (x1 -> x2 ... xn -> {IO} a) -> IO Int

where {IO} means that IO specifier is optional here. 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 base on which you should 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 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]

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 - 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 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 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 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 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. 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