-
Notifications
You must be signed in to change notification settings - Fork 15
7ML7W Lua Day 2 Tables All the Way Down
Tuzz does a rubric with no small amount of panache, in the face of a torrent of interruptions and other varieties of unprofessionalism.
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 dryTill 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.
IT'S THE SAME THING LOL
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?
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)
else
error('Unknown key: ' .. key)
end
end
function strict_write(table, key, value)
print 'writing'
if rawget(table, key) then
error('Duplicate key: ' .. key)
else
rawset(table, key, value)
end
end
local mt = {
__index = strict_read,
__newindex = strict_write
}
treasure = {}
setmetatable(treasure, mt)
> treasure.gold = 50
writing
> treasure.gold = 50
> treasure.gold
50
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 }
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
end,
take_hit = function(self)
self.health = self.health - 10
end
__tostring = function()
return 'WOOOOO'
end
}
vs
Villain = {
health = 100,
new = function(self, name)
local obj = {
name = name,
health = self.health
}
setmetatable(obj, { __index = self, __tostring = self })
return obj
end,
take_hit = function(self)
self.health = self.health - 10
end
__tostring = function()
return 'WOOOOO'
end
}
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]
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.
[INDENTATION SNARK CENSORED]
Mudge: require, this is very JavaScript isn't it.
All: Isn't it.
THE END
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
.
[DIFFICULTY RANT CENSORED]
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.
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:
- Home
- Documentation
- Choosing a Topic
- Shows & Tells
- Miscellaneous
- Opt Art
- Reinforcement Learning: An Introduction
- 10 Technical Papers Every Programmer Should Read (At Least Twice)
- 7 More Languages in 7 Weeks
- Lua, Day 1: The Call to Adventure
- Lua, Day 2: Tables All the Way Down
- Lua, Day 3
- Factor, Day 1: Stack On, Stack Off
- Factor, Day 2: Painting the Fence
- Factor, Day 3: Balancing on a Boat
- Elm, Day 1: Handling the Basics
- Elm, Day 2: The Elm Architecture
- Elm, Day 3: The Elm Architecture
- Elixir, Day 1: Laying a Great Foundation
- Elixir, Day 2: Controlling Mutations
- Elixir, Day 3: Spawning and Respawning
- Julia, Day 1: Resistance Is Futile
- Julia, Day 2: Getting Assimilated
- Julia, Day 3: Become One With Julia
- Minikanren, Days 1-3
- Minikanren, Einstein's Puzzle
- Idris Days 1-2
- Types and Programming Languages
- Chapter 1: Introduction
- Chapter 2: Mathematical Preliminaries
- Chapter 3: Untyped Arithmetic Expressions
- Chapter 4: An ML Implementation of Arithmetic Expressions
- Chapter 5: The Untyped Lambda-Calculus
- Chapters 6 & 7: De Bruijn Indices and an ML Implementation of the Lambda-Calculus
- Chapter 8: Typed Arithmetic Expressions
- Chapter 9: The Simply-Typed Lambda Calculus
- Chapter 10: An ML Implementation of Simple Types
- Chapter 11: Simple Extensions
- Chapter 11 Redux: Simple Extensions
- Chapter 13: References
- Chapter 14: Exceptions
- Chapter 15: Subtyping – Part 1
- Chapter 15: Subtyping – Part 2
- Chapter 16: The Metatheory of Subtyping
- Chapter 16: Implementation
- Chapter 18: Case Study: Imperative Objects
- Chapter 19: Case Study: Featherweight Java
- The New Turing Omnibus
- Errata
- Chapter 11: Search Trees
- Chapter 8: Random Numbers
- Chapter 35: Sequential Sorting
- Chapter 58: Predicate Calculus
- Chapter 27: Perceptrons
- Chapter 9: Mathematical Research
- Chapter 16: Genetic Algorithms
- Chapter 37: Public Key Cryptography
- Chapter 6: Game Trees
- Chapter 5: Gödel's Theorem
- Chapter 34: Satisfiability (also featuring: Sentient)
- Chapter 44: Cellular Automata
- Chapter 47: Storing Images
- Chapter 12: Error-Correcting Codes
- Chapter 32: The Fast Fourier Transform
- Chapter 36: Neural Networks That Learn
- Chapter 41: NP-Completeness
- Chapter 55: Iteration and Recursion
- Chapter 19: Computer Vision
- Chapter 61: Searching Strings
- Chapter 66: Church's Thesis
- Chapter 52: Text Compression
- Chapter 22: Minimum spanning tree
- Chapter 64: Logic Programming
- Chapter 60: Computer Viruses
- Show & Tell
- Elements of Computing Systems
- Archived pages