Block lambda grammar: BlockArguments

Brendan Eich brendan at mozilla.org
Sat Jan 14 14:41:58 PST 2012


> 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.

/be
>
> -- 
> Dr. Axel Rauschmayer
> axel at rauschma.de <mailto:axel at rauschma.de>
>
> home: rauschma.de <http://rauschma.de>
> twitter: twitter.com/rauschma <http://twitter.com/rauschma>
> blog: 2ality.com <http://2ality.com>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
> 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.
>>
>> 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
>
> https://mail.mozilla.org/pipermail/es-discuss/2011-May/014675.html
>
> /be
>>
>> -- 
>> Dr. Axel Rauschmayer
>> axel at rauschma.de <mailto:axel at rauschma.de>
>>
>> home: rauschma.de <http://rauschma.de>
>> twitter: twitter.com/rauschma <http://twitter.com/rauschma>
>> blog: 2ality.com <http://2ality.com>
>>
>> Brendan Eich <mailto:brendan at mozilla.org>
>> January 13, 2012 12:58 PM
>> Fixed: 
>> http://wiki.ecmascript.org/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.
>>
>> /be
>>
>> Brendan Eich <mailto:brendan at mozilla.org>
>> January 12, 2012 11:43 PM
>>> 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.
>>
>> /be
>>>
>>> or
>>>
>>>   get_map() {|e| e*e}
>>>
>>> or similar. I will fix.
>>>
>>> /be
>>> _______________________________________________
>>> es-discuss mailing list
>>> es-discuss at mozilla.org
>>> https://mail.mozilla.org/listinfo/es-discuss
>>> Axel Rauschmayer <mailto:axel at rauschma.de>
>>> January 12, 2012 11:16 PM
>>> http://wiki.ecmascript.org/doku.php?id=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 <mailto:brendan at mozilla.org>
>> January 12, 2012 11:39 PM
>>> Axel Rauschmayer <mailto:axel at rauschma.de>
>>> January 12, 2012 11:16 PM
>>> http://wiki.ecmascript.org/doku.php?id=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.
>>
>> /be
>> Axel Rauschmayer <mailto:axel at rauschma.de>
>> January 12, 2012 11:16 PM
>> http://wiki.ecmascript.org/doku.php?id=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 | ... }
>>
>>
>>
> 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({|| }, {|| })
>
> 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?
>
> -- 
> Dr. Axel Rauschmayer
> axel at rauschma.de <mailto:axel at rauschma.de>
>
> home: rauschma.de <http://rauschma.de>
> twitter: twitter.com/rauschma <http://twitter.com/rauschma>
> blog: 2ality.com <http://2ality.com>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
> Brendan Eich <mailto:brendan at mozilla.org>
> January 13, 2012 12:58 PM
> Fixed: 
> http://wiki.ecmascript.org/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.
>
> /be
>
> Brendan Eich <mailto:brendan at mozilla.org>
> January 12, 2012 11:43 PM
>> 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.
>
> /be
>>
>> or
>>
>>   get_map() {|e| e*e}
>>
>> or similar. I will fix.
>>
>> /be
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>> Axel Rauschmayer <mailto:axel at rauschma.de>
>> January 12, 2012 11:16 PM
>> http://wiki.ecmascript.org/doku.php?id=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 | ... }
>>
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/78316fee/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: postbox-contact.jpg
Type: image/jpeg
Size: 1222 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/78316fee/attachment-0002.jpg>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: postbox-contact.jpg
Type: image/jpeg
Size: 1290 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/78316fee/attachment-0003.jpg>


More information about the es-discuss mailing list