Block lambda grammar: BlockArguments

# Axel Rauschmayer (14 years ago)

strawman:block_lambda_revival

I’m trying to understand the syntax: BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( InitialValue )

  • Wouldn’t this allow the following? BlockLambda [no LineTerminator here] BlockLambda

  • InitialValue means that paren-free can be combined with arguments that aren’t blocks, right?

    myLoopFunc(initValue1)(initValue2) { | arg1, arg2 | ... }

    I think I would prefer the following (IIRC, more like Ruby):

    myLoopFunc(initValue1, initValue2) { | arg1, arg2 | ... }

# Brendan Eich (14 years ago)

Axel Rauschmayer <mailto:axel at rauschma.de> January 12, 2012 11:16 PM strawman:block_lambda_revival

I’m trying to understand the syntax: BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( InitialValue )

  • Wouldn’t this allow the following? BlockLambda [no LineTerminator here] BlockLambda

Yes.

  • InitialValue means that paren-free can be combined with arguments that aren’t blocks, right?

Yes.

myLoopFunc(initValue1)(initValue2) { | arg1, arg2 | ... }

No, the myLoopFunc(initValue1) is a CallExpression -- see

CallWithBlockArguments :

 CallExpression [no LineTerminator here] BlockArguments

The return value of that ordinary CallExpression is the callee of the paren-free call.

I think I would prefer the following (IIRC, more like Ruby):

myLoopFunc(initValue1, initValue2) { | arg1, arg2 | ... }

That parses, as described above. The two-argument CallExpression must return a function that takes the block arguments.

I see a problem in the grammar in the strawman, now that you mention it: no way to produce a simple identifier callee from CallExpression, so no

map {|e| e*e}

only

v.map {|e| e*e}

or

get_map() {|e| e*e}

or similar. I will fix.

# Brendan Eich (14 years ago)

Brendan Eich <mailto:brendan at mozilla.org> January 12, 2012 11:39 PM v.map {|e| e*e}

Er, not even that -- Arguments required in a CallExpression, so v().map or v.map() but not just v.map. Fixes coming tomorrow.

# Brendan Eich (14 years ago)

Fixed: doku.php?id=strawman:block_lambda_revival&do=diff

The LeftHandSideExpression productions and their kids (NewExpression and CallExpression) are funky and I keep misremembering how NewExpression is what bottoms out via MemberExpression -> PrimaryExpression at Identifier.

# Axel Rauschmayer (14 years ago)

Fixed: doku.php?id=strawman:block_lambda_revival&do=diff

The LeftHandSideExpression productions and their kids (NewExpression and CallExpression) are funky and I keep misremembering how NewExpression is what bottoms out via MemberExpression -> PrimaryExpression at Identifier.

If I read the grammar correctly, then you can do things such as (read "~~~>" as "desugars to"):

 myfunc {|| } {|| } (arg3) (arg4)  ~~~>  myfunc({|| }, {|| }, arg3, arg4)

The above is a function call with 4 arguments. My wish would be different: I would want to put lambdas after a function or method call and treat those lambdas as additional arguments:

 myfunc(arg1, arg2) {|| } {|| }  ~~~>  myfunc(arg1, arg2, {|| }, {|| })
 myfunc {|| } {|| }  ~~~>  myfunc({|| }, {|| })

Rationale: I would always make lambdas trailing arguments, similar to if (cond) {} {} And I would rather achieve this effect without currying. Following a block with a non-block doesn’t seem like a good idea.

Has the other approach been considered?

# Brendan Eich (14 years ago)

Axel Rauschmayer <mailto:axel at rauschma.de> January 13, 2012 9:09 PM

If I read the grammar correctly, then you can do things such as (read "~~~>" as "desugars to"):

 myfunc {|| } {|| } (arg3) (arg4)  ~~~>  myfunc({|| }, {|| }, 

arg3, arg4)

