表达式中的语句

lua-users home
wiki

在 Lua 中,语句(包括赋值和局部变量声明)通常不能放在表达式中,而必须放在单独的语句中。这与例如 C 语言形成对比,在 C 语言中,可以有一个表达式作为副作用进行赋值。这通常用于以下情况:

while((c = fgetc(fh)) != EOF) { fputc(c, fh2); }

double x, y, z;
if (strcmp(v, "0,0,0") == 0) printf("zeros\n");
else if(sscanf(v, "%f,%f,%f", &x, &y, &z) == 3) {
  printf("tuple (%d,%d,%d)\n", x, y, z);
}
else printf("unknown\n");

以 Lua 为例,考虑以下代码:

local w = (x+y+z)^2 + (x+y+z) + 1

包含单个表达式,但冗余,通常仅通过将代码移到单独语句中的赋值来简化计算。

local xyz = x+y+z
local w = xyz^2 + xyz + 1

甚至

local w; do 
  local xyz = x+y+z
  w = xyz^2 + xyz + 1
end

这在某种程度上是个人喜好问题,但我们失去了将计算作为单个表达式(w = ...)的优点。这种风格变得更加[命令式]

有各种使用闭包和函数/元表副作用(甚至记忆)的解决方法来用单个表达式编写它,但它们在这里效率不高,通常是糟糕的选择。

local w = (function() local xyz = x+y+z; return xyz^2 + xyz + 1 end)()

或者可以这样做

local w = (function(xyz) return xyz^2 + xyz + 1 end)(x + y + z)

这与 Scheme 使用[let] 进行的转换相同,并且避免创建最外层的 upvalues。

虽然这不是有效的 Lua 语法,但最好将其作为单个表达式编写,如下所示:

local w = let xyz = x+y+z in xyz^2 + xyz + 1

至少在理论上,这在以函数式风格编写程序或编写修改另一个 Lua 程序的程序(类似于MetaLua)时是有用的。事实上,Metalua 结合了一种类似于此的机制来允许更有效的代码。

注意它与 Lisp 的相似之处

(let ((xyz (+ x y z)))
  (+ (* xyz xyz) xyz 1)
)

和 OCaml。

模式:存储的表达式

我们可以通过让表达式调用一个函数来实现与表达式中的局部变量类似的效果,该函数然后进行一些赋值。它可以具有以下语法:

local ex = StoredExpression()
for _,v in ipairs{"4,5,6", "7,8,9", "0,0,0"} do
  if v == "0,0,0" then print("zeros")
  elseif ex(string.match(v, "(%d),(%d),(%d)")) then
    print("tuple", ex[1], ex[2], ex[3], "of size", ex.n)
  else
    print("unknown")
  end
end
-- Outputs: tuple   4       5       6       of size 3
--          tuple   7       8       9       of size 3
--          zeros

以下是 StoredExpression 的实现:

do
  local function call(self, ...)
    self.__index = {n = select('#', ...), ...}
    return ...
  end
  function StoredExpression()
    local self = {__call = call}
    return setmetatable(self, self)
  end
end

这也允许以下操作:

result = ex(math.random()) and (ex[1] < 0.3 and "low" or
                                ex[1] > 0.7 and "high" or
                                "med")

可能需要小心,因为子表达式的执行顺序并不总是定义的。

--DavidManura,2007-02。StoredExpression 的实现由 RiciLake 改进。

用“let”扩展 Lua 的提案

RiciLake 讨论的提案是在 Lua 语言中添加一个新的“let”结构,用于在表达式中嵌入语句,包括局部变量声明。

建议的语法是

let <chunk> in <expr>

