【问题标题】:Element by Element comparison in LuaLua中逐个元素的比较
【发布时间】:2016-08-08 03:28:29
【问题描述】:

我正在尝试使用标准 < 运算符在 Lua 中进行逐个元素比较的方法。例如,这是我想做的:

a = {5, 7, 10}
b = {6, 4, 15}
c = a < b -- should return {true, false, true}

我已经有代码用于加法(以及减法、乘法等)。我的问题是 Lua 强制将比较结果与布尔值进行比较。我不想要一个布尔值,我想要一个表格作为比较的结果。

到目前为止,这是我的代码,加法有效,但小于比较无效:

m = {}
m['__add'] = function (a, b)
    -- Add two tables together
    -- Works fine
    c = {}
    for i = 1, #a do
        c[i] = a[i] + b[i]
    end
    return c
end
m['__lt'] = function (a, b)
    -- Should do a less-than operator on each element
    -- Doesn't work, Lua forces result to boolean
    c = {}
    for i = 1, #a do
        c[i] = a[i] < b[i]
    end
    return c
end


a = {5, 7, 10}
b = {6, 4, 15}

setmetatable(a, m)

c = a + b -- Expecting {11, 11, 25}
print(c[1], c[2], c[3]) -- Works great!

c = a < b -- Expecting {true, false, true}
print(c[1], c[2], c[3]) -- Error, lua makes c into boolean

Lua 编程手册说__lt 元方法调用的结果总是被转换为布尔值。我的问题是,我该如何解决这个问题?我听说 Lua 对 DSL 很好,我真的需要语法才能在这里工作。我认为使用 MetaLua 应该是可能的,但我不确定从哪里开始。

一位同事建议我只使用&lt;&lt; 而不是__shl 元方法。我尝试了它并且它有效,但我真的想使用&lt; 来减少,而不是使用错误的符号进行破解。

谢谢!

