Statements In Expressions

lua-users home
wiki

In Lua, statements (including assignments and local variable declarations) cannot normally be placed in expressions but rather must be in separate statements. This contrasts to, for example, the C language, where one can have an expression that does an assignment as a side effect. This is often used for things like

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

or

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");

To take a Lua example consider this:

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

contains a single expression but is redundant and is normally simplified computationally only by moving code into an assignment in a separate statement:

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

or even

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

It's somewhat a matter of taste, but we lose the nicety of having the computation as a single expression (w = ...). The style becomes more [imperative].

There are various workarounds with closures and function/metatable side-effects (or even memoizing) to write it with a single expression, but they are not as efficient here and would typically be poor choices:

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

Or one could do

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

which is the same transformation Scheme uses with [let] and avoids creating the outmost upvalues.

Though this is not valid Lua syntax, it could be preferable to write this as a single expression as follows:

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

At least there are theoretical reasons why this would be useful, in writing programs in a functional style or for a program that modifies another Lua program, a la MetaLua. In fact Metalua incorporates a mechanism similar to this to allow more efficient code.

Notice the resemblance to Lisp:

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

and OCaml.

Pattern: Stored Expressions

We can achieve a similar effect to locals in expressions by having the expression call a function that then does some assignment. It can have a syntax like this:

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

Here is the implementation of StoredExpression:

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

This also allows things like

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

Some care may be needed since the order of execution of sub-expressions is not always defined.

--DavidManura, 2007-02. StoredExpression implementation was improved by RiciLake.

Proposal to extend Lua with "let"

The proposal as discussed with RiciLake is to add a new "let" construct to the Lua language for embedding statements, including local variable declarations, in an expression.

The proposed syntax is

let <chunk> in <expr>

where "let <chunk> in <expr>" acts as an expression (or expression list?) and where "let <chunk> in" acts like a low-precedence prefix operator (like not or # not with low precedence):

Locals in <chunk> are visible in <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

Let...in with Metalua

The let ... in ... syntax has been implemented in Metalua. See [1], and particularly this one [2].

Alternative Metalua proposal

In [3], another way to put statements where an expression is expected is presented. This Metalua extension defines a stat...end block, which can be put in an expression context. Its value as an expression is the parameter of the first return statement executed in the stat...end block.

Therefore, stat <foo> bar is semantically equivalent to ((function() foo end)()) in plain Lua. However, the Metalua implementation uses a much more efficient compilation, which doesn't involve the creation of a closure with upvalues.

For instance, print(stat local x=21; return 2*x end) will print 42, as would have the slower and less readable print(((function()local x=21; return 2*x; end)()))

Hack: Expression Stack

Warning: the following is academic and isn't really recommended for most situations.

Let's define the following functions:

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

We can then do

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

It's more terse, though at the expense of the function call overhead. That overhead might be removed if we made save/restore a built-in operation in Lua. It behaves somewhat like a stack as in [Forth] but with one element.

This concept might be extended to support more than one memory location:

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

We can then do things like

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'))

That seems like a complicated way of inefficiently reimplementing local variables, in which the variables aren't really local

Eventually we'd want to clear the saved table so it doesn't grow to infinity. There may be various approaches, such as using a circular queue or periodically clearing this table.

--DavidManura

Related stuff (older)

Here's another example:

-- 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

The values x, y, and z must be stored away in temporary variables before we can operate on them--that is, assuming we don't want to call rotate_coordinates three times:

--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

See Also

Lua 5.1


RecentChanges · preferences
edit · history
Last edited January 1, 2010 12:25 am GMT (diff)