7ML7W Lua Day 2 Tables All the Way Down

Paul Mucur edited this page Jan 26, 2018 · 6 revisions

Tuzz does a rubric with no small amount of panache, in the face of a torrent of interruptions and other varieties of unprofessionalism.

The Poem

We debate reading Murray's Burns tribute but decide it would be questionable for any of us to effect a Scottish accent, so here it is for posterity:

O my Lua's like a red, red rose,
That's newly made perfect:
O my Lua's got a prototype,
That's us'd by a' the object.

As fair are thou, my bonie lang,
So deep in tables am I;
And I will code thee still, my dear,
Till a' the C's gang dry

Till a' the C's gang dry, my dear,
And the hashes merge wi' the arrays;
And I will code thee still, my dear,
While the errors o' funcs still raise.

And loop-thee-weel, my only Lua!
And loop-thee-weel, a while!
And I will code again in Lua,
Tho' it tak some time t'compile!


Some of the chapter didn't seem amazingly well motivated, but fundamentally it's trying to illustrate that with one simple underlying idea, you can pretty easily construct something quite powerful like an OO system. It's basically the same as when we built our own OO using records in TAPL.


The justification for "arrays and hashes, together at last!" seemed weak but maybe we just have to accept some degree of language boosterism.

Tom: How does key equality work? If I use a table or a function as a key, is it value equality, reference equality, what? I was too lazy to check.

Deleting keys by assigning nil to them seemed a bit weird - wouldn't you ever want to have a key with a nil value? Is it just not supported?

Some discussion of JavaScript the Scottish language ensues - we're not sure if it's directly comparable with e.g. undefined - can you set a property to undefined? We're too lazy to check.

Iteration - the pairs(t) function is interesting - it's a function that gets called over and over again until it returns nil. Under the hood it's a coroutine, but isn't explained as such in the book because we haven't got there yet.



my_array = {1,2,3}

Is the literal syntax just sugar then for {1=1,2=2,3=3}? Some suggestion that no, under the hood a different thing internally is actually being created, presumably for Reasons Of Performance.

You can mix the syntax - Tom suspects the storage is segregated so the array bit is separate from the hash, but presented as one thing.

Leo: Can I restore my sweet, sweet zero-indexing with my_array[0] = 'wat'?

Experimentation suggests that yes you can, but the ordering is WEIRD, i.e. the zeroth element gets stuck on the end.


Tom: Again the motivation is really weak. Why do we want custom lookup? Accessing by index and name is really powerful! That's, like, data structures for you!

So this is comparable to the Scottish Language's prototypes, to which the book refers so we're allowed to.

Mudge: presumably there is some underlying metatable that implements the default behaviour for all the things the metatable controls; it's a bit frustrating that you can't get access to it. What can you do?

Exercise spoiler: http://lua-users.org/wiki/MetatableEvents

First motivation - we can give tables a proper __tostring behaviour! NICE. We are ALL about strings at computation club.

We observe the implementation - array appending is a bit gross, no?

my_array = {1,2,3}

