views:

5448

answers:

27

This is a Tips & Tricks question with the purpose of letting people accumulate their patterns, tips and tricks for Lua.

Lua is a great scripting language, however there is a lack of documented patterns, and I'm sure everyone has their favorites, so newcomers and people wondering if they should use it or not can actually appreciate the language's beauty.

+17  A: 

Swap variables:

a, b = b, a

If a is nil, copy b's value:

a = a or b
Leonardo Constantino
+1 for the swap. FYI, Python and other languages support this syntax as well. Very Convenient!
Justin Ethier
+20  A: 

Metatables are sexy, but depending on the language you're coming from they may take a while to wrap your head around.

The first thing you need to realize is that Lua has one data structure - the table - and everything is an entry in a table. Functions, strings, everything.

So when you call a function, or look something up in an array, or whatever, you're actually telling Lua to look up a key value pair in a table. The neat thing is that with metatables you can tell Lua what to do if the key isn't found. You can either point it at a different table or alternatively a function.

This opens up all sorts of neat tricks. You can create a lookup table of English to German translations - and if you ever try to translate something that isn't in the table it can print an apology, display the untranslated string, and/or log an error to let the developers know they're missing a translation. Sure you can DO that in other languages, but you end up with messy branching conditionals. With Lua it's transparent.

Say you've got a table of translations called tranMsgs:

setmetatable(tranMsgs,tranFail)
tranFail.__index = function(t,k)
-- code goes here
end

Now whenever anyone tries to lookup a missing translation the above code will run. Very simple, very understandable.

You can also use metatables to cache complex calculations.

cachedResults = {}
setmetatable(cachedResults, expensiveFunction)
expensiveFunction.__index = function(t,k)
cachedResults.k = sqrt(sqrt(k^k))
return cachedResults.k
end

Now calling print(cachedResults[50]) will be much faster after the first time. And sure you can do that in any language, but it's brief, elegant, and easy to understand in Lua. :-)

If you're coming from a language like C or Java, getting your head around metatables will open the door to a lot of fun tricks.

Cody Hatch
+3  A: 

Here's an elegant way to compute a Fibonacci series with metatables, which I suspect is what Thomas was thinking of:

fibotable = {[1] = 0, [2] = 1}
setmetatable(fibotable, {
    __index = function (t, i)
        t[i] = t[i-1]+t[i-2]
        return t[i]
    end
})
print(fibotable[11])

(Stolen from Jan De Vos's post)

What's happening here is that when you try to check the 11th number in the series it checks to see if it's been calculated. If not it tries to do so by adding the 10th and 9th numbers - which requires checking to see if they've been calculated yet. If either or both of them haven't been calculated yet it recurses until it hits something which has been calculated, then it calculates all the needed numbers, storing them as it goes so that they'll be cached for future lookups and calculations. It's conceptually very elegant, I think.

Cody Hatch
I´m sorry but I don´t find that to be any elegant at all... Specially if you compare that algorithm to similar pieces in functional languages.
Vicent Marti
Tanoku - you were right. The solution in my post now is much more elegant, I think. :-)
Cody Hatch
`print(fibotable[-1])`
finnw
+1  A: 

This is a global metatable trick I use to define useful aliases when using Lua, the cool thing is the aliases are actually getting the results from functions not from static data, my favorite is "now" as an alias to get the current system time.

    system_mt = { --this table cannot be local, because the __index goes on recursively
      now = function() 
        return os.time()
      end,
      homepath = function()
        return os.getenv("HOMEPATH")
      end,
      hostname = function()
        local socket = socket or require"socket"
        if socket then
          return socket.dns.gethostname()
        else
          return "LOCALHOST"
        end  
      end,
      source = function()
        local t = debug.getinfo(3,'S')
        return t.source
      end,
      __index = function(t,k)
        if system_mt[k] then --Global variables 
          return system_mt[k]()
        end 
      end,   
    }
    setmetatable(_G,system_mt)

    stamp = now
    print(now)
    print(source)
    print(localhost)
    dofile(homepath.."/myscripts/test.lua")
    print(now-stamp)
Robert Gould
+2  A: 

