Play With Lua!

My Lua init file

without comments

I don’t think it’ll surprise anyone when I say that I think Lua is a great language. But, it does have a very small standard library. So, there are a few other utilities that I end up including or writing in almost everything I do. Luckily, Lua has a facility for loading them automatically when I open up the REPL.

How to invoke it

Lua, when the REPL starts up, reads an environment variable called LUA_INIT. If this variable contains Lua code, it runs it; otherwise, if it begins with an “@” symbol, it’ll read it as the name of a file to run. So, in my .bashrc I have this line:

# Lua init
export LUA_INIT="@$HOME/.lua/init.lua"

My .lua directory

There are three things I load every time: the “inspect” library and the “middleclass” library, both by Enrique Garcia, and LPeg, by Roberto Ierusalimschy. So, here’s the complete listing of what I have:

-rw-rw-r-- 1 randrews randrews  1465 May 17 22:52 init.lua
-rw-rw-r-- 1 randrews randrews  9183 May 10 12:36 inspect.lua
-rwxrwxr-x 1 randrews randrews 52048 May 17 22:49 lpeg.so
-rw-rw-r-- 1 randrews randrews  6116 May 10 11:45 middleclass.lua
-r--r--r-- 1 randrews randrews  6286 May 17 22:49 re.lua

(re is a regular expression library that comes with LPeg)

The init.lua file itself starts out by including the three libraries:

package.path = package.path .. ";" .. os.getenv("HOME") .. "/.lua/?.lua"
package.cpath = package.cpath .. ";" .. os.getenv("HOME") .. "/.lua/?.so"
inspect = require 'inspect'
class = require 'middleclass'
lpeg = require 'lpeg'
re = require 're'

Note that I have to stick the .lua directory on to both package.path and package.cpath, because in all likelihood I’m not running the REPL from the .lua directory itself: the normal path element of "?.lua" won’t find these.

Table utilities

After that, I put in a few things that I think make using tables easier. To begin with, I noticed that most of the standard library functions in the table table take a table as the first parameter, meaning it would be very convenient to call them as methods. So, I make a shortcut to create a table that has table as its metatable:

setmetatable(table,
             { __call = function(_, ...)
                   return setmetatable({...}, table.mt) end })
 
table.mt = {
    __index = table,
    __tostring = function(self)
        return "{" .. self:map(tostring):concat(", ") .. "}"
    end
}

This means I can do something like this:

t = table(1,2,3)
print(t) -- prints "{1, 2, 3}"
t:insert(4)
s = t:concat(":") -- s is now "1:2:3:4"

It’s just a convenient shorthand, and having a real tostring is very helpful in the REPL.

Higher-order functions

You may have noticed that map function in there. I also defined a couple handy functions of my own in table. That’s the first, map, which transforms a table into another table using a function:

t = table(1,2,3,4)
t2 = t:map(function(x) return x*x end)
print(t2) -- prints "{1, 4, 9, 16}"

It’s much more terse than a for loop, and present in all functional languages. It’s defined like this:

function table:map(fn, ...)
    local t = table()
    for _, e in ipairs(self) do
        local _, r = fn(e, ...)
        t:insert(r)
    end
    return t
end

Any extra parameters you pass to map are passed through to the callback.

The next function is another simple one, select. This builds a new table from all the elements for which a callback function returns true:

t = table(1,2,3,4,5)
odds = t:select( function(n) return n%2 == 1 end )

This is another straightforward but very handy function that can make a lot of things more concise:

function table:select(query)
    local t = table()
 
    self:map( function(el)
            if query(el) then t:insert(el) end
    end)
 
    return t
end

Reduce

The final utility I put in is reduce. This reduces a table down to one value, given a function (and an optional initial value). For example, here’s how to use reduce to find the maximum value of a table of numbers:

t = table(1,7,3,19,4)
max = t:reduce( function(a,b)
    if a > b then return a
    else return b
end )

This will call the function on the first two elements, 1 and 7, returning 7. Then it will call it with the result of the first call and the third element, 3. Then the result of that and the next element, and so on:

1, 7  ==> 7
7, 3  ==> 7
7, 19 ==> 19
19, 4 ==> 19

You can optionally pass in a second argument that will be used as the initial value; if you don’t then the first thing in the array is the initial value:

function table:reduce(fn, init)
    local i = 1
    local accum = init
    if init == nil then
        accum = self[1]
        i = 2
    end
 
    while i <= #self do
        accum = fn(accum, self[i])
        i = i + 1
    end
 
    return accum
end

There are a few short functions dealing with numeric arrays that are just calls to reduce:

function table:sum()
    return self:reduce( function(a,b) return a+b end, 0 )
end
 
function table:max(comp)
    return self:reduce( function(a,b)
            if a > b then return a
            else return b end end )
end
 
function table:min(comp)
    return self:reduce( function(a,b)
            if a < b then return a
            else return b end end )
end

Notice that I have to pass an initial value to the call in sum: this is because without it the sum of an empty table would be nil, and it’s more useful for it to be zero.

The code

As always, the code is available on Github. To use it, you’ll need to build your own copy of LPeg since that binary is for my system (you can obtain it on their page), and set the LUA_INIT environment variable, which is different depending on what system you’re on: for Unix (or Mac) you should put it in your login script, which is probably .bashrc, .profile, or something like that. For Windows you’ll have to go through the control panel.

Written by randrews

May 22nd, 2015 at 12:00 pm

Posted in Uncategorized