My Lua init file
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.