Statements In Expressions |
|
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.
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.
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
The let ... in ...
syntax has been implemented in Metalua. See [1], and particularly this one [2].
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)()))
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.
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)
Lua 5.1