[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Syntactic sugar for function parameters
- From: Rici Lake <lua@...>
- Date: Mon, 5 Feb 2007 11:26:15 -0500
On 5-Feb-07, at 10:35 AM, Fabien wrote:
Rici wrote:
Here's the exprlist problem:
foo(|x| sin(x), cos(x))
How many arguments am I passing to foo? If lambda bodies are
exprlists,
then that should be one argument (a lambda_expr which defines a
function of one argument with two results). If lambda bodies are
exprs,
then that is two arguments, the first being a function of one
argument,
and the second being an expression using a different x.
Traditionally, e.g. in ML dialects, Haskell or "plain old" lambda
calculus, lambdas have very low precedence, i.e. what you wrote is to
be read as a multireturn lambda. It's quite convenient, as the other
interpretation could safely be written "foo((|x| sin(x)), cos(x))":
only the last arg of an application can be treated as a multireturn.
That's the approach taken by metalua.
Well, that's fine. I note that in Lisp/Scheme -- which might or might
not be what you mean by "plain old" lambda calculus, but certainly a
venerable implementation of it -- you do end up with extra trailing
parentheses on a curry, although you can get around it with macros:
(lambda (x) (lambda (y) (cons x y)))
Your point about currying is reasonable, but I wonder if the common
case is currying or multiple return values.
I fail to find a practical example where currying impedes access to
multireturns, so I'd obviously choose currying, but clearly I'm
tainted with a functional background.
I'm not saying currying "impedes access" to multireturns. What I'm
saying is that in order to allow currying to work the way you want it
to, you're removing an 'end' marker which, in my opinion (although
others are free to disagree, and probably do), makes the code harder to
read.
Anyway, bottom lines are:
- parentheses canceling the multireturns break the principle of least
surprise, so I feel that they're one of the very few un-lua-ish
features of lua. I would have preferred a standard function "one =
|x,...| x" to explicitly cut the lists when I want to (there's been a
recent discussion here about multireturns being unusable in the middle
of a table, and no satisfying solution has been found IIRC).
I proposed a solution to this, including a sketch of a practical
implementation (although I admit that I haven't yet extracted the
practical implementation from a larger hack, I can assure you that it
is very easy to implement in a few lines.) To reiterate, the suggestion
is that ';' in a table-display causes the previous expression to be
expanded as a multireturn, while ',' causes it to be adjusted to a
single expression. Roughly speaking, the grammar for table-displays
becomes:
-- The second production is not quite accurate because the trailing
-- comma causes truncation of the last expression; writing it correctly
-- is tedious and doesn't serve any useful didactic purpose.
table-display ::= '{' table-list '}'
| '{' table-list ',' '}'
| '{' table-list ';' '}'
;
table-list-list ::= -- EMPTY
| table-list-list ';' table-list
;
table-list ::= key-assignment-list
| expression-list
;
key-value-list ::= key-value
| key-value-list ',' key-value
;
-- Right recursion; the last expression in the list is
-- treated as a multi-return.
expression-list ::= multi-expression
| expression ',' expression-list
;
key-value ::= NAME '=' expression
| '[' expression ']' '=' expression
;
The implementation of this is, as I said, straight-forward. When a
semi-colon is encountered, the compiler emits code to append the
current list of expressions, leaving the last one as multi-return. In
order to do this, it's necessary to keep the current table array
insertion point on the stack, instead of hard-coding it into the vm
code, so the opcode to create a new table is modified to use two stack
slots, putting the table in the first one and initializing the second
one to 1. The opcode which adds array values to a table uses the second
stack slot as the starting index, and updates it to the next starting
index. Although this uses one extra stack slot, it is roughly the same
speed as the existing code, and the compiler can be simplified
slightly.
I'm not sure that I agree that macros are better for producing new
control structures than blocks
They're more complex, but more powerful. Try to write a
continuation-based system, or a structural pattern matching control
without macros :) Both approaches have their respective strengths, and
I certainly won't deny that Smalltalk is one of the finest language
ever designed.
Sure, both are useful, and they can coexist. If one promotes
metaprogramming for the masses, as it were, then one might want
to come up with at least one option -- even if less powerful --
that is easy to use.
Besides, implementing smalltalk blocks from macros doesn't seem
difficult, except for the usual codewalker lack of compositionality
issue.
The complex part of implementing smalltalk blocks is the correct
handling of ^ (return) inside a block. This could be macro-expanded
into call/cc, if you have call/cc (actually, only a very limited form
of call/cc is necessary), but I don't see any obvious way of doing
that in Lua without introducing a coroutine; furthermore, the
coroutine solution may fail unexpectedly because Lua coroutines
lack delimiters.