The above is a function call with 4 arguments. My wish would be different: I would want to put lambdas after a function or method call and treat those lambdas as additional arguments:

 myfunc(arg1, arg2) {|| } {|| }  ~~~>  myfunc(arg1, arg2, {|| }, 

{|| }) myfunc {|| } {|| } ~~~> myfunc({|| }, {|| })

The closing parenthesis after arg2 really ought to mean end of formal parameter list. Anything else is too magical.

Rationale: I would always make lambdas trailing arguments, similar to if (cond) {} {} And I would rather achieve this effect without currying.

Why should foo(arg1)(arg2) and foo(arg1){||arg2} differ?

Your use-case is satisfied by returning a function (memoized, singleton even), but the symmetry between (arg1, ... argN) and space-separated BlockArguments should not be broken.

Following a block with a non-block doesn’t seem like a good idea.

This was an explicit goal, in order to support use-cases including setTimeout and promises APIs.

Has the other approach been considered?

Yes, see

esdiscuss/2011-May/014675

# Brendan Eich (14 years ago)

Brendan Eich <mailto:brendan at mozilla.org> January 14, 2012 9:41 AM

Axel Rauschmayer <mailto:axel at rauschma.de> January 13, 2012 9:09 PM

If I read the grammar correctly, then you can do things such as (read "~~~>" as "desugars to"):

 myfunc {|| } {|| } (arg3) (arg4)  ~~~>  myfunc({|| }, {|| }, 

arg3, arg4)

The above is a function call with 4 arguments. My wish would be different: I would want to put lambdas after a function or method call and treat those lambdas as additional arguments:

 myfunc(arg1, arg2) {|| } {|| }  ~~~>  myfunc(arg1, arg2, {|| }, 

{|| }) myfunc {|| } {|| } ~~~> myfunc({|| }, {|| })

The closing parenthesis after arg2 really ought to mean end of formal parameter list. Anything else is too magical.

s/formal/actual/

# Axel Rauschmayer (14 years ago)
  myfunc(arg1, arg2) {|| } {|| }  ~~~>  myfunc(arg1, arg2, {|| }, {|| })
 myfunc {|| } {|| }  ~~~>  myfunc({|| }, {|| })

The closing parenthesis after arg2 really ought to mean end of formal parameter list. Anything else is too magical.

I think that’s how Ruby does it. I’m on the fence. It is indeed quite magical (and not the good kind of magical).

Why should foo(arg1)(arg2) and foo(arg1){||arg2} differ?

BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( InitialValue )

I still don’t fully understand the ( InitialValue ) at the end – it’s a single value in parens that can come after several blocks. If Ruby-style magic isn’t an option then I would expect (and prefer) that there were only two calling “modes”:

  • Traditional: myfunc(arg1, arg2, ...}
  • Paren-free – lambdas only: myfunc {|| body1} {|| body2} ...

Following a block with a non-block doesn’t seem like a good idea. This was an explicit goal, in order to support use-cases including setTimeout and promises APIs.

With the above I meant: In paren-free mode, following a block with a non-block doesn’t seem like a good idea. With a normal paren call, I don’t see any problems.

# Brendan Eich (14 years ago)

Axel Rauschmayer <mailto:axel at rauschma.de> January 14, 2012 1:32 PM

  myfunc(arg1, arg2) {|| } {|| }  ~~~>  myfunc(arg1, arg2, {|| 

}, {|| }) myfunc {|| } {|| } ~~~> myfunc({|| }, {|| })

The closing parenthesis after arg2 really ought to mean end of formal parameter list. Anything else is too magical.

I think that’s how Ruby does it.

Indeed:

$ ruby def foo(a,b) yield a+b end foo(1,2) {|x| puts x} 3D

I’m on the fence. It is indeed quite magical (and not the good kind of magical).

