The second day of Lua seemed much more interesting - and challenging! - than the first. We're finally in the realm of things that feel specifically Lua-ish, rather than generic to most scripting languages.
Lua dictionaries and arrays are essentially the same data structure, a key-value collection called a "table". If you provide the table with keys, it's a dictionary. If you leave out the keys, it creates them for you in the form of sequential numbers - so you can then access the values using a not-unfamiliar array square-bracket notation. What will put some noses out of joint, but is rather splendid in my opinion, is that array indexes start from 1: I'm in favour of anything that makes programming languages easier for ordinary humans to grok, and a[1] being the second element of an array, a[2] the third and so on, is really bad from this point of view. Just because we, as professionals, have gotten used to this and are never caught out by it any more doesn't make it a good feature. It just means Stockholm Syndrome has set in.
More intriguing than the tables themselves are the fact that they have "metatables", which can be used to override table default behaviour. So for instance if you want to define how the contents of your table are viewable to the world, you set "__tostring" in the metatable. We're shown how to redefine the default ability to read and write to a table with "__index" and "__newindex" in the metatable respectively. All handy stuff.
Of course this is all leading towards a demonstration of how to do object-oriented stuff in Lua. We see a table that acts as a prototype, with a "new" method that returns instance objects. The crucial lines look a little like black magic at first:
setmetatable(obj, self) self.__index = self"self" being the prototype object and "obj" the returned instance. But all it really means is that the instance can refer back up to its parent for methods that it doesn't find locally.
A brief mention of inheritance and syntactic sugar later, we move onto coroutines, which is what Lua uses for multitasking in lieu of threads. Generators seem to have gotten quite in vogue lately - even PHP has come round to them now - so it's useful stuff to be getting to grips with.
The exercises are more interesting this week, though partly a strange reason: the "hard" task was easier than the "medium" ones, possibly even than one of the "easy" ones! Either that or I completely missed the point somewhere, but it did feel like the author thinks of coroutines as harder to get to grips with than I found to be the case. Whereas it took me a long time reading up on the global metatable (which hasn't even been mentioned before these exercises) to get a picture of how to correctly use it to globally override an operator, and almost as long poring over examples of Lua OO to feel like my class was sufficiently elegant. Even the "easy" concatenate function took me longer than I was expecting, simply because it turns out to be the case that if you set "array2 = array1" then it is a reference, not a copy, and any changes to array2 will be made to array1 as well. The last thing I wanted to do was to pass two arrays to my "concatenate" function and have either of them altered in the process.
Exercises:
function concatenate(a1, a2) local add_values_to_array = function(from_array, to_array) for k, v in pairs(from_array) do to_array[#to_array + 1] = v end end local a3 = {} add_values_to_array(a1, a3) add_values_to_array(a2, a3) return a3 end
function strict_write(table, key, value) if _private[key] and value ~= nil then error("Duplicate key: " .. key) else _private[key] = value end end
setmetatable(_G, {__newindex = function(t, k, v) rawset(t, k, v) if type(v) == "table" then setmetatable(v, {__add = concatenate}) end end})
Queue = {} Queue.__index = Queue function Queue.new() local self = setmetatable({}, Queue) self.items = {} self.add = function(self, item) self.items[#self.items + 1] = item end self.remove = function(self) if #self.items == 0 then return nil end first = self.items[1] self.items[1] = nil for i = 1, #self.items do self.items[i] = self.items[i+1] end return first end return self end
function retry(count, body) local failures = 0 for i = 1, count do print("Try " .. i) generator = coroutine.create(body) _, value = coroutine.resume(generator) if value == nil then return end end print("Failed") end