Personal tools

HsLua

From HaskellWiki

(Difference between revisions)
Jump to: navigation, search
m (basic proof-reading (tidying up grammar, &c.))
(typo)
 
Line 1: Line 1:
What is Lua? It's the scripting language (like Perl) targeted to be
+
What is Lua? It's a scripting language (like Perl) targeted to be
 
easily integrated into any host application. You can do it in
 
easily integrated into any host application. You can do it in
 
literally 5 minutes. let's try:
 
literally 5 minutes. let's try:
Line 5: Line 5:
 
== Example 1: running Lua scripts ==
 
== Example 1: running Lua scripts ==
   
First, you need to download and unpack brilliant HsLua package [1]
+
First, you need to download and unpack the brilliant HsLua package [1]
 
written by Gracjan Polak:
 
written by Gracjan Polak:
 
http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hslua
 
http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hslua

Latest revision as of 18:03, 3 October 2010

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:

Contents

[edit] 1 Example 1: running Lua scripts

First, you need to download and unpack the brilliant HsLua package [1] written by Gracjan Polak: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hslua

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 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 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 built-in Lua functions into this instance. Note that fresh Lua instances have access only to a dozen standard (arithmetic) operations. By careful selection of functions provided to the 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.

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

[edit] 2 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] <- Lua.callfunc l "getuserpwd" "mail.google.com"


[edit] 3 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 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 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 :)

[edit] 4 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. 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 an 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 a 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 an 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 pair of operations is "callfunc" and "callproc". They both call a Lua function passing to it arbitrary number of arguments. The only difference is that "callfunc" returns and "callproc" omits the 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 the 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 the IO specifier is optional here. n>=0, i.e. register/callfunc/callproc may also register/call functions having no arguments.


[edit] 5 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.


[edit] 6 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


[edit] 6.1 [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.

[edit] 6.2 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

[edit] 6.3 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?

[edit] 6.4 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.


[edit] 7 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