Semantics and abstract syntax of lambdas

# David-Sarah Hopwood (17 years ago)

Michael Day wrote:

Hi Brendan,

Please read

strawman:lambdas

There is a lot of discussion over whether it is necessary to introduce syntax sugar instead of a "lambda" keyword, but is there any remaining controversy over the semantics of lambdas in JavaScript, or is that considered settled at this point?

Semantic or abstract-syntax issues that are not settled include:

  • how to avoid the hazard pointed out by Mark Miller where a value can be unintentionally leaked from the tail position of a lambda;

  • whether the primitive form of a lambda has a block, statement, or expression as its body;

  • some details of the desugaring of functions to lambdas, for example how to handle hoisting of 'var' declarations;

  • the interaction of 'let'/'const' declarations with lambdas, especially if the former do not have consistent semantics for all scopes;

  • whether lambdas are mutable, immutable, or can be either;

  • how 'break', 'continue', and 'return' are bound (which interacts with other proposals such as strawman:return_to_label, and how/whether user-defined control structures are to be supported);

  • the precise semantics of calling a lambda that contains a 'break' or 'continue' to a loop or statement that has already completed, or a 'return' from a function execution that has already completed;

  • how much of the feature set proposed on the strawman page (for example default and destructured arguments) is supported by the primitive form of a lambda, vs by desugaring.

The strawman page has suggested answers for some of these issues, but not all of them, and the answers given on that page are not fixed in stone.

# Brendan Eich (17 years ago)

On Dec 5, 2008, at 6:20 PM, David-Sarah Hopwood wrote:

Michael Day wrote:

Hi Brendan,

Please read

strawman:lambdas

There is a lot of discussion over whether it is necessary to
introduce syntax sugar instead of a "lambda" keyword, but is there any
remaining controversy over the semantics of lambdas in JavaScript, or is that considered settled at this point?

Semantic or abstract-syntax issues that are not settled include:

Your list of issues (snipped here to reduce context) is good, and
should be reflected in the wiki. Cc'ing Dave.

  • how to avoid the hazard pointed out by Mark Miller where a value can be unintentionally leaked from the tail position of a lambda;

Just to give credit where due, Waldemar was the Cassandra who
prophesied this hazard ;-).

The strawman page has suggested answers for some of these issues, but not all of them, and the answers given on that page are not fixed in stone.

That goes without saying -- the answers are fixed in straw! It's not
stoneman:lambdas :-P.

Tradtionally one switches from straw to iron or a stronger metal. At
least for Ada.

# Jon Zeppieri (17 years ago)

On Fri, Dec 5, 2008 at 9:20 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:

Semantic or abstract-syntax issues that are not settled include:

  • how to avoid the hazard pointed out by Mark Miller where a value can be unintentionally leaked from the tail position of a lambda;

Can we take a step back? Before determining "how to avoid the hazard," perhaps we should discuss whether it's worth avoiding.

First, a question for the people who see a hazard here: is the hazard somehow peculiar to the ES lambda proposal? Because from Mark's description, he would have equal justification for citing a hazard in the design of Scheme, Lisp, &c. Since I've never heard a schemer complain about this before, I'm more than a little skeptical of the danger.

Here is Brendan's example:

lambda (secret) { compute(secret); void 0; }

Brendan notes that "too few will remember to [add the void statement], so implicit return values will tend to leak."