Here is a benign technique for deprecating functions from a larger code base:

    --// Useful wrapper function for printf
    function printf(s,...)
      print(string.format(s,...))
    end

    --//deprecated metatable
    deprecate_mt = {
      __index = function(t,k)
        printd(t.__deprecate_msg)
        return t.__deprecate_table[k]
      end,
      __newindex = function(t,k,v)
        printd(t.__deprecate_msg)
        t.__deprecate_table[k] = v
      end,
    }

    --//Use functional programming to redefine the function 
    --//or table as a new one that wraps the old one in a deprecated metatable
    function deprecate(o,msg)
      local msg = msg or "deprecated call" --//Using Lua's closure abilities to store msg
      if type(o) == "function" then --//functions are first class citizens in Lua
        return function(...) 
        --//returning a function that wraps the deprecated function, this is functional programming at it's best
                printf("deprecated[%s]",msg) 
                return o(...)
               end
      else
        local dmt = getmetatable(o) or {}
        dmt.__index = function(t,k)
          printf(t.__deprecate_msg)
          return t.__deprecate_table[k]
        end
        dmt.__newindex = function(t,k,v)
          printf("deprecated[%s]",t.__deprecate_msg)
          t.__deprecate_table[k] = v
        end
        dt = {
          __deprecate_msg = msg,
          __deprecate_table = o,
        }
        setmetatable(dt,dmt)
        return dt
      end
    end

    function foo()
      print("foo")
    end

    function bar()
      print("bar")
    end

    foo()
    foo = deprecate(foo,"foo has been deprecated use bar instead")
    foo() --//"foo has been deprecated use bar instead" \n "foo"

    oldtable={"hello"}
    oldtable = deprecate(oldtable, "oldtable has been deprecated use newtable instead")
    print(oldtable[1]) --// "oldtable has been deprecated use newtable instead" \n "hello"
Robert Gould
+9  A: 

The 'metafib' example by Cody Hatch is not quite correct (or, at least, the comment explaining it is not). There is no recursion going on, in fact, the code given is very inefficient: whenever a fibonacci number is requested that is not yet known, the entire sequence is recomputed from the start.

A version that does use recursion (not using __call but __index, so you get the n-th number by using [n] instead of (n)):

fibotable = {[1] = 0, [2] = 1}
setmetatable(fibotable, {
    __index = function (t, i)
        t[i] = t[i-1]+t[i-2]
        return t[i]
    end
})
print(fibotable[11])

If you try to get number '11' from the table, and it is not yet there, the __index metamethod will be executed. This method will calculate the requested number by adding the 10th and 9th numbers together (for which, potentially, __index will be called again), and then store it in the array. If you were to print the entire table after the last print statement, you would see it contained all the fibonacci numbers up to and including 11.

Jan de Vos
You are absolutely correct. I misread what the for loop was doing and...well...yeah. Embarrassing. And your example is MUCH cleaner too.
Cody Hatch
+3  A: 

Here's a small Lua based unittest framework defined in Lua:

  unittest = {        
    test= {
      equal = function(a,b)
        return a == b
      end,
      notequal = function(a,b)
        return a ~= b
      end,
      exception = function(a,...)
        --//do a protected call kinda like Lua's try/catch
        local ok, err = pcall(a,...)
        if not ok then
          return true
        end
        return false
      end,
      noexception = function(a,...)
        --//do a protected call kinda like Lua's try/catch
        local ok, err = pcall(a,...)
        if not ok then
          return false
        end
        return true
      end,
    },--//end of tests
    --//start unit testing environment
    start = function()
      --//store the calling environment (=closure)
      ut = { oldenv = getfenv(2) }
      --//allow new environment to index the global environment
      setmetatable (ut,{__index = _G})
      setfenv(2, ut)
    end,
    stop = function()
      --//Return the environment to what it used to be, unscratched
      setfenv(2, getfenv(2).oldenv) 
    end
  }

    --//declare some global variable
    a = 25
    --//the code I want to test in a protected enviroment
    do --//if you run this from the commandline you "do" to create a closure
    unittest.start()
        a = 1 --//oops! someone redefined my global variable!!!
        print(unittest.test.equal(a,1))
        print(unittest.test.notequal(a,2))
        print(unittest.test.exception(aFunctionThatDoesntExist,10))
        print(unittest.test.noexception(math.sin,19))
        print(a) --// 1
    unittest.stop()
    end
    --//luckily the test enviroment contained all damage to the global table!
    print(a) --//25

I use this for unit-testing but you can also use this to define safe environments to avoid having user code redefine your applications base code

Robert Gould
+5  A: 

lua-users wiki: Sample Code:

Following is a list of pages with sample code. Types of code include standalone reusable functions/classes, reusable modules, example code illustrating the use of other modules, tutorials on using other modules, full programs, and design patterns.

eed3si9n
A: 

Another simple trick that's not commonly seen in code, but totally awesome is:

=("foo"):upper() ->output: FOO

However this actually is very dangerous if you run unsafe code because you can access the global string module from any string, so malicious code could do something like:

