方法柯里化 |
|
foo:bar())不带调用部分(foo:bar)求值为柯里化闭包。目前我们的语义是
function object:wrap(method_name) local f = self[method_name] return function(...) return f(self, ...) end end
但是,如果我们能够做到以下事情,而不是这样做
f = foo:wrap("bar")
我们就可以简单地说
f = foo:bar;
Slact 提出了这个想法,我一直在琢磨解析器,看看实现起来有多容易。
优点:与现有代码完全兼容,以现有语法添加了一种使用匿名方法的简单方法。
缺点:每次方法调用都创建一个新的闭包会严重影响性能。可能应该优化这一点,只在下一个片段不是参数列表(表明我们正在保存它而不是调用它)时创建闭包。
在纯 Lua 中测试,包装 wrap(foo, 'bar') 和直接调用 nowrap(foo,'bar') -- 对于一个空函数 nowrap 之间有 106% 的速度差异。如果纯 Lua 的测试代表了在解析器中实现此功能的性能损失,那么保留当前对方法调用后参数的检查作为一种优化是有意义的。
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>
所有有趣的部分都在 lparser.c 中。需要更改 primaryexp 的代码生成。需要做两件事:如果我们有一个 foo:bar 结构,它需要生成代码将闭包留在栈顶——大部分上面已经列出,但之后需要进行一些清理。此外,实际闭合的函数需要存在于常量表中。我们有三种选择:
最后一种选择可能最简单,但可能导致创建大量相同的函数——需要对此进行测试。