lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



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.