Should I read this as "too few ES programmers, who aren't used to expression languages, will forget... &c."? If so, then why does anyone suspect that people unfamiliar and uncomfortable with expression languages will abandon functions for lambdas? If not, should I draw the conclusion that expression languages are intrinsically broken? (I'm not buying that.)

  • whether the primitive form of a lambda has a block, statement, or expression as its body;

The only problem with the proto-proposal to limit lambda bodies to expressions (which is otherwise a good idea) is that the ES expression sub-language isn't powerful enough. The ternary expression is awful for conditionals more complex than "if a then b else c." Without a let expression, people would be nesting immediately applied lambdas, which quickly becomes unwieldy. Without a letrec expression, they would be forced into vomit-inducing syntactic horrors to bind local recursive functions.

So, unless people want to expand the expression grammar significantly, I think the expression body is a nonstarter.

A statement body (as opposed to a block body, I mean) just seems bizarre. Does it have some advantage? For now, I think Dave's proposal wins.

# Yuh-Ruey Chen (17 years ago)

Jon Zeppieri wrote:

The only problem with the proto-proposal to limit lambda bodies to expressions (which is otherwise a good idea) is that the ES expression sub-language isn't powerful enough. The ternary expression is awful for conditionals more complex than "if a then b else c." Without a let expression, people would be nesting immediately applied lambdas, which quickly becomes unwieldy. Without a letrec expression, they would be forced into vomit-inducing syntactic horrors to bind local recursive functions.

So, unless people want to expand the expression grammar significantly, I think the expression body is a nonstarter.

How feasible would it be to make JS a pure expression language? That is, can the language be modified to allow things like:

x = if (a) b; else c;

first_test_prop = for (let p in o) if (/^test/.test(p)/) { p; break; }

# Lex Spoon (17 years ago)

On Mon, Dec 8, 2008 at 4:08 AM, Yuh-Ruey Chen <maian330 at gmail.com> wrote:

Jon Zeppieri wrote:

So, unless people want to expand the expression grammar significantly, I think the expression body is a nonstarter.

How feasible would it be to make JS a pure expression language? That is, can the language be modified to allow things like:

x = if (a) b; else c;

first_test_prop = for (let p in o) if (/^test/.test(p)/) { p; break; }

There's a simpler way to expand the expression language dramatically. I'm not sure why so few languages include it, and I'd be curious if it has been considered for Harmony.

The technique is to include an expression form which contains a sequence of statements followed by an expression. In Scala, the syntax looks like this:

{ statement1; statement2; result_expression }

So you can do things like:

{ val x = 1; val y = 2; x+y }

The result would be 3. I'm not sure what the best syntax would be for JavaScript, but surely there is room somewhere.

A light syntax for this kind of expression would immediately solve the issue with lambdas that result in expressions. This, in turn, honestly seems important for lambdas to really be convenient to use. (Granted, lambdas being convenient is much less important than having them available at all.)

There are side benefits to this expression form, too. It means you can much more frequently replace a function call by the function's contents, even when the function appears in an expression-only context. For example, you can more frequently inline the g() in f(g()). This makes programmers more dextrous, because their rewrites can be more localized. Further, it has obvious application for an optimizing compiler such as GWT's....

# Yuh-Ruey Chen (17 years ago)

Lex Spoon wrote:

On Mon, Dec 8, 2008 at 4:08 AM, Yuh-Ruey Chen <maian330 at gmail.com> wrote:

Jon Zeppieri wrote:

So, unless people want to expand the expression grammar significantly, I think the expression body is a nonstarter.

How feasible would it be to make JS a pure expression language? That is, can the language be modified to allow things like:

x = if (a) b; else c;

first_test_prop = for (let p in o) if (/^test/.test(p)/) { p; break; }

There's a simpler way to expand the expression language dramatically. I'm not sure why so few languages include it, and I'd be curious if it has been considered for Harmony.

The technique is to include an expression form which contains a sequence of statements followed by an expression. In Scala, the syntax looks like this:

{ statement1; statement2; result_expression }

So you can do things like:

{ val x = 1; val y = 2; x+y }

The result would be 3. I'm not sure what the best syntax would be for JavaScript, but surely there is room somewhere.

Unfortunately, "{" in expression context is taken by the object literal. However, I think "{{" would be feasible. That is, if the parser is in the expression context, if it encounters two '{' tokens in a row, it could treat it as you defined above, e.g.

val = {{ val x = 1; val y = 2; x + y }}

A light syntax for this kind of expression would immediately solve the issue with lambdas that result in expressions. This, in turn, honestly seems important for lambdas to really be convenient to use. (Granted, lambdas being convenient is much less important than having them available at all.)

Do you have an explicit syntax in mind?

I can turn this around and say that with really convenient lambdas, expressions of the form you discuss would be trivial, e.g.

val = fn { val x = 1; val y = 2; x + y }()

There are side benefits to this expression form, too. It means you can much more frequently replace a function call by the function's contents, even when the function appears in an expression-only context. For example, you can more frequently inline the g() in f(g()). This makes programmers more dextrous, because their rewrites can be more localized. Further, it has obvious application for an optimizing compiler such as GWT's....

Very true, and one of the reasons I dislike the distinction between statements and expressions.

# Lex Spoon (17 years ago)

On Wed, Dec 17, 2008 at 9:42 PM, Yuh-Ruey Chen <maian330 at gmail.com> wrote:

Unfortunately, "{" in expression context is taken by the object literal. However, I think "{{" would be feasible. That is, if the parser is in the expression context, if it encounters two '{' tokens in a row, it could treat it as you defined above, e.g.

val = {{ val x = 1; val y = 2; x + y }}

Cool. I will confess this particular syntax will likely invite confusion about when to use { } versus {{ }}, but maybe that's not true, or maybe there is some other light syntax that can be dreamed up.

A light syntax for this kind of expression would immediately solve the issue with lambdas that result in expressions. This, in turn, honestly seems important for lambdas to really be convenient to use. (Granted, lambdas being convenient is much less important than having them available at all.)

Do you have an explicit syntax in mind?

I can turn this around and say that with really convenient lambdas, expressions of the form you discuss would be trivial, e.g.

val = fn { val x = 1; val y = 2; x + y }()

That's very helpful, though it does seem roundabout.

It presupposes that lambdas take multiple statements, of course. I guess I'm saying that the one-expression version of lambda can lead to ultra-tight code when it applies, so it's a pity to give it up. However, with a light syntax for block expressions, the language can make lambda be one-expression but programmers can still easily write multi-statement lambdas. For example:

x => x+1

x => {{ var y = x+1; y*2 }}

The former is about as tight as can be, while the latter seems fine as a multi-statement syntax.

# Brendan Eich (17 years ago)

On Dec 18, 2008, at 6:45 AM, Lex Spoon wrote:

x => x+1 x => {{ var y = x+1; y*2 }}

There's no need to double braces in the => syntax if we require an

object initialiser to be parenthesized:

x => ({p: 1, q: true})

# Peter Michaux (17 years ago)

On Thu, Dec 18, 2008 at 8:43 AM, Brendan Eich <brendan at mozilla.com> wrote:

On Dec 18, 2008, at 6:45 AM, Lex Spoon wrote:

x => x+1 x => {{ var y = x+1; y*2 }}

There's no need to double braces in the => syntax if we require an object initialiser to be parenthesized:

x => ({p: 1, q: true})

Why cause such extra confusion for programmers?

Unfortunately I've lost most of my interest in this discussion as some folks seem to be desperately in need of some sort of new syntax. I just don't get it. The lambda(){} syntax would work just fine without any new feature conflicts. Even if all known conflicts have kludge workarounds to enable some terse sugary syntax, isn't anyone worried about the conflicts that will appear later?

Peter

# Brendan Eich (17 years ago)

On Dec 18, 2008, at 10:36 AM, Peter Michaux wrote:

On Thu, Dec 18, 2008 at 8:43 AM, Brendan Eich <brendan at mozilla.com>
wrote:

On Dec 18, 2008, at 6:45 AM, Lex Spoon wrote:

x => x+1 x => {{ var y = x+1; y*2 }}

There's no need to double braces in the => syntax if we require an
object initialiser to be parenthesized:

x => ({p: 1, q: true})

Why cause such extra confusion for programmers?

What "extra" confusion?

You already have to parenthesize an object initialiser in statement
context, e.g. as entire program source passed to eval in ES3+, or as
the body of an expression closure or let expression in JS1.7.

Why penalize every other lambda body with doubled braces for the
rare hard case of a lambda whose body is an object initialiser
expression? Hard cases make bad law.

Unfortunately I've lost most of my interest in this discussion as some

Yeah, we know. Don't assume I'm on board with => either. But this was

a bikeshedding exercise, as Lex allowed. And he made a better case for
=> as purtiest shed-color than anyone else has so far :-P. (Not to say

purtier than other colors, just the best case for => so far.)

folks seem to be desperately in need of some sort of new syntax. I just don't get it. The lambda(){} syntax would work just fine without any new feature conflicts. Even if all known conflicts have kludge workarounds to enable some terse sugary syntax, isn't anyone worried about the conflicts that will appear later?

I explicitly raised the issue Waldemar already raised, of conflicts or
future-proofing when extending the grammar informally.

But you can't sell lambda with fear of alternatives. The big, non-bike- shedding issue remains: is TCP-uber-alles, or even just the completion
value leakage hazard, a mismatch for most JS programmers? Rogue return
from lambda unwinding a function unexpectedly, dynamic escape- continuation errors, expression languages... Sure, Schemers and
similar expert folks love 'em. Are they worth the footgun-risks?

# Breton Slivka (17 years ago)

Okay, is it possible to introduce lambdas with no new syntax at all? possibility 1: for instance, suppose that we have a function that doesn't use arguments or "this". Can the implementation statically analyze a function to determine whether a function uses those, and if it does not, optimise it? Can we do stack optimized tail calls using the "return" keyword, via static analysis? Are there other ways for an implementation to examine how a function is used to optimize performance, without altering syntax or semantics?

possibility 2: This is probably a silly question, but, can we reuse object literal syntax?

mylambda = { let: {a:null, b:null, c:0}, call: { a+b+c } };

mylambda.call(2,2); //4 mylambda.call(3,3,3); //9

in this case, assigning a block to a property in an object literal is invalid. I must admit ignorance of how the parser/interpreter works here. Is it possible to take this context as a special case, and treat the block as a lambda body, and a let property as a parameters list with default values? I'm thinking about treating code as data here. Kind of like lisp's S-expression syntax: originally designed for data, but adapted to express programs.

are there other possibilities for expressing the lambda functionality using only the syntax that ecmascript already has?

# Brendan Eich (17 years ago)

On Dec 18, 2008, at 8:43 AM, Brendan Eich wrote:

On Dec 18, 2008, at 6:45 AM, Lex Spoon wrote:

x => x+1 x => {{ var y = x+1; y*2 }}

There's no need to double braces in the => syntax if we require an
object initialiser to be parenthesized:

x => ({p: 1, q: true})

GNU C statement expressions are bracketed by ({ and }) too. Not
recommending that as a lambda syntax (evaluation model would want it
to desugar to a lambda that's immediately applied with no argument).

Probably this GNU C extension is an recommendation against any
dissimilar addition to JS of ({Stmt; ...}), thinking a bit more ;-).

# Dave Herman (17 years ago)

Lex Spoon wrote:

The technique is to include an expression form which contains a sequence of statements followed by an expression. In Scala, the syntax looks like this:

{ statement1; statement2; result_expression }

So you can do things like:

{ val x = 1; val y = 2; x+y }

The result would be 3. I'm not sure what the best syntax would be for JavaScript, but surely there is room somewhere.

I mostly agree; I was originally going to propose this but then I
realized that since JS already has completion values (i.e., even
statements produce results-- I gather this was originally added so
that `eval' can take a syntactic representation of a Program and still
return a useful value) so I thought we should just reuse that instead
of adding a new, possibly unnecessary mechanism.

But I suspect the completion values are relatively obscure and not
really understood by users. Worse, there are some statement forms that
don't produce a completion value and some really obscure corner cases
-- including some that make it undecidable which substatement will
actually produce the completion.

So I would be interested in a simple syntactic form like Lex's
suggestion. Imagine for a moment the following idea didn't cause
parsing problems (it does, but bear with me). Say we had a sequence- expression form:

 { stmt; ... ; stmt; => expr }

and then add two kinds of block literals, a block-statement form and a
sequence-expression form:

 ^(x1,...,xn) { stmt; ... ; stmt }
 ^(x1,...,xn) { stmt; ... ; stmt; => expr }
  1. Tail position would only be a property of expressions, not
    statements [1]. That's simpler.

  2. There's no "accidental return" hazard because you have to
    explicitly use the => form.

  3. It doesn't break Tennent because the return position is a fixed
    part of the syntax, not a separate operator whose meaning gets
    captured by lambda.

It does make lambda-as-control-abstraction a few characters more
expensive. More immediately, this notation is obviously wildly
conflicting with the object-literal syntax. I probably should leave it
to the syntax warriors to figure out how to spell this in unambiguous
ASCII. But there's the idea.

Dave

[1] IOW, the only tail positions would be:

  • the tail expression of a sequence-expression
  • the two branches of a ? :
  • the second operand of the || operator