getmetatable("foo").__index.upper = function() print("bye bye sucker");os.exit() end
=("foo"):upper() >output: bye bye sucker (application quits)
Robert Gould
You may protect yourself from this as described in here: http://stackoverflow.com/questions/325323/is-there-anyway-to-avoid-this-security-issue-in-lua/326323#326323
Alexander Gladysh
+6  A: 

One of the biggest pain when debugging is the lack of function for printing a table. Here is a function I use to do that :

function dump(o)
    local s = ''
    if type(o) == 'table' then
     s = '{ '
     for i = 1, #o do
      s = s .. dump(o[i])
      if i ~= #o then s = s .. ', ' end
     end
     s = s .. ' }'
    else
     s = tostring(o)
    end

    return s
end

Of course you can enhance it, but I find it very useful.

Wookai
A number of alternative implementations is listed here: http://lua-users.org/wiki/TableSerialization
Alexander Gladysh
+2  A: 

Here is a Y combinator in Lua:

function Y(f)
    local function recurse(...)
        return f(recurse, ...)
    end
    return recurse
end

factorial = Y(function(factorial, n)
    if n == 1 then
    return 1
    else
        return factorial(n - 1) * n 
    end
end)

print(factorial(5)) --> 120
Robert Gould
+5  A: 

It is a good thing to stimulate Stack Overflow community to share their favorite Lua snippets, but, in my opinion, the lack of documented patterns is overemphasized.

For example, please see most excellent books "Lua Programming Gems" and "Programming in Lua". Also do not forget Lua wiki

Alexander Gladysh
Exactly, and great books.
Sébastien RoccaSerra
Indeed but this post began before Gem's was released ;)
Robert Gould
Anyways I agree with you to a point, but there are some things here that you can't find in any of the those sources, so the thread's got a value of its own.
Robert Gould
Err, yes, sorry. Is see now that this is an old thread. It just popped up in my RSS reader.Although I'd referenced those Lua books and wiki in the question itself ;)
Alexander Gladysh
+6  A: 

The Lua compiler optimizes tail calls into something resembling a goto. This allows for an interesting way to express a Finite State Machine where the individual states are written as functions and state transitions are performed by tail-calling the next state.

Coming from a background where tail calls are not optimized it looks a bit like recursion gone wild, of course.

-- Define State A
function A()
  dosomething"in state A"
  if somecondition() then return B() end
  if done() then return end
  return A()
end

-- Define State B
function B()
  dosomething"in state B"
  if othercondition() then return A() end
  return B()
end

-- Run the FSM, starting in State A 
A()

The example above is more than a little contrived, but it shows the key features. State transitions take the form "return NewState() end" and exiting the FSM completely is done by returning anything other than a tail call from any state.

Just make sure that you really are using tail calls, or you will get recursion gone wild after all... in particular

-- bad example!
function C()
  D()   -- note no return keyword here
end

does not tail call D. Instead, C explicitly returns nothing after calling D, which required a stack frame. In effect,

-- bad example!
function E()
  E()
end

will eventually overflow the stack, but

-- infinite loop hiding in the tail call
function F()
  return F()
end

will loop forever.

RBerteig
+6  A: 

Here is a little class module I wrote with the purpose of looking nice (syntactic sugar). It's not a full feature system, but does serve as an interesting paradigm for making a user-friendly class system in Lua:

mt_class ={}
function mt_class:inherit(...)
    local parents = {...}
    self.__parents = self.__parents or {}
    local k,parentname
    for k,parentname in ipairs(parents) do
     table.insert(self.__parents,getfenv(1)[parentname])
    end
    return self
end

function class(name)
    local env = getfenv(1)
    local __classname = name
    local builder = {}
    local define= function(builder,proto)
     env[__classname]=proto
     local __parents = builder.__parents
     local find =function(self,key)
      local result = proto[key]
      if not result and __parents then
       local k,parent
       for k,parent in ipairs(__parents) do
        result = parent[key]
        if result then break end
       end
      end
      return result
     end
     function proto.new(instance)
      assert(instance~=proto)
      local instance = instance or {}
      return setmetatable(instance,{__index = find})
     end
    end
    return setmetatable(builder,{__index=mt_class,__call=define})
end

And it's usage is quite elegant:

class "Dog" {
    say="Bowow"
}

class "Rover":inherit "Dog" {
    middle ="Rover"
}

r = Rover.new{likes="slippers"}
print(r.say)
print(r.name)
print(r.likes)

So there you go, defining classes is easy with an API like this.

Robert Gould
+2  A: 

