Method Curry |
|
foo:bar()
) without the call part (foo:bar
) evaluate to curried closures. At present we have the semantics:
function object:wrap(method_name) local f = self[method_name] return function(...) return f(self, ...) end end
But it would be nice if instead of doing
f = foo:wrap("bar")
we could just say
f = foo:bar;
Slact came up with the idea, I've been noodling around with the parser a bit to see how easy it is to implement.
Advantages: fully compatible with existing code, adds and easy way to use anonymous methods with already existing syntax.
Disadvantages: creating a new closure at each method call is a serious performance hit. Should probably optimize that away and only create the closure if the next fragment isn't an argument list (indicating that we're saving it rather than calling it).
Tested in pure Lua, there's a 106% speed difference between wrapping wrap(foo, 'bar')
and plain calling nowrap(foo,'bar') -- for a do-nothing function nowrap
. If the tests from pure Lua are representative of the performance hit from coding this within the parser, it may make sense to retain the present check for arguments after a method call as an optimization.
Notes:
-- on changes to the grammar <ToxicFrog> Hmm. I think this changes primaryexp from: prefixexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } to prefixexp { '.' NAME | '[' exp ']' | ':' NAME | funcargs } -- on the emitted code <ToxicFrog> Table at 0 <ToxicFrog> SELF 0 0 <<field>> <ToxicFrog> CLOSURE 2 <<wrapper>> <ToxicFrog> MOVE 0 0 <ToxicFrog> MOVE 0 1 <ToxicFrog> CLOSE 0 <ToxicFrog> And then some stack cleanup. (FIXME: do I fully understand what CLOSE does?) <ToxicFrog> And <<wrapper>> is a function that looks like: <ToxicFrog> GETUPVAL 1 0 <ToxicFrog> GETUPVAL 2 1 <ToxicFrog> VARARG 3 0 <ToxicFrog> TAILCALL <ToxicFrog> 1 0 0 <ToxicFrog> RETURN 0 0 <ToxicFrog> So, the problems that need to be solved: <ToxicFrog> - emitting <<wrapper>> into the generated chunk <ToxicFrog> - making the stack consistent after we generate the closure </pre>
All the interesting bits are in lparser.c. Need to change the code generation for primaryexp. Two main things need to be done: if we have a foo:bar construct, it needs to emit code that leaves the closure on top of the stack - most of that's listed above, but some cleanup needs to be done after. Furthermore, the actual function it closes needs to be present in the constant table. We have three options here:
The last option is probably easiest, but could result in lots of identical functions being created - need to test this