It's way too magical in my view to retrofit to JS. Again, making "if" and "while" and so on into block-lambda calling functions seems a wild goose chase. These forms take statements, not just block-statements (and not block-lambdas to call). They're built into the language via the C-like syntax.

Imitating "if" etc. is fine. It's a motivation for the paren-free and semicolon-free CallWithBlockArguments statement form.

And such imitation is doable via the strawman grammar, simply by returning a function (currying a bit). Singletons or pairs can be used if there's no need to retain the "if condition":

function myIf(cond) { // The block-lambdas here are singletons (no upvars)... return cond ? {|t, e| t} : {|t, e| e}; }

myIf (x > y) {|| "greater"} {|| "not greater"}

Of course, one might want a dummy "else" ("myElse") in between the then and else blocks.

Why should foo(arg1)(arg2) and foo(arg1){||arg2} differ?

BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( InitialValue )

I still don’t fully understand the ( InitialValue ) at the end – it’s a single value in parens that can come after several blocks. If Ruby-style magic isn’t an option then I would expect (and prefer) that there were only two calling “modes”:

  • Traditional: myfunc(arg1, arg2, ...}
  • Paren-free – lambdas only: myfunc {|| body1} {|| body2} ...

The objection (if you read the whole thread containing the message I cited, I think you'll find it) was that requiring only block-lambdas for the paren-free call form means expression arguments, even ones as simple as numeric literals, must be bracketed by {|| and }. Why not allow ( and ) instead?

One counter-argument is that this looks the argument list of a call expression whose callee is everything to the left. But paren-free call syntax supports newline termination, so I added the ... ( Initial Value ) production to satisfy setTimeout-like use cases without requiring {|| and | as brackets.

If this alternate production bites back in some way, or is simply under-used or too surprising, I'll yank it.

Indeed I could simplify CallWithBlockArguments to take only one BlockLambda argument but that seems unnecessarily restrictive. More comments welcome.

Following a block with a non-block doesn’t seem like a good idea. This was an explicit goal, in order to support use-cases including setTimeout and promises APIs.

With the above I meant: In paren-free mode, following a block with a non-block doesn’t seem like a good idea. With a normal paren call, I don’t see any problems.

The consequences might include

setTimeout {|arg| ...} {|| 1000} {|| x}

That is a bit harsh since 1000 (one second) is a literal and /* compute arg here */ is typically simply a pass-by-value reference to a variable in the scope of this setTimeout call, e.g. x in this example.

The strawman therefore supports

setTimeout {|arg| ...} (1000) (x)

for example.

Could we go further and support certain primary expressions, namely all of the PrimaryExpression right-hand sides except for object literal?

NonObjectLiteralPrimaryExpression : this Identifier Literal ArrayLiteral ( Expression )

?

Not a problem grammatically except for the added complexity, and the specific usability issues arising from that slight complexity:

  • Leaving out object literal means one must parenthesize an object literal but not an array literal.
  • Allowing K but not K+1, requiring instead (K+1), may blow back with users.
# Axel Rauschmayer (14 years ago)

If Ruby-style magic isn’t an option then I would expect (and prefer) that there were only two calling “modes”:

  • Traditional: myfunc(arg1, arg2, ...}
  • Paren-free – lambdas only: myfunc {|| body1} {|| body2} ...

The objection (if you read the whole thread containing the message I cited, I think you'll find it) was that requiring only block-lambdas for the paren-free call form means expression arguments, even ones as simple as numeric literals, must be bracketed by {|| and }. Why not allow ( and ) instead?

(*) One counter-argument is that this looks the argument list of a call expression whose callee is everything to the left. But paren-free call syntax supports newline termination, so I added the ... ( Initial Value ) production to satisfy setTimeout-like use cases without requiring {|| and | as brackets.

If this alternate production bites back in some way, or is simply under-used or too surprising, I'll yank it.

Three arguments in favor of yanking:

  • Thanks to lambdas, setTimeout already looks very nice – even as a parenthesized function call.

  • Putting callable values last is more likely, the grammar rule introduces an odd asymmetry, by not allowing leading a leading ( InitialValue ) . I’m not saying that that would be a desirable feature (currying is fine here), just that trailing non-lambdas seem much rarer than leading non-lambdas.

  • The counter-argument (*) you mention above weighs heavily. Most people (certainly me) probably expect a function or method call when they see a parenthesized value.

The following seems like an elegant and easy to understand rule to me: “The parameters of a function or method call are either parenthesized or paren-free. In the latter case, all parameters must be lambdas.”

# Brendan Eich (14 years ago)

Axel Rauschmayer <mailto:axel at rauschma.de> January 14, 2012 4:58 PM

Three arguments in favor of yanking:

  • Thanks to lambdas, setTimeout already looks very nice – even as a parenthesized function call.

Good -- and you make a good case here in terms of paren-counting:

setTimeout {|arg| ...} (1000) (x)

is worse by paren-count cost than:

setTimeout({|arg| ...}, 1000, x)

and with the style I expect where there's a space between the setTimeout and the {, worse in total character count even including the commas required in the second form.

Let B be the number of block-lambdas and I be the number of InitialValue expressions. In general we have B spaces (one before each block-lambda) and 2I parens and I spaces (one before each parenthesized InitialValue) or B+3I overhead with the paren-free syntax in the expected style, vs. 2 parens around the whole arg list + 2(B+I-1) ", " pairs between args, for 2(B+I). So the trade-off relation is

paren-free <=> parenthesized

B+3I <=> 2B+2I or

I <=> B

So if I < B, paren-free wins. If I >= B, parenthesized wins by I-B

chars. This ignores readability trade-off between ", " separation and " (" with ")" on the right of each InitialValue.

  • Putting callable values last is more likely, the grammar rule introduces an odd asymmetry, by not allowing leading a leading ( InitialValue ) . I’m not saying that that would be a desirable feature (currying is fine here), just that trailing non-lambdas seem much rarer than leading non-lambdas.

Agreed.

  • The counter-argument (*) you mention above weighs heavily. Most people (certainly me) probably expect a function or method call when they see a parenthesized value.

I think you are right.

The following seems like an elegant and easy to understand rule to me: “The parameters of a function or method call are either parenthesized or paren-free. In the latter case, all parameters must be lambdas.”

Given the overhead trade-off relation, I'm ditching the whole ( InitialValue ) alternative. Thanks for poking at this!

# Brendan Eich (14 years ago)

Brendan Eich <mailto:brendan at mozilla.org> January 14, 2012 6:31 PM

Given the overhead trade-off relation, I'm ditching the whole ( InitialValue ) alternative. Thanks for poking at this!

Done: doku.php?id=strawman:block_lambda_revival&rev=1326440792&do=diff

# Axel Rauschmayer (14 years ago)

Cool. I also have a feeling that every paren-free lambda after the first one should have some kind of preceding keyword or name, but I don’t know how one could make that happen. Brace-free object literals? (only half joking)

# Brendan Eich (14 years ago)

Nothing is going to match Smalltalk on this. Keyword parameters are not even on the map because object literals suck the oxygen out of the room. And then you'll want to get rid of the || for empty block parameters.

I say pause with a simpler design and digest, ruminate, prototype, user-test.

# Herby Vojčík (14 years ago)

-----Pôvodná správa---

# Brendan Eich (14 years ago)

Herby Vojčík <mailto:herby at mailbox.sk> January 15, 2012 1:24 AM

"Nothing is going to match Smalltalk on this. Keyword parameters are not even on the map because object literals suck the oxygen out of the room. And then you'll want to get rid of the || for empty block parameters."

Sorry for this question, it is more a meta one. The paragraph above seems too dense for me to digest (and I thought my English is pretty good). Would it be possible to explain a little more? Like what is meant by "going to match"

"be as good as" Smalltalk.

and what is "this" in "on this;

"on keyword selector syntax".

and things like "not in the map"

No strawman or even idle chatter about adding keyword parameter passing to JS.

, "o.l. suck the oxygen out of the room", and the last sentence, too.

We have discussed here and in TC39 how {key: val} actual parameters in the language already (object literals) remove most of the justification and almost all motivation for adding key: val parameter syntax to call expressions.

Thank you, Herby

Hope this helps.

[ did you know you quoted cca 50 screens? 8-O I took the liberty of omitting them. ]

I'm using a new mail user agent and it does tend to overcite. Sorry, will try to fix.

# Herby Vojčík (14 years ago)

Thank you, it helped.

It is the fact that in ES up till now, {} block were always delimited by a keyword (if/else is probably the only case when two blocks were present). So it is not a bad idea to retain it.

The problem with this is, one can put arguments in normal parentheses as well as paren-free and keywords are only needed in paren-free scenario... nevertheless. I have an idea. What if this would be allowed:

function myIf(cond, ifBlock myElse elseBlock={||}) { ... }

that is, to allow to separate formal argument names with keywords, not only with commas (again, there can be some restriction like only first few ot last few can be separated this way, so it conforms the overall goals of the grammar).

Then, one could write (well, never mind now if it needs currying or not, it is not a topic now):

myIf(a>b) {|| doThisIfAWins() } myElse {|| doThisIfBWins() };

but also (since elseBlock has default value)

myIf(a>b) {|| doThisIfAWins() };

and of course, it still can write

myIf(a>b, {|| doThisIfAwins() }, {|| doThisIfBWins());

though it is a question if it would not be more consistent to do:

myIf(a>b, {|| doThisIfAwins() } myElse {|| doThisIfBWins());

# Lasse Reichstein (14 years ago)

On Mon, Jan 16, 2012 at 2:14 PM, Herby Vojčík <herby at mailbox.sk> wrote:

Thank you, it helped.

It is the fact that in ES up till now, {} block were always delimited by a keyword (if/else is probably the only case when two blocks were present). So it is not a bad idea to retain it.

I believe it's perfectly valid to write blocks (for no particular reason) without any keyword. I.e.: function foo() { var x = 42; { var y = 10; print(x+y); } return x; }

Obviously it's useless, but it is valid. You could add a label to the block if you want to actually use it for something:

bar: { if (x == 10) { y = 10; break bar; } if (x == 20) { y = 0; break bar; } ... }

/L 'Bad examples are bad!'

# Herby Vojčík (14 years ago)

Lasse Reichstein wrote:

On Mon, Jan 16, 2012 at 2:14 PM, Herby Vojčík<herby at mailbox.sk> wrote:

Thank you, it helped.

It is the fact that in ES up till now, {} block were always delimited by a

{} blocks which are parts of the same control structure. I did not talk about free blocks, but about bound blocks. Sorry if it was not clear.

keyword (if/else is probably the only case when two blocks were present). So it is not a bad idea to retain it.

I believe it's perfectly valid to write blocks (for no particular reason) without any keyword. I.e.: function foo() { var x = 42; { var y = 10; print(x+y); } return x; }

Obviously it's useless, but it is valid. ...

# Brendan Eich (14 years ago)

Lasse Reichstein <mailto:reichsteinatwork at gmail.com> January 16, 2012 5:54 AM

I believe it's perfectly valid to write blocks (for no particular reason) without any keyword. I.e.: function foo() { var x = 42; { var y = 10; print(x+y); } return x; }

Obviously it's useless, but it is valid.

It becomes useful with the introduction of 'let', 'const', and ES.next 'function' in block declarations.

You could add a label to the block if you want to actually use it for something:

bar: { if (x == 10) { y = 10; break bar; } if (x == 20) { y = 0; break bar; } ... }

/L 'Bad examples are bad!'

;-)