my_array[#my_array + 1] = thing_to_append

Mudge: the way we apply the behaviour to the metatable rather than the thing itself is interesting. Is it comparable to ruby's singleton classes? Not sure it's a useful comparison.

Leo: Can you set the metatable of a metatable?

Mudge: you can but it doesn't do the chaining the way you'd expect - you find out later but there's a special thing you need to override.

Tom: Not a huge fan of the aesthetics here - look at __tostring, it's not snake case or camel case or anything, it's just... what is it?

Joel: Why does it need to be double-underscored?

Good Q - the metatable is effectively its own namespace, why do you need this extra signifier?

Mudge: Just marks it as being special somehow? You never directly call these things, after all? e.g. you don't use __tostring, it just happens during coercion?

Tom: yeah, it seems to go straight to the metatable for behaviour - you can't call it directly on a table [we verify this]

Maciej: are they trying to segregate data and behaviour maybe?

The strict_write example

Mudge: the reason for the _private table here is that you're not actually overriding the getter/setter methods, they only get called if the key doesn't exist, so we have to do shenanigans.

A lot of debate ensues about whether this is the case, and indeed it is:

function strict_read(table, key)
  print 'reading'
  if rawget(table, key) then
    return rawget(table, key)
    error('Unknown key: ' .. key)

function strict_write(table, key, value)
  print 'writing'
  if rawget(table, key) then
    error('Duplicate key: ' .. key)
    rawset(table, key, value)

local mt = {
  __index = strict_read,
  __newindex = strict_write

treasure = {}
setmetatable(treasure, mt)
> treasure.gold = 50
> treasure.gold = 50
> treasure.gold

So yep, the raw behaviour takes over when the keys exist - think of it as similar to ruby Hash.new { |k| behaviour_when_no_k }

Prototypes and Villains

Why doesn't it copy take_hit in from the Villain? Is it just motivating the next step where we get to prototypes proper?

Next step: we're really baffled by the self.__index = self thing. So it's like... using itself as a backup lookup target for lookups on... itself? What's the point?

Mudge: [tries some pointing]

Tom: OHH - is it because it's, like, jumping over obj and [I got wholly lost at this point - @tomstuart please can you write that thing wot you said?]

Mudge does an excellent live coding and we work it out!

So, they've got:

setmetatable(obj, self)
self.__index = self

Okay, so the setmetatable call says "for any special __thing methods, look on self".

And then the __index def says "for any missing key lookups, look for them on self instead.

So, why can't you do:

setmetatable(obj, { __index = self })

We experiment, and you CAN. This works fine for looking up the object methods like take_hit(). And you're not modifying self which seems preferable. So why isn't this used? It's simpler - is the former an idiom?

Rich pinpoints the answer: with the former, if you define special methods like __tostring on the prototype, they're automatically picked up by the instance. If we didn't do this, we'd have to explicitly assign them in the setmetatable call:

Villain = {
  health = 100,

  new = function(self, name)
    local obj = {
      name = name,
      health = self.health

    setmetatable(obj, self)
    self.__index = self

    return obj

  take_hit = function(self)
    self.health = self.health - 10

  __tostring = function()
    return 'WOOOOO'


Villain = {
  health = 100,

  new = function(self, name)
    local obj = {
      name = name,
      health = self.health

    setmetatable(obj, { __index = self, __tostring = self })

    return obj

  take_hit = function(self)
    self.health = self.health - 10

  __tostring = function()
    return 'WOOOOO'

So that's nice.

While I transcribed this there was some FASCINATING discussion of syntactic sugar that I mostly missed.


It's 8pm at this point and we're all like "coroutines, yeah whatever grandad". But Rich, an ACTUAL GRANDAD [may not be an actual grandad] forces us to talk about Fibonacci, so we do.

Mudge: Did they really have to inline the assignments?

All: Yes. Otherwise you need an intermediate variable.

Mudge: ...

Tom: Okay but what IS a coroutine. Think of it as making a stack frame or an activation record or whatever you call them in your program, and storing that state somehow - the program counter and local variables, so when you yield out of that you can return to it later when you resume. You're making some space in memory to store all that data.

[I missed some error chat, the best kind of chat. Sorry.]

Tuzz: you're calling some global thing with coroutine.yield and coroutine.resume - how does it know where to go?

Tom: think of it as like return - you're going to your caller, which is determined through the call stack. So when you yield, you're going to your, er, resumer? Which is known by the runtime. And if the coroutine is finished, maybe resume is a no-op? [Ed: I'm too lazy to check]

A scheduler

Scheduler / item contract: ignoring the wrapper around yield, jobs basically yield a delay in which to perform the job again, and the scheduler resumes the action coroutine defined by the job.


Mudge: require, this is very JavaScript isn't it.

All: Isn't it.



Not many people seem to have done them. We don't have time (it's 8.17pm).

Mudge: As before, the difficulty is wonky, and the medium is WELL HARD. Some sort of magic table global __G c.f. JavaScript's window, and __G.__G is __G.


Little retrospective

Interstitial computer experiments worked well - taking time to futz around with stuff we didn't understand was good.

Joel: seating arrangements maybe not ideal - two rows means poor eye contact, so difficult to know when to speak.

Could we provide copies of the chapter so we don't necessarily need to see the screen?

Do we need to have read the chapter in advance? Ideally yes but it's NOT a requirement for coming along. If at least some of us have read it it'll be totally fine.

Tom: This isn't really about the book, but more preparation about who brings what? We're over-dipped today and under-breaded. Organisation!

What about the day? Are we voting again?

Rich: personal pref is is pick a day and stick with it, and it feels like Thursday has been somewhat picked. Predictability is useful if childcare is involved, and it's a lot of organiser overhead.

This is weighed against people who can't make specific days on a regular basis.

Conclusion: stick with Thursday for lua, maybe reconsider for the next lang? Okeydoke.

Next organiser: Axel volunteers! Thanks Axel!

Thanks also to Rich for organising and driving the screen, Tuzz for opening and closing remarks under almost impossible duress, and everyone else for turning up and saying excellent words in a perceptive order.

Post-meeting Bonus Oddity

Elena showed us how loading the exact same Lua file using either dofile or lua -l reversed the order that the keys and values of a table were printed. We were suitably aghast:

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.