I just whipped up a set of code for a simple "iowriter" class that can help in writing data to files. You need the LOOP (Lua Object Oriented Programming) library, but you already have it if you use the default install of Lua.

require 'loop.simple'
class = loop.simple.class
classof = loop.simple.classof
initclass = loop.simple.initclass
instanceof = loop.simple.instanceof
isclass = loop.simple.isclass
memberof = loop.simple.memberof
members = loop.simple.members
new = loop.simple.new
rawnew = loop.simple.rawnew
subclassof = loop.simple.subclassof
superclass = loop.simple.superclass
local open = {}
ioWriter = class{
 stream = false
}
ioWriter[open] = false
function ioWriter:open(name)
 if not self[open] then
     self.stream = io.open(name, "w")
     self[open] = true
 else
     error("attempted to open an already open writer")
 end
end
function ioWriter:write(str)
 self.stream:write(str)
end
function ioWriter:writeLine(str)
 self.stream:write(str .. '\n')
end
function ioWriter:close(self)
 if self[open] then
     self.stream:flush()
     self.stream:close()
        self.stream = false
     self[open] = false
    else
     error("attempted to close an already closed writer")
    end
end

Example script:

require 'ioWriter.lua'
local writer = iowriter()
writer:open("test.txt")
writer:write("this is a test, ")
writer:writeLine("this is the second test")
writer:close()

The most useful part about this is that it provides enough simplification to wrap your own more specialized implementation around this, like an HTML or XML writing class.

Suggestions for improvement are welcome!

RCIX
You could add a sample of usage? Although I (and other Lua buffs) can see how this should work, it's not clear for newcomers how to use your writer class
Robert Gould
As soon as i can i'll update my sample to fix a bug i found and imrove functionality a little, along with adding an example.
RCIX
+2  A: 

One can have any function call an arbitrary metatable function like so:

function toclass(obj)
    if getmetatable(obj).__toclass then
        return getmetatable(ob).__toclass(obj)
    else
        --fancy to class logic here...
    end
end
RCIX
+1  A: 

If you want to protect a metatable but still have it readable (for say being able to access and call metamethods) you can do something like this:

local mt = {}
local t = { string = "foo"}
mt.__tostring = function(self) return self.string end
mt.__metatable = mt
setmetatable(t, mt)
local bar = gemetatable(t).__tostring(t) -- should work
setmetatable(t, mt) -- won't work
RCIX
A: 

The difference between else if and elseif. If you have

if foo then
    --do something...
elseif bar then
    --do something else...
end

it's roughly equivalent to

if foo then 
    --do something
else
    if bar then
        --do something else
    end
end

Fine, unless you have multiple else if statements (where do the extra elses go?). Plus, there's not enough end's.

RCIX
+1  A: 

A table merging and a table copying function:

-- thanks to http://stackoverflow.com/questions/1283388/lua-merge-tables/1283608#1283608 and
-- http://stackoverflow.com/questions/1397816/changing-one-table-seems-to-change-the-other/1400485#1400485
-- tableMerge:
-- merges two tables, with the data in table 2 overwriting the data in table 1
function tableMerge(t1, t2)
    for k,v in pairs(t2) do
        if type(v) == "table" then
            if type(t1[k]) ~= "table" then -- if t1[k] is not a table, make it one
                t1[k] = {}
            end
            tableMerge(t1[k], t2[k])
        else
            t1[k] = v
        end
    end
    return t1
end

-- tableCopy:
-- takes a table and returns a complete copy including subtables.
function tableCopy(t)
    return tableMerge({}, t)
end
RCIX
A: 

A function that rounds whole numbers and a function that rounds numbers to the specified number of decimal places:

-- round:
-- takes a number and returns a rounded up or down version
-- if number is 0.5, it rounds up
function round(num)
    local floor = math.floor(num)
    local ceiling = math.ceil(num)
    if (num - floor) >= 0.5 then
     return ceiling
    end
    return floor
end

-- decRound:
-- takes a number and the number of places to round to and returns a suitably rounded number
function decRound(num, placesToRound)
    local multipler = tonumber("1" .. string.rep("0", placesToRound), 10)
    num = num * multipler
    num = round(num)
    num = num / multipler
    return num
end

they could probably be compressed into one function, but i'll leave that as an exercise to the reader.

RCIX
A: 

A function to truncate a number and to truncate a number to the specified number of decimal places:

-- truncate:
-- given a number, returns a number with X max digits
function truncate(num, count)
    local numString = tostring(num)
    numString = string.sub(numString, 0, count)
    return 0 + numString -- forces conversion to number
end

