Now that metalua 0.4 has been released, I took a bit of time to play with syntax. I'm usually reluctant to superficial syntax tweaking: 90% of the time, it breaks source compatibility for a marginal or even negative increase in readability. However, it's impossible to identify the remaining 10% interesting cases unless you give them a fair shot. Hence I introduced two experimental extensions:
- xloop the extended loop syntax;
- xmatch the extended pattern matching syntax
xloop
The original idea behind xloop stemmed from Common Lisp's infamous
loop macro. This is an incredibly powerful and arcane macro. It's quite fun to hack it, but it's also a surefire way to get unmaintainable code in most team coding conditions. So I reduced its scope so dramatically that, well, it's got almost nothing left from the original loop :) But I've got what I believe is a sound base, on which I'll be able to gradually add new features to try.
The central idea is that a Lua loop consists of a loop header, followed by a body "do ... end". What I'm going to provide is the ability to chain several headers, so that you can write:
for i=0,9 for j=0,90,10 do
print (i+j)
end
instead of:
for i=0, 9 do
for j=0, 90, 10 do
print (i+j)
end
end
The headers can be:
for
, both in its numeric and enumerator versions. These are composed by nesting the loops, as shown above.while
, which gives a condition not to exit the loop.until
, which gives a condition to exit the loop.if
, which allows to zap an iteration of the loop without breaking it.
while
and
until
header elements act as conditionial
break
statements, whereas
if
acts as a conditional
continue
. If a
break
appears in the loop's body, all the (implicit) nested loops are broken at once.
Some things that might turn out to be useful idioms include "
for i=1,100 if not prime[i] do ... end
", or "
for i=1, 1000 while not canceled do ... end
", "
for i=1,10 for j=1,10 if i~=j do ... end
"...
This might look not-so-interesting, but I plan to go on playing with it, in case something truly interesting emerges. I think that introducing some
fold
and
map
operators, as alternatives or complements to the loop's body, might be interesting (they would be roughly equivalent to directives such as
collect
or
sum
for Lisp's loop macro). There's a point where too many directives will hurt readability, and you can only tell after you've tried, but that's why I'm not merging these extensions with metalua's more mature stuff: they're virtually guaranteed to be at least partially rolled back.
xmatch
Structural pattern matching is already a metalua extension. However, it's the one I used and tuned the most extensively, and it's directly inspired by established functional programming languages, so it's fairly mature. The painfully repetitive idioms I've met are:
- functions which solely consist of a match statement;
- contrived uses of match statements when I meant it to return an expression.
Moreover, patterns are only usable in match statements, where several alternative patterns might apply. In some cases, we know that a given pattern will match, we only want to use it as a way to destructurate a piece of data, in order to easily create assignments.
match functions
instead of:
function f(a, b, c)
match a, b, c with
| x1, y1, z1 -> foo()
| x2, y2, z2 -> bar()
end
end
you can write:
match function f
| x1, y1, z1 -> foo()
| x2, y2, z2 -> bar()
end
This is very similar to Lua's "
function f()... end
" as a shortcut for "
f=function()...end
". Still in Lua's spirit, if you want to create a local match function, as in "
local function f(...) match ... end end
", you can use "
local match function f ... end
".
anonymous match functions
Of course, you can also declare anonymous functions, this is not Python after all :)
f = match function
| x if x%2==0 -> return 'even'
| _ -> return 'odd'
end
expression match
You are now allowed to put a
match...with
where an expression is expected. In that case, the conditional blocks are replaced by conditional expressions:
print(match x with 1 -> 'one' | _ -> many)
. Notice that the
end
keyword in Lua terminates statement blocks, not expressions, and therefore isn't expected by the
match
expression.
Destructuring bind
This could be summed up as a single-case match statement which escapes its scope. Suppose that you know that
x
contains something of the form
`If{ cond, { stat1, stat2, ... } }
and you want to get the value of
cond
and the first statement of the body. You can write the full code:
assert(x.tag=='If')
cond = x[1]
stat = x[2][1]
Or you can use a bind:
bind `If{ cond, { stat, ... } } = x
. There is also a local bind, which declare the variables as new locals instead of merely assigning them:
local bind `If{ cond, { stat, ... } } = x
. Of course, if the pattern doesn't match, you get a runtime error.