如何在 Lua 函数上应用元表以启用它们之间的自定义运算符?
Posted
技术标签:
【中文标题】如何在 Lua 函数上应用元表以启用它们之间的自定义运算符?【英文标题】:How to apply meta-tables on Lua functions in order to enable custom operators between them? 【发布时间】:2019-08-03 13:16:01 【问题描述】:我正在尝试为特定函数设置一个元表,以启用自定义运算符以在它们之间进行函数组合。
我想在我的库中引入组合运算符的原因是因为我不想像这样将参数括号嵌套到另一个括号中:f(g(h(x)))
,而是:f * g * h(x)
,或者类似的东西不会需要嵌套括号。
到目前为止,我有两种方法可以实现我的目标:
尝试为 Lua 函数设置 __mul
元方法,我不确定具体如何。
通过为__call
和__mul
提供元方法,通过元表将所有可组合函数重新定义为可调用的功能组合表。
我为今天的第二种方法(功能复合表)写了一个实验(和一个工作)implementation。但我不认为它很优雅,它更像是一种 hack,在内存和处理方面的开销很大,只是语法糖。它是如此复杂以至于它有自己的调用堆栈队列,因为 Lua 评估自定义运算符左关联 而功能组合实际上是右关联(我在实现时错误地认为是这样,结果证明是必要的完全不同的原因。见 cmets)。
我目前使用函数和元表实现第一种方法的尝试如下所示:
local compositable =
__mul = function(a, b)
a(b)
end
local function f(x) return x*x end
local function g(x) return -x end
setmetatable(f, compositable) -- Error, table expected, got function.
setmetatable(g, compositable) -- "
local result = f * g(4)
print(result) -- Expected result: -16
但这不起作用,似乎只有 Lua 表和字符串才允许使用 Lua 中的元表设置。
【问题讨论】:
根据Wikipedia,函数组合是关联的,所以不需要使__mul
右关联。
@luther,哎呀。对不起,我会纠正这个。然而事实证明,出于不同的原因,我必须将此运算符实现为右关联(因为这样底层函数将从尚未评估的左侧复合表接收,而不是用于计算的实际数值)。今晚我会尝试更深入地研究这个并再次检查我的代码,看看我是否可以在没有右关联的情况下重新实现它。
因此,您实际上是在尝试通过在函数调用之前评估 *
来更改运算符优先级。我建议像(f * g)(x)
这样调用。更改运算符优先级可能会违反有关语法的基本假设,从而使代码难以阅读。请注意,您并不总是需要括号,因为您不会总是立即调用新函数。将组合函数存储到变量或将其作为参数传递是很常见的,就像匿名函数一样。
【参考方案1】:
虽然所有 Lua 值都可以有元表,但 setmetatable
仅 为表设置元表。表和完整的用户数据可以在单独的值上设置元表,而不同类型的值都共享每种类型的元表。例如,所有字符串都具有相同的元表。
所以你不能为一个函数设置一个元表,但你可以为所有函数设置一个元表。只有debug
库可以通过debug.setmetatable
做到这一点。同样,这将适用于所有功能。
【讨论】:
您好,Nicol,感谢您的回答。您认为在这种情况下将debug
库用于非调试目的是否合适?我在别处读到debug
库应该只用于自省和分析错误,因此最终用户不应依赖debug
。我想如果不改变 C 语言中的 Lua 本身,就没有优雅的方法来实现我想要的吗?
@DavidTamar:您实际上是在破解 Lua 以将其转换为您自己设计的语言,其中函数具有专门的运算符,这些运算符会导致常规 Lua 函数未知的行为。我想你可以称之为“内省”的一种形式。
嗨 Nicol,我想通知您,我关于函数组合的右关联性的假设实际上是不正确的,现在我在原始帖子中更正了这一点,感谢 Luther 的评论。 (请注意,尽管如此,我仍然必须将运算符实现为右关联,因为我不想将复合表而不是数值发送到底层函数中。但这不是我在开场问题中陈述的最初原因.)
你好 - “setmetatable 只设置表的元表” - 那不是真的。检查(Lua5.3):getmetatable(_VERSION)
您还可以更改它们或在字符串上设置例如 __call 元函数。
@koyaanisqatsi:您可以检索非表值的元表,但不能使用setmetatable
设置这样的元表。您必须使用 debug
库的函数版本。【参考方案2】:
如 cmets 中所述,您需要编写 (f * g)(x)
。然后下面的代码就可以了。
debug.setmetatable(function()end,
__mul=function (f,g) return function (x) return f(g(x)) end end)
function f(x) return x*x end
function g(x) return -x end
print((f*g)(2))
print((g*f)(2))
这段代码有点浪费,因为它在每次调用时都会创建一个复合函数。
【讨论】:
“它在每次调用中创建一个复合函数”——这让我很担心。我不想为代码维护造成巨大的性能开销。我还试图避免使用调试库来实际使用我的模块。今天我有了一个新的想法,就是用一个单独的函数来处理合成本身:composite(f, g, h) (x, y, z)
,我认为它更像 Lua 风格,而且它的实现相当简单而且不那么 hacky。你觉得有什么缺点吗?以上是关于如何在 Lua 函数上应用元表以启用它们之间的自定义运算符?的主要内容,如果未能解决你的问题,请参考以下文章