Context
I'm trying to write a class system which will generate melodies by performing permutations on the notes and rhythms. I'm gonna use this in the tool "xStream" in the "Renoise" software. My example here is a really dumbed down and generic version from what I really have.
I'm also making this as a learning opportunity. My OOP skills are weak, and I still haven't completely wrapped my head around metatables. So a preemptive sorry if I'm totally missing something. I'm doing all my code from the style of the basic OOP examples in the 3rd edition of Programming in Lua.
My question
What I want to do is have class methods with similar functionality all grouped into 'nested' tables. Eg, a table of 'melody permutations', a table of 'rhythm permutations', a table of miscellaneous utility methods, etc.
In the code, I have a class SomeClass
. It has two types of print functions: print1_notes
is in the 'main table' (ie, a key of SomeClass
). print2.notes
is in a 'nested' table print2
, which is a key of SomeClass
(ie, notes
is just a key of SomeClass.print2
).
I can call print1_notes
just fine. The issue is when I call the print2
methods with the colon operator.
If I don't use sugar (eg, obj.print2.notes(obj)
), then no problem. But when I do (eg, obj.print2:notes()
), I get errors about "attempt to (blah blah) a function value ...".
- How come it works without the colon operator?
- Do methods in
SomeClass.print2
have access to the keys inSomeClass
? Do they have access the same way that the non-nested table keys do? - I was thinking that
print2
needs an__index
key to tell its methods to look up the keys inSomeClass
when it doesn't know whatself.a_key
is. Butself
(inside theprint2
methods) isn't a key. It's really just an alias forSomeClass
. Is it though? Seems like there's a discrepancy with the nested table. - Do I need to make SomeClass the metatable of print2? Is that even possible, since print2 isn't a separate table from SomeClass?
- Should I try a different approach? Maybe multiple inheritance?
Thanks. Sorry if this needs to be moved or if someone has asked this before.
My code
SomeClass = {
new = function (self, t)
t = t or {}
setmetatable(t, self)
self.__index = function (_, key)
return self[key]
end
--should I add a setmetatable here? perhaps:
--setmetatable(self.print2, self)
return t
end,
notes = {},
set_notes = function (self, t)
self.notes = t or {}
self.N = #self.notes
end,
print1_notes = function (self)
print("There are "..tostring(self.N).." notes :", table.unpack(self.notes))
end,
--table of different print functions
print2 = {
notes = function (self)
--is self an alias for SomeClass?
assert(self.notes, "Error: self.notes = nil")
print("There are "..tostring(self.N).." notes :", table.unpack(self.notes))
end,
first_note = function (self)
fn = self.notes[1]
print("first note is: ", fn)
end,
},
}
obj = SomeClass:new()
obj:set_notes{ 10,14,5, 10,14,5, 17 }
print("\ncalling print1_notes without sugar:")
obj.print1_notes(obj)
print("\ncalling print1_notes with sugar:")
obj:print1_notes()
print("\ncalling print2.notes without sugar")
obj.print2.notes(obj)
print("\ncalling print2.notes with sugar")
obj.print2:notes() --this gives an error: "attempt to get length of a function value"
obj.print2.first_note(obj) --this works fine
obj.print2:first_note() --this gives an error:
-- "attempt to index a function value (field 'notes')"
EDIT to code: instances of tostring(N)
needed to be replaced with tostring(self.N)
.
EDIT: weird errors has to do with the fact that SomeClass.print2.notes
has a notes member like SomeClass.notes
. SomeClass.print2.first_note
avoids this complication. (I'll explain more when I answer)
EDIT: I came up with a solution. It's not pretty, but it works. I'll post my answer below.