其中“let <chunk> in <expr>”充当表达式(或表达式列表?),而“let <chunk> in”充当低优先级前缀运算符(如 not#,但优先级较低)。

<chunk> 中的局部变量在 <expr> 中可见。

-- typical usage
y = let local t = complex_function(x) in t and g(t)

-- any statement (not just local variable declarations) can be used
y = let local x = 5; print("hello") in x*2

-- can be nested
y = let local x = 5 in let local y = x in y*2  -- sets y=10

-- useful when declaring closures this way
local func =
  let
    local x = 10
  in function()
    x = x + 1
    return x
  end

local y =
  let local x = 0
      for _,v in pairs(t) do x = x + v end
  in  x+x^2+x^3

-- using let with tuple proposal
t[let x... = 1,2,3 in x] = true

-- if statments:
local y
if x == 1 then
  print(x)
elseif let y = compute(x) in y > z then
  print("more", y)
elseif y < -z then
  print("less", y)
end

--DavidManura

Metalua 中的 let...in

Metalua 中已经实现了 let ... in ... 语法。参见 [1],特别是这个 [2]

Metalua 的另一种方案

[3] 中,介绍了另一种将语句放在需要表达式的地方的方法。这个 Metalua 扩展定义了一个 stat...end 块,它可以放在表达式上下文中。它作为表达式的值是 stat...end 块中执行的第一个 return 语句的参数。

因此,stat <foo> bar 在语义上等同于普通 Lua 中的 ((function() foo end)())。但是,Metalua 的实现使用了一种更高效的编译方式,它不涉及创建带有 upvalue 的闭包。

例如,print(stat local x=21; return 2*x end) 将打印 42,就像速度较慢且可读性较差的 print(((function()local x=21; return 2*x; end)())) 一样。

技巧:表达式栈

警告:以下内容是学术性的,不建议在大多数情况下使用。

让我们定义以下函数

local save, restore; do
  local saved
  save = function(value) saved = value; return true end
  restore = function() return saved end
end

然后我们可以做

local z = save(x+y+z) and restore()^3 + restore() + math.sqrt(restore())

它更简洁,但以函数调用开销为代价。如果我们使保存/恢复成为 Lua 中的内置操作,则该开销可能会被消除。它的行为类似于 [Forth] 中的栈,但只有一个元素。

这个概念可以扩展到支持多个内存位置

local save, restore do
  local saved = {}
  let = function(name, value) saved[name] = value; return true end
  get = function(name) return saved[name] end
end

然后我们可以做以下事情

local z =
  let('n', x+y+z) and
  let('m', x^2+y^2+z^2) and
  get('n')^3 + get('n') + math.sqrt(get('m'))

这似乎是一种复杂且低效地重新实现局部变量的方式,其中变量实际上不是局部的。

最终,我们希望清除保存的表,以防止它无限增长。可能有多种方法,例如使用循环队列或定期清除此表。

--DavidManura

相关内容(较旧)

以下是一个示例

-- How I might like to write it
-- Assuming rotate_coordinates() returns a tuple of three numbers.
-- Note: Invalid Lua.
function transform_object(o)
  return is_vector(o) and do
    local x, y, z = rotate_coordinates(o[x], o[y], o[z])
    return {x*2, y*2, z*2}
  end or o*2
end

在对 x、y 和 z 进行操作之前,必须将它们存储在临时变量中——也就是说,假设我们不想调用 rotate_coordinates 三次。

--Yuck
function transform_object(o)
  return is_vector(o) and {
      select(1, rotate_coordinates(o[x], o[y], o[z])) * 2,
      select(2, rotate_coordinates(o[x], o[y], o[z])) * 2,
      select(3, rotate_coordinates(o[x], o[y], o[z])) * 2
  } or o*2
end

这可能看起来不太推荐,但这是我能想到的最好的方法,对于您的表达式没有语法高亮,我表示歉意...

function Let(statement)
   local locals = {}
   return function(In)
      return function(expression)
         if In == "In" or In == "in" then
            table.insert(locals, statement)
            local func = load(locals[1] .. ' return ' .. expression)
            return func()
         else
            error("'In' or 'in' expected near " .. In, 2)
         end
      end
   end
end

val = Let 'local x = 10' 'In' 'x - x'
local val2 = Let 'local x = 9' 'In' 'x * x'
print(Let 'local x = 5' 'In' 'x + x')

print(val + 1 + val2)

另请参阅

Lua 5.1


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2014 年 8 月 19 日下午 6:11 GMT (差异)