-- decTruncate:
-- given a number with a decimal place and the number of decimal places to truncate to, returns a number truncated to the specified number of decimal places.
function decTruncate(num, decimalPlaces)
    local wholeNumString = tostring(math.floor(num))
    return truncate(num, decimalPlaces + string.len(wholeNumString) + 1)
    -- we're adding 1 because we have to take the decimal point into account in truncation
end
RCIX
+3  A: 

Seemingly little-known syntactic sugar:

foo = {a=1, b=2}
function foo:test(c)
    print(self.a, c)
end
foo:test(3) --prints: 1   3

Declaring it as foo:test instead of foo.test adds an implicit first parameter named self. Calling foo:test(3) is the same as calling foo.test(foo,3). You can use this for OOP, creating new instances of foo like so:

myFoo = {}
for k,v in pairs(foo) do myFoo[k] = v end
myFoo.a = 27
myFoo:test(8) --prints: 27    8

Note that simply myFoo = foo won't work as you'll be creating a reference rather than a copy.
Beware if using this with libraries such as LuaGnome; if you did myWidget:connect("clicked", myFoo.test, myFoo), you'd end up getting the event as self and myFoo as a. In this case you have to declare test as function foo.test(a, self) since GTK is not aware of the self parameter.

Rena
+1  A: 

clean.seeall is an alternative to package.seeall

You normally use it with module this way when declaring modules:

module(..., clean.seeall)

<rest of module code>
...

With package.seeall, there is a big drawback: The module now mirrors the global environment, (for example yourmodule.print == print) (Lua Module Function Critiqued)

With clean.seeall, the module environment inherits the global environment, but it does not expose it to the outside.

local clean = {}
_G[(...) or "clean"] = clean

-- Called as module(..., clean.seeall)
-- Use a private proxy environment for the module,
-- so that the module can access global variables.
--  + Global assignments inside module get placed in the module
--  + Lookups in the private module environment query first the module,
--    then the global namespace.
function clean.seeall(_M)
  local priv = {}   -- private environment for module
  local privmt = {}
  privmt.__index = function(priv, k)
    -- debug the "global" namespaces: print(_M._NAME .. "." .. k)
    return _M[k] or _G[k]
  end
  privmt.__newindex = _M
  setmetatable(priv, privmt)
  setfenv(3, priv)
end
kaizer.se
haven't tested this one yet, but it does seem like a great idea! Thanks for sharing
Robert Gould
The lookup function is written beautifully as `return _M[k] or _G[k]` but there is a bug that I haven't noticed despite using this for a whole application: false values. So it needs to be rewritten with explicit if/else clauses. :-(
kaizer.se
+1  A: 

Short if-else:

print(nil and 'true' or 'false') -- prints false
print(1 and 'true' or 'false') -- prints true
arkilus
A: 

A simple function wrapper to execute as a coroutine:

function cororun (func)
  local f = coroutine.wrap (func)
  f (f)
end

With this wrapper, you can call an asynchronous function as if it's a synchronous function. Use this like so:

cororun (
  function (f)
    some_asynchronous_function (f) -- pass wrapped function as a callback
    local val = coroutine.yield ()

    another_asynchronous_function (f)
    local another_val = coroutine.yield ()
  end)

Note that f always points the function itself.

torus
A: 

Not a super tricky item, but one that I use over and over:

If you miss the compactness of C's conditional operator: '?', for example:

printf("The operation %s", status ? "succeeded":"failed");

Then in Lua you might like to use a function I defined as 'iif' (for inline-if) so that you can in Lua do this:

print "The operation "..iif(status,"succeeded","failed")

Nice and compact. The iif() function is simply:

function iif(cond,tru,fls) --inline if, instead of C's (cond ? tru:fls)  
    if cond then  
        return tru  
    else  
        return fls  
    end  
end
proFromDover
In most cases, this will work for you and doesn't require a user-built function: status and "succeeded" or "failed"
sworoc
In other words, `function iff(cond,tru,fls) cond and tru or fls end`
benzado
+1  A: 

(Disclaimer: Shameless self-plug ahead)

MiddleClass is a small (124 LOC) library that you can use for object orientation in Lua.

require('MiddleClass')

local MyClass = class('MyClass')

function MyClass:initialize(x,y,z)
  super.initialize(self)
  self.x, self.y, self.z = x,y,z
end

function MyClass:foo(x,y,z)
  return self.x+x, self.y+y, self.z+z
end

MiddleClass supports single inheritance and (a rudimentary form of) mix-ins.

There's also a middleclass-extras project with some utility tables implementing stateful classes, callback handling and signaling, among others.

egarcia