【问题讨论】:

  • 为什么需要它作为运营商?你不能只使用普通功能吗?
  • @Piglet 我正在制作 DSL。我希望能够做类似a*(b+5)/c&lt;d 之类的事情,其中​​abcd 都是向量(数组)。将其重写为前缀函数调用非常冗长:less_than(divide(multiply(a, add(b, 5),c),d)。我不能要求我的用户写那个。

标签: lua dsl lua-table metalua


【解决方案1】:

Lua 中的比较返回一个布尔值。

除了改变 Lua 的核心,你无能为力。

【讨论】:

  • 谢谢,我在手册中看到了。我想我希望我可以预先解析代码或其他东西。我可以用 MetaLua 做吗?
  • 关于从哪里开始修补 Lua 的任何建议。看起来比较运算符在内部没有返回任何内容,它们都被解释为跳转。即使在 a = 1 &lt; 2 的情况下,Lua 也使用条件跳转将 truefalse 分配给 a。
  • @jenny,没错。这并不简单。
【解决方案2】:

正如其他人已经提到的那样,没有直接的解决方案。但是,通过使用类似 Python 的通用 zip() 函数,如下所示,您可以简化问题,如下所示:

--------------------------------------------------------------------------------
-- Python-like zip() iterator
--------------------------------------------------------------------------------

function zip(...)
  local arrays, ans = {...}, {}
  local index = 0
  return
    function()
      index = index + 1
      for i,t in ipairs(arrays) do
        if type(t) == 'function' then ans[i] = t() else ans[i] = t[index] end
        if ans[i] == nil then return end
      end
      return table.unpack(ans)
    end
end

--------------------------------------------------------------------------------

a = {5, 7, 10}
b = {6, 4, 15}
c = {}

for a,b in zip(a,b) do
  c[#c+1] = a < b -- should return {true, false, true}
end

-- display answer
for _,v in ipairs(c) do print(v) end

【讨论】:

    【解决方案3】:

    你能忍受有点冗长的 v()-notation:
    v(a &lt; b) 而不是 a &lt; b 吗?

    local vec_mt = {}
    
    local operations = {
       copy     = function (a, b) return a     end,
       lt       = function (a, b) return a < b end,
       add      = function (a, b) return a + b end,
       tostring = tostring,
    }
    
    local function create_vector_instance(operand1, operation, operand2)
       local func, vec = operations[operation], {}
       for k, elem1 in ipairs(operand1) do
          local elem2 = operand2 and operand2[k]
          vec[k] = func(elem1, elem2)
       end
       return setmetatable(vec, vec_mt)
    end
    
    local saved_result
    
    function v(...)  -- constructor for class "vector"
       local result = ...
       local tp = type(result)
       if tp == 'boolean' and saved_result then
          result, saved_result = saved_result
       elseif tp ~= 'table' then
          result = create_vector_instance({...}, 'copy')
       end
       return result
    end
    
    function vec_mt.__add(v1, v2)
       return create_vector_instance(v1, 'add', v2)
    end
    
    function vec_mt.__lt(v1, v2)
       saved_result = create_vector_instance(v1, 'lt', v2)
    end
    
    function vec_mt.__tostring(vec)
       return 
          'Vector ('
          ..table.concat(create_vector_instance(vec, 'tostring'), ', ')
          ..')'
    end
    

    用法:

    a = v(5, 7, 10); print(a)
    b = v(6, 4, 15); print(b)
    
    c =   a + b ; print(c)  -- result is v(11, 11, 25)
    c = v(a + b); print(c)  -- result is v(11, 11, 25)
    c = v(a < b); print(c)  -- result is v(true, false, true)
    

    【讨论】:

    • 我只想说你的想法很棒。这不是我能想到的。很有创意!我不确定我是否可以使用它。我正在做一个 DSL,我认为这种语法会让人们感到困惑,例如有人会尝试:c = v((a &lt; b) == false) 它会崩溃。
    • (a &lt; b) == false 在您的 DSL 中是什么意思?您正在将向量与布尔值进行比较。
    • 它将对每个项目进行逐个元素的比较,例如{false, true, false} == false 将返回 {true, false, true}。同样,我在计算时没有问题,只需要帮助使语法可接受。
    • 要实现您自己的相等性,例如vector == boolean,您必须定义特殊值(对象)TRUEFALSE,因为它无法使用__eq 元方法来比较两种不同的类型.您可以对操作使用方括号语法:v[a &lt; b]v[a + b]。那么误写v[[a &lt; b] == FALSE]会产生语法错误。你也可以检测到这样的错误:(a &lt; b) == FALSE
    【解决方案4】:

    您只有两个选择来使用您的语法:

    选项 1:修补 Lua 核心。

    这可能会非常困难,而且将来会成为维护的噩梦。最大的问题是 Lua 在非常低的水平上假设比较运算符 &lt;&gt;==~= 返回一个布尔值。

    Lua 生成的字节码实际上会在任何比较时跳转。例如,c = 4 &lt; 5 之类的东西被编译成看起来更像if (4 &lt; 5) then c = true else c = false end 的字节码。

    您可以使用luac -l file.lua 查看字节码的样子。如果您将c=4&lt;5 的字节码与c=4+5 进行比较,您就会明白我的意思。加法代码更短更简单。 Lua 假设您将使用比较进行分支,而不是赋值。

    选项 2:解析代码、更改代码并运行

    这是我认为你应该做的。这将非常困难,预计大部分工作已经为您完成(使用类似LuaMinify)。

    首先,编写一个可用于比较任何事物的函数。这里的想法是,如果它是一个表格,则进行特殊比较,但其他一切都使用&lt;

    my_less = function(a, b)
       if (type(a) == 'table') then
         c = {}
         for i = 1, #a do
           c[i] = a[i] < b[i]
         end
         return c
        else
          return a < b
        end
    end
    

    现在我们需要做的就是将每个小于运算符 a&lt;b 替换为 my_less(a,b)

    让我们使用来自LuaMinify 的解析器。我们将使用以下代码调用它:

    local parse = require('ParseLua').ParseLua
    local ident = require('FormatIdentity')
    
    local code = "c=a*b<c+d"
    local ret, ast = parse(code)
    local _, f = ident(ast)
    print(f)
    

    所有这一切都会将代码解析为语法树,然后再次将其吐出。我们将更改 FormatIdentity.lua 以使其进行替换。将第 138 行附近的部分替换为以下代码:

        elseif expr.AstType == 'BinopExpr' then --line 138
            if (expr.Op == '<') then
                tok_it = tok_it + 1
                out:appendStr('my_less(')
                formatExpr(expr.Lhs)
                out:appendStr(',')
                formatExpr(expr.Rhs)
                out:appendStr(')')
            else
                formatExpr(expr.Lhs)
                appendStr( expr.Op )
                formatExpr(expr.Rhs)
            end
    

    仅此而已。它将用my_less(a*b,c+d) 替换c=a*b&lt;c+d 之类的东西。只需在运行时将所有代码推入即可。

    【讨论】:

    • 谢谢!我会试试你的代码,看看它是如何工作的。我喜欢这个想法,因为它给我留下了我想要的语法,这非常重要。
    猜你喜欢
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 2012-12-19
    • 2022-01-18
    • 2020-07-25
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    相关资源
    最近更新 更多