表達式中的敘述

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] 所使用的相同轉換方式,且避免建立最外層上值。

儘管這不是有效的 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 改善。

建議擴充 Lua 以納入「let」

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

let ... in ... 語法已在 Metalua 中實作。請參閱 [1],特別是 [2]

替代的 Metalua 建議

[3] 中,提出了另一種方法,可以在預期表達式的部分放置陳述式。此 Metalua 擴充功能定義了一個 stat...end 區塊,此區塊可以放置在表達式環境中。作為表達式,其值是 stat...end 區塊中執行的第一個 return 陳述式的參數。

因此,在一般 Lua 中,stat <foo> bar 在語義上等於 ((function() foo end)())。不過,Metalua 實作使用更有效率的編譯,不需要建立帶有上值的閉包。

例如,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


RecentChanges · 偏好設定
編輯 · 歷史記錄
最後編輯時間為 2014 年 8 月 19 日下午 6:11 GMT (差異)