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

This may not seem very reccomended, but it's the best I could think of, sorry for your expressions not having syntax highlighting...

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)



See Also

Lua 5.1


RecentChanges · preferences
edit · history
Last edited August 19, 2014 5:11 pm GMT (diff)