The second day of the Lua introduction is certainly heavier going than the first. We are introduced properly to the data structure that is at the heart of Lua, the table.
Tables are efficiently implemented datastructures that can work like arrays or like hashes or like both at the same time. Lua also allows you to change the behaviour of tables using metatables.
This is a powerful concept that allows us to implement our own OO system from the ground up. Lua gives you just enough to work with to make this not only possible, but syntactically slightly pleasant. The other big topic today was Lua’s concurrency system. It uses coroutines to enable multitasking. Again this is a very minimalist approach, and requires some work by the programmer.
The day is rounded off with an interview with the language creator, Roberto Ierusalimschy (pictured). He taks about the trade-offs inherent in designing programming languages and identifies Lua’s sweet spot as being scripting, saying
Nowadays, most people use "scripting language" as almost a synonym for dynamic language. But scripting has a more specific meaning, of a language to … "glue," software that is frequently written in a different language … Lua has always been developed with this kind of use in mind.
My experiences
I liked working with Lua’s simple, prototype OO system. It felt like I was really getting my hands dirty. Similarly with metatables. This sort of under-the-hoodness is great in a scripting language, but I would worry about building larger systems in this sort of way. It feels like it might blow up at any moment.
There were a few syntactical choices which I wasn’t so keen on.
Hey Lua, ALGOL-68 called. It wants its array indexing system back!
- The 1-indexed arrays — "Hey Lua, ALGOL-68 called. It wants its array indexing system back!" — that is just a matter of taste.
- Having to write self.variablename when talking about class variables. You will see some ugly code in my solutions to the exercises.
- I didn’t like the fact that the order in which you define functions and variables can be significant. I want to be able to refer to things I haven’ yet defined.
Another thing that would be nice to have is completion in the REPL. I guess that I have been spoiled by spending time in GHCi which is very nice. Installing rlwrap helped make the history features of readline work as expected, which is something I guess.
Other than that Lua has been nice to work with and I feel like I am getting my head around the language pretty well.
Exercises
Easy exercises
Write a function called concatenate(a1,a2) that takes 2 arrays and returns a new array with all the elements of a1 followed by all the elements of a2.
Trivial. Lua didn’t get in the way at all here.
function concatenate (a1, a2)
for i=1,#a2 do
a1[#a1+i] = a2[i]
end
return a1
end
Our strict table implementation in Reading and Writing on p19 doesn’t provide a good way to delete items from the table. If we try the usual approach treasure.gold = nil, we get a duplicate key error. Modify strict_write to allow deleting keys (by setting their values to nil)
Again, the easy in the exercise titles seems appropriate. I simply modified the first line of the function so that instead of reading:
if _private[k] then
It read:
if _private[k] and v ~= nil then
Medium exercises
Change the global metatable you discovered in the Find section earlier so that any time you try to add 2 arrays using the plus sign (eg. a1+a2), Lua concatenates them together using your concatenate function
This one stretched my brain. I am still not sure if my solution was what the author was looking for. It took me a few hours, because I was thinking that I ought to be able to change the add function in the global environment metatable (G). When I realized that variables are just elements in the global table, and that they would have their own metatables, I saw that I could override the _newindex function in _G to add a new metatable to tables as they were created, things made sense.
This is by no means a perfect answer, as tables decared with local scope (i.e. local t = {}) won’t have the metatable applied to them. But it does work and to be honest my brain hurt by this point!
function newindex (t, k, v)
rawset(t,k,v)
if type(v) == "table" then
-- print "setting mt"
setmetatable(t[k]
, { add = concatenate
, _tostring = dumper.write
}
)
end
end
setmetatable(G,{newindex=newindex})
Using Lua’s built-in OO syntax, write a class called Queue that impements a FIFO queue as follows.
q=Queue.new() returns a new object
q:add(item) adds item past the last one currently in the queue
q:remove() removes and returns the first item in the queue or nill if it is empty
Compared to the previous question, this was a breeze and implemented in about 15 minutes. I used the Penlight library’s pretty printing capabilities here for debugging purposes.
dumper = require "pl.pretty"
Queue = { q = {} }
function Queue:new()
local obj = { }
setmetatable(obj, self)
self._index = self
return obj
end
function Queue:add(item)
if item == nil then
error "Can't add nil item to queue"
end
self.q[#self.q+1] = item
end
function Queue:remove()
if #self.q<1 then
return nil
else
fst = self.q[1]
for i=1,#self.q - 1 do
self.q[i] = self.q[i+1]
end
self.q[#self.q] = nil
return fst
end
end
function Queue:show()
dumper.dump(self.q)
end
Hard exercise
Using coroutines, write a fault tolerant function retry(count, body) that works as follows
Call the body function
If body yields a string with coroutine.yield(), consider this an error message and restart body from the beginning
Don’t retry more than count times; if you exceed count, print an error message and return
If body returns without yielding a string, consider this a success.
Again, this seemed way easier than the metatables exercise. Perhaps I missed something or creating a new coroutine per try is not what the author had in mind. I have added a debug variable at the top of the code as a convenience when I was building it — it toggles the display of my debug messages.
local debug=false
function retry(count, body)
local tries = 0
for i=1, count do
tries = i
_,res = coroutine.resume(coroutine.create(body))
if type(res) ~= "string" then
break
else
if(debug) then print("Err: " .. res) end
end
end
if(tries>=count) then
if(debug) then print("Failure after " .. tries .. " attempts") end
return
end
if(debug) then print("Succeeded after " .. tries .. " attempts") end
end
retry( 5
, function()
if math.random()>0.2 then
coroutine.yield("something bad happened")
end
print "Success!"
end
)
Wrapping up
So, that’s it for day two. Lua is a nice dynamic, scripting language so far. It is very minimalist and fundamentally structured around the simple but powerful table data structure.
Image of Roberto Ierusalimschy speaking at the Lua Workshop Toulouse 2013 from Wikipedia, taken by Alexander Gladysh and used under a Creative Commons licence.