block lambda revival
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
As promised/threatened: strawman:block_lambda_revival
From page:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
Peter
At the very bottom, there is a note that says:
"With GLR parsing for the spec grammar, we could consider, e.g. (x) {x} instead of {|x| x} with LineTerminator excluded between parameters and body."
FWIW, Speaking from a developers perspective, the syntax: (x){x} , has a very familiar voice that I'm confident would find immediate acceptance and fast adoption (I know this because it's recently been discussed by myself and several different groups of my developer peers)
On May 21, 2011, at 7:31 AM, Rick Waldron wrote:
At the very bottom, there is a note that says:
"With GLR parsing for the spec grammar, we could consider, e.g. (x) {x} instead of {|x| x} with LineTerminator excluded between parameters and body."
FWIW, Speaking from a developers perspective, the syntax: (x){x} , has a very familiar voice that I'm confident would find immediate acceptance and fast adoption (I know this because it's recently been discussed by myself and several different groups of my developer peers)
The problem is ambiguity:
b = a.forEach (x) { x * x }
looks like a call, a.forEach(x), whose return value is assigned to b, where the assignment expression is followed by a block statement on the same line, a syntax error currently.
The restriction that ) and { are on the same line is necessary for this to be a backward-compatible extension, but it doesn't help the parsing ambiguity.
Putting block parameters on the inside of the { }, even though that requires | | or a longer way of bracketing the parameters, does not introduce this ambiguity.
On May 20, 2011, at 9:55 PM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
The argument, as I understand it from Smalltalk, Ruby, and E experts, is empirical, and in part a matter of intentional design: users write blocks as actual parameters to make control abstractions. Most such abstractions taking block arguments do not let those blocks escape through the heap or a return value -- the blocks are downward funargs. This aids in making new control abstractions more efficient than functions to implement, as well as more usable. Built-in control flow statements have (optionally) braced bodies that do not need parenthesization, so why should new ones created by users passing blocks?
When I wrote essential, I was not claiming that there's a logical proof of necessity. Rather I was declaring that this strawman includes paren-free block-argument-bearing call expressions as an essential design element. Chopping it out chops down the whole strawman.
Brendan,
Thanks for the clarification on that.
I've been following the shorter function syntax discussions pretty closely, as well as reading the strawman proposals, so please forgive me if this has been addressed or refuted.
Given your example: b = a.forEach (x) { x * x } And the issues you noted there, would the same issues apply here:
b = a.forEach( (x) { x * x } ) ^ ^
Maybe unrelated:
Is b = a.forEach (x) { x * x } meant to illustrate a general syntax ambiguity? Perhaps I'm missing a piece of the puzzle because forEach has a second, optional thisArg param - how would that be included?
Thanks in advance!
On May 21, 2011, at 7:56 AM, Rick Waldron wrote:
Brendan,
Thanks for the clarification on that.
I've been following the shorter function syntax discussions pretty closely, as well as reading the strawman proposals, so please forgive me if this has been addressed or refuted.
Given your example: b = a.forEach (x) { x * x } And the issues you noted there, would the same issues apply here:
b = a.forEach( (x) { x * x } ) ^ ^
That thought has occurred to several people but then it looks like a block erroneously run up against a.forEach, where inside the block (the outer braces) there's a parenthesized expression run up against an inner block containing an expression.
Note how the inner braces are not necessary.
We could make this work but it would risk turning what might be an easy error, losing a newline between a.forEach and a braced block, into something that runs and has very different meaning. Another possible error: losing ( and ) around an object literal passed to a.forEach.
Maybe unrelated:
Is b = a.forEach (x) { x * x } meant to illustrate a general syntax ambiguity? Perhaps I'm missing a piece of the puzzle because forEach has a second, optional thisArg param - how would that be included?
My thinking is: write a second block argument:
b = a.forEach {|x| self.list.push(x * x); } {self}
where self is the |this| parameter to pass.
The ambiguity requiring GLR parsing is that (a, b) is a comma expression and a formal parameter list (the two syntaxes overlap but formal parameters may want "more syntax", e.g. guards, destructuring shorthand, parameter default values with larger syntax than nested assignment expressions cover, etc.). A parser can't decide until the restricted {...} is parsed fully. That requires GLR or equivalent.
On Sat, May 21, 2011 at 7:43 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 20, 2011, at 9:55 PM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
When I wrote essential, I was not claiming that there's a logical proof of necessity. Rather I was declaring that this strawman includes paren-free block-argument-bearing call expressions as an essential design element. Chopping it out chops down the whole strawman.
I understand what you're writing above but I think that ties this proposed short syntax for functions (or lambdas) to something the arrow syntax is not tied to and so makes it a "do you like apples or oranges?" choice. In another thread, some people were suggesting {||} as an exact alternative to the thin arrow.
Peter
On Sat, May 21, 2011 at 7:56 AM, Rick Waldron <waldron.rick at gmail.com> wrote:
Brendan, Thanks for the clarification on that. I've been following the shorter function syntax discussions pretty closely, as well as reading the strawman proposals, so please forgive me if this has been addressed or refuted. Given your example: b = a.forEach (x) { x * x } And the issues you noted there, would the same issues apply here: b = a.forEach( (x) { x * x } ) ^ ^
I don't see a prefix being a problem to reduce ambiguity. It would actually be beneficial for readability.
b = a.forEach( lambda(x) { x * x } )
It seems this strawman is proposing true lambdas so the name is apt. It sure is easy to Google "JavaScript lambda" or search code for "lambda".
functionreturn is 14 characters. lambda is 6 characters. That is a 8/14 character savings which is pretty good for those worried about character count. I don't know why we have to be so extreme and get the savings up to 12/14. In the case that the lambda has a few possible return points the savings on repeated "return" would be repeated savings.
This strawman is much more interesting because it is not just sugar. It introduces something JavaScript does not have now which is a real lambda and that could take JavaScript in positive directions in the future. Many syntax possibilities and macros need to desugar to a real lambda that obeys Tennent Correspondence Principle, correct? Opening that door for the future would be beneficial.
I'm understanding the intention of the strawman correctly?
Peter
On 21/05/2011, at 16:43, Brendan Eich wrote:
On May 20, 2011, at 9:55 PM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
The argument, as I understand it from Smalltalk, Ruby, and E experts, is empirical, and in part a matter of intentional design: users write blocks as actual parameters to make control abstractions. Most such abstractions taking block arguments do not let those blocks escape through the heap or a return value -- the blocks are downward funargs. This aids in making new control abstractions more efficient than functions to implement, as well as more usable. Built-in control flow statements have (optionally) braced bodies that do not need parenthesization, so why should new ones created by users passing blocks?
When I wrote essential, I was not claiming that there's a logical proof of necessity. Rather I was declaring that this strawman includes paren-free block-argument-bearing call expressions as an essential design element. (...)
<fwiw>
I for one do like explicit call()s better, the way it's always been in JS. ( and it's just 2 chars ), perhaps because I can't avoid to see it as a plus to share syntax with C.
C is one of the -if not the- most important languages in history, and I can see good reasons for you wanting to borrow from its syntax back then, in 1995 (but even now, too, because it's still one of the most important and popular languages)
But I can't say so much of borrowing from coffeescript's syntax ? Perhaps in 30 years, we'll see :-)
Why the (sudden) urge to copy so many bits ( paren-free, -> // =>, etc ) of coffee's syntax ? (does JS have an identity crisis now ?)
WRT the syntactic noise that these () ; {} etc introduce in the source: I don't like the way children write their SMSs either, everything are shorthands and there's no punctuation marks: sure it's faster to write, but not easier to read.
A bit less (syntactic) noise would be good. But a bit less isn't "let's make a new JS that not even a JS programmer can recognize".
I'm a JS programmer that isn't ashamed of its C heritage, and I don't think JS.next needs that breaking change.
I'd put the stress in the other important things more than in trying to make it look more "current-fashion".
</fwiw>
Chopping it out chops down the whole strawman.
No paren-free call()s, no {|| ... } blocks ? Why ?
WRT lexical this
: I think a simple (illegal var name) in the first parameter position, for example '@', might serve :
({ lex:lexicalThis, dyn:dynamicThis }).lex().dyn();
function lexicalThis () { var that= this; {|@,a,b,etc| assert(this === that)}(); return this; }
function dynamicThis () { var that= this; {|a,b,etc| assert(this !== that)}(); return this; }
On May 21, 2011, at 9:50 AM, Peter Michaux wrote:
On Sat, May 21, 2011 at 7:43 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 20, 2011, at 9:55 PM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
When I wrote essential, I was not claiming that there's a logical proof of necessity. Rather I was declaring that this strawman includes paren-free block-argument-bearing call expressions as an essential design element. Chopping it out chops down the whole strawman.
I understand what you're writing above but I think that ties this proposed short syntax for functions (or lambdas) to something the arrow syntax is not tied to and so makes it a "do you like apples or oranges?" choice.
I'm doing integrated design, a particular usable salad. Good nutrition often requires "both" not "either or".
In another thread, some people were suggesting {||} as an exact alternative to the thin arrow.
Yes, I cited the thread in the strawman. So what? Chopping proposals into little pieces does not help, we've seen this before.
On May 21, 2011, at 10:38 AM, Jorge wrote:
On 21/05/2011, at 16:43, Brendan Eich wrote:
On May 20, 2011, at 9:55 PM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
The argument, as I understand it from Smalltalk, Ruby, and E experts, is empirical, and in part a matter of intentional design: users write blocks as actual parameters to make control abstractions. Most such abstractions taking block arguments do not let those blocks escape through the heap or a return value -- the blocks are downward funargs. This aids in making new control abstractions more efficient than functions to implement, as well as more usable. Built-in control flow statements have (optionally) braced bodies that do not need parenthesization, so why should new ones created by users passing blocks?
When I wrote essential, I was not claiming that there's a logical proof of necessity. Rather I was declaring that this strawman includes paren-free block-argument-bearing call expressions as an essential design element. (...)
<fwiw>
I for one do like explicit call()s better, the way it's always been in JS. ( and it's just 2 chars ),
You are always free to over-parenthesize. Blocks can be passed as primary expressions too (I left that out of the grammar, sorry -- fixing).
perhaps because I can't avoid to see it as a plus to share syntax with C.
C is one of the -if not the- most important languages in history, and I can see good reasons for you wanting to borrow from its syntax back then, in 1995 (but even now, too, because it's still one of the most important and popular languages)
Sorry, it depends on audience. A lot of programmers these days (more's the pity in my view) do not learn C. They learn Java. Others come up at GitHub University (;-) and learn dynamic languages with significant newlines and even significant whitespace. It's not the C/Unix world I knew from my youth.
But I can't say so much of borrowing from coffeescript's syntax ? Perhaps in 30 years, we'll see :-)
The arrow syntax is in many languages. I did take => from CoffeeScript because binding |this| is quite common and it seems to me it ought to have shorthand that does not prejudge it as uncommon or with some puritanical sin-tax.
Yes, one wants languages such as C to telegraph micro-economics: shorter forms should be more efficient, ceteris paribus. No hidden unexpected costs. JS is not such a language and has never been, even before getters and setters.
Why the (sudden) urge to copy so many bits ( paren-free, -> // =>, etc ) of coffee's syntax ? (does JS have an identity crisis now ?)
Please lose the attitude. Look around you, the world is not C and it's not 1995. I look at many languages, like Steve Yegge and others (Guy Steele has written about this eloquently), I'm a polyglot and language lover when it comes to programming languages.
Language chauvinists tend to draw lines in the sand and make war. That's silly, self-limiting.
True, some languages are not much used due to their flaws, and we can learn from those. But popular languages are used in spite of their technical flaws, too.
It pays to look at what people actually use, and learn why they like it. In that light, I've approached CoffeeScript as a source of potential good ideas. We in TC39 are certainly not standardizing CoffeeScript, and Jeremy Ashkenas doesn't want anything like that. Rather, we're trying to modernize JS enough so that some of the pressure for transpiled languages is reduced, and in any event so that the transpilations are simpler and more efficient.
Languages are not people, they do not have self-aware identities or identity crises. Languages do have skin (surface syntax) or to use a trope I prefer, clothing. Clothing involves aesthetics as well as utility, usability, ergonomics. C and JS patterned on it are hardly the last word or the latest mode.
Since the early days of Lisp, syntax design and even support for multiple syntaxes for the same semantics have formed an evolving art. That evolution continues, whether you like it or not, and developers can vote with their fingers and virtual feet. You see this most clearly on github.com.
WRT the syntactic noise that these () ; {} etc introduce in the source: I don't like the way children write their SMSs either, everything are shorthands and there's no punctuation marks: sure it's faster to write, but not easier to read.
Shorter forms in programming languages can be faster to read too, though. Reading 'function' all over the place, e.g. in the Monadic Eval example Claus cited, is a drag. In no way does it win on pure reading, ignoring creation and maintenance aspects of writing.
A bit less (syntactic) noise would be good. But a bit less isn't "let's make a new JS that not even a JS programmer can recognize".
People learn new forms. ES3 added function expressions and they were recognizable, but perhaps more confusing for looking like function declarations than if they had shorter syntax. Consider the de-facto semi-standard still being mooted: eval("function (...) {...}") done to compile a specialized function. That source is not a valid Program, it's an anonymous function expression.
I'm a JS programmer that isn't ashamed of its C heritage, and I don't think JS.next needs that breaking change.
Now you are making a false statement, on two counts:
-
JS is not entirely governed by its C heritage. Where pray tell does 'function' occur in C? C functions are much more concise.
-
No one, least of all me, has proposed breaking anything, as in breaking compatibility. The proposed extensions are not in general breaking changes (useless labels becoming early errors in Harmonoy aside, and that is a separable proposal).
I'd put the stress in the other important things more than in trying to make it look more "current-fashion".
"Fashion" as better, more usable syntax is not only aesthetics and fluff, or a racket to sell more new clothes before the old ones wear out.
On Sat, May 21, 2011 at 10:50 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 21, 2011, at 9:50 AM, Peter Michaux wrote:
In another thread, some people were suggesting {||} as an exact alternative to the thin arrow.
Yes, I cited the thread in the strawman. So what? Chopping proposals into little pieces does not help, we've seen this before.
But the two strawmans conflate ideas unnecessarily. Someone out there may want thin arrow lambdas without paren free calling. If they do then how do they choose between the {||} lambda paren free verses the arrow function strawmans. They cannot.
There are several issues that can be dealt with separately.
-
Should new syntax be introduced for functions?
-
Should lambdas be introduced? If so what would there syntax be?
-
Should paren free calls be introduced?
Peter
On May 21, 2011, at 10:05 AM, Peter Michaux wrote:
On Sat, May 21, 2011 at 7:56 AM, Rick Waldron <waldron.rick at gmail.com> wrote:
Brendan, Thanks for the clarification on that. I've been following the shorter function syntax discussions pretty closely, as well as reading the strawman proposals, so please forgive me if this has been addressed or refuted. Given your example: b = a.forEach (x) { x * x } And the issues you noted there, would the same issues apply here: b = a.forEach( (x) { x * x } ) ^ ^
I don't see a prefix being a problem to reduce ambiguity. It would actually be beneficial for readability.
b = a.forEach( lambda(x) { x * x } )
'lambda' is not reserved, and extant JS on the web uses it.
You're ignoring the goal of providing paren-free block-argument call syntax for control abstractions that look like built-in control-flow statements.
It seems this strawman is proposing true lambdas so the name is apt. It sure is easy to Google "JavaScript lambda" or search code for "lambda".
Do that now and see all the hits for uses of lambda we would be breaking.
functionreturn is 14 characters. lambda is 6 characters. That is a 8/14 character savings which is pretty good for those worried about character count. I don't know why we have to be so extreme and get the savings up to 12/14. In the case that the lambda has a few possible return points the savings on repeated "return" would be repeated savings.
'lambda' is not reserved and still overlong. TC39 and this list have been over this ground before. I don't see anything new in your post to change the outcome.
This strawman is much more interesting because it is not just sugar. It introduces something JavaScript does not have now which is a real lambda and that could take JavaScript in positive directions in the future. Many syntax possibilities and macros need to desugar to a real lambda that obeys Tennent Correspondence Principle, correct? Opening that door for the future would be beneficial.
All true (and why I revived this idea as a strawman), but nothing to do with syntax.
I'm understanding the intention of the strawman correctly?
What is the question? New semantics and syntax come in an integrated design. Pointing out "look, new semantics!" does not automatically argue that the syntax should revert to something (a) not reserved; (b) long-winded like bad old 'function'.
On Sat, May 21, 2011 at 10:38 AM, Jorge <jorge at jorgechamorro.com> wrote:
Why the (sudden) urge to copy so many bits ( paren-free, -> // =>, etc ) of coffee's syntax ?
I agree. I don't see how Coffeescript deserves to be such a strong influence with such a short history and limited use.
(does JS have an identity crisis now ?)
JavaScript has always had an identity crisis, hasn't it? ;-)
Peter
On Sat, May 21, 2011 at 11:09 AM, Brendan Eich <brendan at mozilla.com> wrote:
'lambda' is not reserved and still overlong. TC39 and this list have been over this ground before. I don't see anything new in your post to change the outcome.
TC39 will not introduce any new keywords to the language?
Peter
Previously, you argued against the importance syntactic convenience, but it seems what you really were opposed to was a new function form that wasn't a true lambda. But now that there is one back on the table, you're OK with counting conciseness as a win.
I don't mean to single you out on this-- it's a general phenomenon on es-discuss that really frustrates me. Design discussions are dysfunctional when they're treated as a zero-sum game between opponents. Design requires being honest about trade-offs. When people feel threatened that their concerns aren't being taken into account, they get defensive and start discounting others' concerns, and then the whole thing becomes a negative feedback loop.
For my part, I will do my best to at least listen to people's concerns and register them as part of the equation, even if not every design can fulfill every constraint. I haven't always succeeded at that, but I'll do my best. I hope everyone will try to do the same.
On May 21, 2011, at 11:12 AM, Peter Michaux wrote:
On Sat, May 21, 2011 at 10:38 AM, Jorge <jorge at jorgechamorro.com> wrote:
Why the (sudden) urge to copy so many bits ( paren-free, -> // =>, etc ) of coffee's syntax ?
I agree. I don't see how Coffeescript deserves to be such a strong influence with such a short history and limited use.
Arrows are in many languages. CoffeeScript is not being standardized and it is not overriding (nor should we ignore it). Please stop rehashing or me-too'ing on this canard that somehow the branded "other" is threatening the JS "self".
Someone may need psychoanalysis here, but it is not JS!
(does JS have an identity crisis now ?)
JavaScript has always had an identity crisis, hasn't it? ;-)
This looks like more projection, or just misattribution. People have identity crises, not programming languages.
I will try to be positive: please have the confidence to look at alternative syntaxes and other languages without getting hung up on provenance or even youthfulness of the language. Open your mind and consider the details on their merits, ignore the branding.
Let's get back to technical issues, if we can.
On May 21, 2011, at 11:16 AM, Peter Michaux wrote:
On Sat, May 21, 2011 at 11:09 AM, Brendan Eich <brendan at mozilla.com> wrote:
'lambda' is not reserved and still overlong. TC39 and this list have been over this ground before. I don't see anything new in your post to change the outcome.
TC39 will not introduce any new keywords to the language?
We will, and we reserved a set in ES5 while unreserving a larger Java-influenced set from ES1 days that actual implementations did not all reserve.
We may even reserve new keywords not in the ES5 future reserved identifiers list. But only with good reason and ideally where we don't break much content, or even any (think contextual keyword, e.g., new and meaningful only in class bodies).
On Sat, May 21, 2011 at 11:20 AM, David Herman <dherman at mozilla.com> wrote:
Previously, you argued against the importance syntactic convenience, but it seems what you really were opposed to was a new function form that wasn't a true lambda. But now that there is one back on the table, you're OK with counting conciseness as a win.
I must not have been clear enough with what I wrote with regard to conciseness. I wrote the following...
functionreturn is 14 characters. lambda is 6 characters. That is a 8/14 character savings which is pretty good for those worried about character count.
The "for those worried about character count" part was intentional and did not include me with regard to reducing the length of function literals. I don't see how that is motivation
Peter
On May 21, 2011, at 11:07 AM, Peter Michaux wrote:
On Sat, May 21, 2011 at 10:50 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 21, 2011, at 9:50 AM, Peter Michaux wrote:
In another thread, some people were suggesting {||} as an exact alternative to the thin arrow.
Yes, I cited the thread in the strawman. So what? Chopping proposals into little pieces does not help, we've seen this before.
But the two strawmans conflate ideas unnecessarily.
Conflate is pejorative and unnecessarily is false. It's a matter of design choice, not necessity.
We seem to go in circles a lot. What part of "integrated design" was unclear? If somehow the whole is rejected, we can pluck out parts and propose them separately, or better: as part of alternative integrated designs.
In particular, I explicitly rejected paren-free call syntax for any function, proposing this novelty only where the actual arguments are block-lambdas. Paren-full, comma-separated call syntax still works.
Paren-free call syntax in general is problematic on grammatical and backward-compatibility grouns. Tying it to block-lambdas as new expression forms avoids these problems. I don't think you acknowledged this design decision, which goes against separating paren-free calls out as a strawman.
Someone out there may want thin arrow lambdas without paren free calling.
People want many things. We are considering only certain proposals, within certain bounded periods, and doing our best. We're not going to consider everything, and (see above, and to repeat myself) we are not chopping everything up into minimal parts and considering all combinations.
If they do then how do they choose between the {||} lambda paren free verses the arrow function strawmans. They cannot.
Too bad.
There are several issues that can be dealt with separately.
- Should new syntax be introduced for functions?
We are past this and considering multiple strawmen.
- Should lambdas be introduced? If so what would there syntax be?
This is not a question-posing exercise or even a Socratic dialog. It's a proposal/disposal process, already well under way. With block lambda revival, my answers are "yes" and "Ruby-esque".
- Should paren free calls be introduced?
I'm not proposing this in general, and I do not believe anyone else
On Sat, May 21, 2011 at 11:36 AM, Brendan Eich <brendan at mozilla.com> wrote:
What part of "integrated design" was unclear?
None of it.
If somehow the whole is rejected, we can pluck out parts and propose them separately, or better: as part of alternative integrated designs.
Understood.
Neither of the two strawmen proposals as wholes appeal to me. You invited feedback. I've given mine. The general public has no official say in the matter. I can only hope some on the committee agree.
Peter
On May 21, 2011, at 11:54 AM, Peter Michaux wrote:
On Sat, May 21, 2011 at 11:36 AM, Brendan Eich <brendan at mozilla.com> wrote:
If somehow the whole is rejected, we can pluck out parts and propose them separately, or better: as part of alternative integrated designs.
Understood.
Neither of the two strawmen proposals as wholes appeal to me. You invited feedback. I've given mine. The general public has no official say in the matter. I can only hope some on the committee agree.
Everyone
On Sat, May 21, 2011 at 10:43 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 20, 2011, at 9:55 PM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
The argument, as I understand it from Smalltalk, Ruby, and E experts, is empirical, and in part a matter of intentional design: users write blocks as actual parameters to make control abstractions. Most such abstractions taking block arguments do not let those blocks escape through the heap or a return value -- the blocks are downward funargs. This aids in making new control abstractions more efficient than functions to implement, as well as more usable. Built-in control flow statements have (optionally) braced bodies that do not need parenthesization, so why should new ones created by users passing blocks?
I like the proposal.
I'm not sure I follow the above, though. I get the point about un-parenthesized blocks making for better control abstraction syntax; I'm just not clear on how that relates to the downward funargs point. In Ruby, for example, the anonymity of the implicit block argument prevents its capture and guarantees that the param is downwards-only. It seems unrelated to parenthesis.
Since the strawman only addresses the call syntax, I figure that it doesn't affect formal argument syntax -- I mean, that you're not proposing something like Ruby's implicit, anonymous, last formal. Since block args would be named, and since they are first-class (evidenced by the examples in the strawman), I'm guessing that they are allowed to escape up the stack. But I may have missed something basic.
On May 21, 2011, at 4:23 PM, Jon Zeppieri wrote:
On Sat, May 21, 2011 at 10:43 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 20, 2011, at 9:55 PM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
An essential part of this proposal is a paren-free call syntax
Why is that essential?
The argument, as I understand it from Smalltalk, Ruby, and E experts, is empirical, and in part a matter of intentional design: users write blocks as actual parameters to make control abstractions. Most such abstractions taking block arguments do not let those blocks escape through the heap or a return value -- the blocks are downward funargs. This aids in making new control abstractions more efficient than functions to implement, as well as more usable. Built-in control flow statements have (optionally) braced bodies that do not need parenthesization, so why should new ones created by users passing blocks?
I like the proposal.
I'm not sure I follow the above, though. I get the point about un-parenthesized blocks making for better control abstraction syntax; I'm just not clear on how that relates to the downward funargs point.
It doesn't. Two separate points. Sorry, I should have used a bulleted list or something :-P.
In Ruby, for example, the anonymity of the implicit block argument prevents its capture and guarantees that the param is downwards-only. It seems unrelated to parenthesis.
Right, and we are convinced that this is an unnecessary concession in Ruby's design toward ease of implementation. Analyzing Harmony code to link uses to definitions it not hard, and arguments.callee is poisoned pill (already; ES5 strict).
An implementation that wants to optimize must do aggressive name use/def analysis, and it can readily detect escape of a funarg.
Since the strawman only addresses the call syntax, I figure that it doesn't affect formal argument syntax -- I mean, that you're not proposing something like Ruby's implicit, anonymous, last formal.
Nope. Strawman grammar changes look complete to me. Nothing left out but the semantics. Please tell me if I'm missing something.
Since block args would be named, and since they are first-class (evidenced by the examples in the strawman), I'm guessing that they are allowed to escape up the stack. But I may have missed something basic.
They can indeed escape (so can Ruby ¶ms, but we don't have arity-mismatch error [yet] and we don't need to complicate the design as Ruby did, in its several different ways). Implementations will cope.
On Sat, May 21, 2011 at 8:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
On May 21, 2011, at 4:23 PM, Jon Zeppieri wrote:
I'm just not clear on how that relates to the downward funargs point.
It doesn't. Two separate points. Sorry, I should have used a bulleted list or something :-P.
Thanks for the clarification.
In Ruby, for example, the anonymity of the implicit block argument prevents its capture and guarantees that the param is downwards-only. It seems unrelated to parenthesis.
Right, and we are convinced that this is an unnecessary concession in Ruby's design toward ease of implementation. Analyzing Harmony code to link uses to definitions it not hard, and arguments.callee is poisoned pill (already; ES5 strict).
Absolutely agree.
I like it. Blocks seem like “true” functions, where current functions always carry the method-ish |this| with them. Blocks are especially useful as a concise callable construct inside methods.
Questions:
-
|this|: How is lexical |this| achieved? Via bind() (or something similar)?
-
Can blocks be used as methods? Not as important, because conciseness for methods is taken care of by the Concise Object Literal Extensions.
-
|return|: Is there a way to return from a block before it ends (like |return| does for functions)?
Thus: are blocks completely different from functions or are they translated to the same construct (internally). I can imagine this being done to make |arguments| lexical, but it seems tricky for handling lexical |this|.
Axel
This seems too brittle to me. The examples conveniently include only lambda parameters in their function calls. Suppose you have:
a = f{|| 42}
and want to add a second lambda parameter:
a = f{|| 42}{|x| x*x}
So far so good, ignoring the little bug that || is a different token than two |'s (we've yet to have a coherent discussion about what really can go into these parameter lists).
Now you want to add a third integer parameter:
a = f{|| 42}{|x| x*x}(7)
Well, that won't work, as it curries rather than supplying the third parameter. Instead you'd need to do:
a = f({|| 42},{|x| x*x},7)
Oh, and don't forget to now insert the comma between the }{. You'll get something entirely different (a lambda called with a lambda parameter, which it then ignores) if you omit it.
The other problem is the necessity of the [no line terminator here] imposed by semicolon insertion. It's very tempting to write
a = f {|x| x*x}
which will at best be a syntax error or at worst be two separate statements (depending on whether we allow a lambda to start a statement).
Waldemar
On May 23, 2011, at 1:44 PM, Waldemar Horwat wrote:
This seems too brittle to me. The examples conveniently include only lambda parameters in their function calls. Suppose you have:
a = f{|| 42}
and want to add a second lambda parameter:
a = f{|| 42}{|x| x*x}
So far so good, ignoring the little bug that || is a different token than two |'s
Yes, I know -- details, details. I'll work on it.
(we've yet to have a coherent discussion about what really can go into these parameter lists).
I gave the grammar with semantics -- did you read it?
Now you want to add a third integer parameter:
a = f{|| 42}{|x| x*x}(7)
You did not read the grammar. The space-separated block-lambda argument list can consist only of block-lambda expressions.
strawman:block_lambda_revival#syntax, the BlockArguments productions. Semantics in next section.
On 05/23/11 16:20, Brendan Eich wrote:
On May 23, 2011, at 1:44 PM, Waldemar Horwat wrote:
(we've yet to have a coherent discussion about what really can go into these parameter lists).
I gave the grammar with semantics -- did you read it?
Yes. However I don't think it'd be tenable to not support rest parameters or whatever normal functions can do.
Now you want to add a third integer parameter:
a = f{|| 42}{|x| x*x}(7)
You did not read the grammar.
Snarky, aren't we? Incorrect too, as I did read the grammar.
The space-separated block-lambda argument list can consist only of block-lambda expressions.
That was the problem I was pointing out.
Waldemar
On May 23, 2011, at 5:12 PM, Waldemar Horwat wrote:
On 05/23/11 16:20, Brendan Eich wrote:
On May 23, 2011, at 1:44 PM, Waldemar Horwat wrote:
(we've yet to have a coherent discussion about what really can go into these parameter lists).
I gave the grammar with semantics -- did you read it?
Yes. However I don't think it'd be tenable to not support rest parameters or whatever normal functions can do.
I didn't include rest parameters (noted in the Notes) and I only tried to include parameter default values and destructuring in the syntax, and neither of those the semantics. Until this strawman is accepted into Harmony, it doesn't seem worth the effort. If you are warm to it, though, I'd be glad to overengineer it for success ;-).
I'll add a note that BlockParameterList is meant to be like FormalParameterList but with the necessary restriction that the default value be a BitwiseXorExpression.
Now you want to add a third integer parameter:
a = f{|| 42}{|x| x*x}(7)
You did not read the grammar.
Snarky, aren't we?
I was just keeping up with your fine "we've yet to have a coherent discussion". Sorry, I'll desnark.
The space-separated block-lambda argument list can consist only of block-lambda expressions.
That was the problem I was pointing out.
I see what you mean, but is it really a problem without user testing? I could try to make it an error but that seems unnecessary at this stage. Unless you have a simple fix in mind?
On May 23, 2011, at 5:26 PM, Brendan Eich wrote:
I see what you mean, but is it really a problem without user testing? I could try to make it an error but that seems unnecessary at this stage. Unless you have a simple fix in mind?
One possible fix is to revise the grammar to require that the paren-free BlockArguments-only call syntax to be parenthesized in any context where the last block-lambda would have an expression after it before a LineTerminator, if there were no parenthesization around the BlockArguments. Comments?
On 05/23/11 17:26, Brendan Eich wrote:
On May 23, 2011, at 5:12 PM, Waldemar Horwat wrote:
On 05/23/11 16:20, Brendan Eich wrote:
On May 23, 2011, at 1:44 PM, Waldemar Horwat wrote:
(we've yet to have a coherent discussion about what really can go into these parameter lists).
I gave the grammar with semantics -- did you read it?
Yes. However I don't think it'd be tenable to not support rest parameters or whatever normal functions can do.
I didn't include rest parameters (noted in the Notes) and I only tried to include parameter default values and destructuring in the syntax, and neither of those the semantics. Until this strawman is accepted into Harmony, it doesn't seem worth the effort. If you are warm to it, though, I'd be glad to overengineer it for success ;-).
I'll add a note that BlockParameterList is meant to be like FormalParameterList but with the necessary restriction that the default value be a BitwiseXorExpression.
I agree this aspect is fine as a first cut.
The space-separated block-lambda argument list can consist only of block-lambda expressions.
That was the problem I was pointing out.
I see what you mean, but is it really a problem without user testing? I could try to make it an error but that seems unnecessary at this stage. Unless you have a simple fix in mind?
I don't have a simple fix in mind. What's making me dubious about this is that this is a function calling syntax that can supply a bunch of literal functions as arguments, but they must all be literal functions. As soon as you want to pass a function held in a variable or pass an argument that's not a function, you can't use the syntax any more. And if you forget to insert a comma between literal functions when refactoring to the regular syntax, you'll silently get an unexpected behavior.
Waldemar
On May 23, 2011, at 5:43 PM, Waldemar Horwat wrote:
I don't have a simple fix in mind. What's making me dubious about this is that this is a function calling syntax that can supply a bunch of literal functions as arguments, but they must all be literal functions. As soon as you want to pass a function held in a variable or pass an argument that's not a function, you can't use the syntax any more. And if you forget to insert a comma between literal functions when refactoring to the regular syntax, you'll silently get an unexpected behavior.
Here are some ideas:
- Extend the current proposal to allow parenthesized expressions interleaved with no line terminators, only optional horizontal space characters, with block-lambda expressions:
bar = foo {|x| x * x} (42);
This avoids the "currying hazard" you cited, without (I hope) introducing other hazards. If someone wants to pass an expression as an argument, then parenthesize and comma-separate all the arguments.
- Parse as proposed but with the restriction that the last block-lambda argument "ends the line", and also parse a mixture of block-lambda expressions and assignment-expressions separated by commas. IOW, parse both
bar = foo {|x| x * x} {| | 42}
and
bar = foo {|x| x * x}, 42;
On May 23, 2011, at 6:09 PM, Brendan Eich wrote:
- Parse as proposed but with the restriction that the last block-lambda argument "ends the line",
Where "ends the line" means either ; or LineTerminator (ignoring spaces including comments that do not include line terminators), I suppose. Ugh. #1 looks better, but it could be misread as currying:
bar = foo({|x| x * x})(42);
I should have listed
- A simpler change: allow only one block-lambda argument in the paren-free call case. This may be enough for now.
On 05/23/11 18:09, Brendan Eich wrote:
On May 23, 2011, at 5:43 PM, Waldemar Horwat wrote:
I don't have a simple fix in mind. What's making me dubious about this is that this is a function calling syntax that can supply a bunch of literal functions as arguments, but they must all be literal functions. As soon as you want to pass a function held in a variable or pass an argument that's not a function, you can't use the syntax any more. And if you forget to insert a comma between literal functions when refactoring to the regular syntax, you'll silently get an unexpected behavior.
Here are some ideas:
- Extend the current proposal to allow parenthesized expressions interleaved with no line terminators, only optional horizontal space characters, with block-lambda expressions:
bar = foo {|x| x * x} (42);
That might work, with the key part being "interleaved". We need to keep the semantics of
bar = foo(42)(33);
the same as now.
This avoids the "currying hazard" you cited, without (I hope) introducing other hazards. If someone wants to pass an expression as an argument, then parenthesize and comma-separate all the arguments.
- Parse as proposed but with the restriction that the last block-lambda argument "ends the line", and also parse a mixture of block-lambda expressions and assignment-expressions separated by commas. IOW, parse both
bar = foo {|x| x * x} {| | 42}
Requiring that an expression end the line is nasty. You often want to use them in || or ?: operators.
bar = foo {|x| x * x}, 42;
Without significant surgery of nearly the entire expression stack in the grammar, this one would be ambiguous with:
(bar = foo {|x| x * x}), 42
Perl has this kind of function call syntax, and every so often it will parse in a way I didn't anticipate. Things get really weird when you nest function calls.
Waldemar
On May 23, 2011, at 6:27 PM, Waldemar Horwat wrote:
On 05/23/11 18:09, Brendan Eich wrote:
Here are some ideas:
- Extend the current proposal to allow parenthesized expressions interleaved with no line terminators, only optional horizontal space characters, with block-lambda expressions:
bar = foo {|x| x * x} (42);
That might work, with the key part being "interleaved". We need to keep the semantics of
bar = foo(42)(33);
the same as now.
Absolutely.
I'll make an attempt, since option 0 (see later mail) is both easy and a bit of a hasty surrender.
This avoids the "currying hazard" you cited, without (I hope) introducing other hazards. If someone wants to pass an expression as an argument, then parenthesize and comma-separate all the arguments.
- Parse as proposed but with the restriction that the last block-lambda argument "ends the line", and also parse a mixture of block-lambda expressions and assignment-expressions separated by commas. IOW, parse both
bar = foo {|x| x * x} {| | 42}
Requiring that an expression end the line is nasty. You often want to use them in || or ?: operators.
You'd have to parenthesize the whole argument list, comma separated, in such cases.
bar = foo {|x| x * x}, 42;
Without significant surgery of nearly the entire expression stack in the grammar, this one would be ambiguous with:
(bar = foo {|x| x * x}), 42
Perl has this kind of function call syntax, and every so often it will parse in a way I didn't anticipate. Things get really weird when you nest function calls.
I have felt that weirdness myself.
Ok, I will focus on option 1. Thanks,
On May 23, 2011, at 6:29 PM, Brendan Eich wrote:
On May 23, 2011, at 6:27 PM, Waldemar Horwat wrote:
On 05/23/11 18:09, Brendan Eich wrote:
Here are some ideas:
- Extend the current proposal to allow parenthesized expressions interleaved with no line terminators, only optional horizontal space characters, with block-lambda expressions:
bar = foo {|x| x * x} (42);
That might work, with the key part being "interleaved". We need to keep the semantics of
bar = foo(42)(33);
the same as now.
Absolutely.
I'll make an attempt, since option 0 (see later mail) is both easy and a bit of a hasty surrender.
Done, at strawman:block_lambda_revival -- this was easy, but it made me wonder whether parentheses for the plain Expression arguments that may occur after the leading BlockLambda ought not better be curly braces. IOW, instead of
bar = foo {|x| x * x} (42);
support this:
bar = foo {|x| x * x} {42};
This is more uniform on its own new terms. If the first actual parameter wants to be an expression, the programmer must parenthesize the entire argument list (comma separated of course), or else write {| | 42} as the first argument.
So, are curlies better than parens in this situation? I think it is a bit of a corner case, but good to address to avoid the currying hazard.
On May 23, 2011, at 6:51 PM, Brendan Eich wrote:
Done, at strawman:block_lambda_revival -- this was easy, but it made me wonder whether parentheses for the plain Expression arguments that may occur after the leading BlockLambda ought not better be curly braces. IOW, instead of
bar = foo {|x| x * x} (42);
support this:
bar = foo {|x| x * x} {42};
This is more uniform on its own new terms. If the first actual parameter wants to be an expression, the programmer must parenthesize the entire argument list (comma separated of course), or else write {| | 42} as the first argument.
So, are curlies better than parens in this situation? I think it is a bit of a corner case, but good to address to avoid the currying hazard.
Silly of me, tempted by syntactic prettiness but this idea does nothing to address your objection.
But here is a grammar change, not too big (and required for yield expressions anyway) that does help:
[from strawman:arrow_function_syntax#grammar_changes]
Change all uses of AssignmentExpression outside of the Expression sub-grammar to InitialValue:
ElementList : // See 11.1.4 Elisionopt InitialValue ElementList , Elisionopt InitialValue ... PropertyAssignment : // See 11.1.5 PropertyName : InitialValue ... ArgumentList : // See 11.2 InitialValue ArgumentList , InitialValue ... Initialiser : // See 12.2 = InitialValue
InitialiserNoIn : // See 12.2 = InitialValueNoIn
Define InitialValue:
InitialValue : AssignmentExpression CallWithBlockArguments
Define CallWithBlockArguments:
CallWithBlockArguments : CallExpression BlockArguments
extend PrimaryExpression:
PrimaryExpression : ... ( CallWithBlockArguments )
And BlockArguments as follows: BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( Expression ) The InitialValue non-terminal will also produce YieldExpression for generators, already in Harmony.
This is not nearly so bad as "significant surgery of nearly the entire expression stack in the grammar", but I may be missing something.
On Fri, May 20, 2011 at 8:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
As promised/threatened: strawman:block_lambda_revival
Sorry to take a step back here. After reading Waldemar's comments, I took a closer look at the formal grammar, and I'm puzzled by the production rule you've added to CallExpression:
CallExpression [no LineTerminator here] BlockArguments
Should that be:
MemberExpression [no LineTerminator here] BlockArguments
instead? This may just be the sleepiness talking, but it looks like a CallExpression always starts with
MemberExpression Arguments
which would require a parenthesized argument list.
On May 24, 2011, at 12:18 AM, Jon Zeppieri wrote:
Should that be:
MemberExpression [no LineTerminator here] BlockArguments
instead? This may just be the sleepiness talking, but it looks like a CallExpression always starts with
MemberExpression Arguments
which would require a parenthesized argument list.
Of course, I should have caught that. I had hoped to support
a.foo() {|...| ...}
but that's too function-looking, and ambiguous without more grammar work.
Fixed, thanks.
I've updated the grammar to fix the bugs Waldemar and Jon pointed out:
ElementList : // See 11.1.4 Elisionopt InitialValue ElementList , Elisionopt InitialValue
PropertyAssignment : // See 11.1.5 PropertyName : InitialValue
ArgumentList : // See 11.2 InitialValue ArgumentList , InitialValue
Initialiser : // See 12.2 = InitialValue
InitialiserNoIn : // See 12.2 = InitialValueNoIn
InitialValue : AssignmentExpression CallWithBlockArguments
Statement : ... CallWithBlockArguments LeftHandSideExpression = CallWithBlockArguments LeftHandSideExpression AssignmentOperator CallWithBlockArguments
PrimaryExpression : ... ( CallWithBlockArguments )
CallWithBlockArguments : MemberExpression [no LineTerminator here] BlockArguments
BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( Expression )
BlockLambda : { | BlockParameterListopt | StatementListopt }
BlockParameterList : BlockParameter BlockParameterList , BlockParameter
BlockParameter : Identifier BlockParameterInitialiseropt Pattern BlockParameterInitialiseropt
BlockParameterInitialiser : = BitwiseXorExpression
The idea is to restrict paren-free block-lambda-argument-bearing calls (we knew those were trouble if not confined!) to only certain contexts:
- as an initial value for
- a property in an initialiser,
- an argument in a parenthesized call,
- a parameter default value,
- a variable declaration initialiser;
- the entirety of the expression in an expression statement;
- the right-hand side of an assignment operator following a left-hand-side expression, the assignment making a complete expression statement;
- parenthesized as a primary expression.
So you can go paren-free in the obvious places. Anywhere hinky, like the condition of an if statement (see strawman:paren_free and thanks to Waldemar for pointing this out) you have to parenthesize.
This seems to check out without conflicts in Bison, although I tested only a reduced sub-grammar of ES5.
On Tue, May 24, 2011 at 3:29 PM, Brendan Eich <brendan at mozilla.com> wrote:
BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( Expression )
The interleaved parenthesized expression worries me for two reasons:
-
Waldemar's "currying hazard" doesn't strike me so much as a hazard as it does expected behavior. But that might just be me.
-
If the the parenthesized expression is a comma expression, e.g.,
f{|x| x}(a, b, c)
I might expect a, b, and c to be spliced into the arguments list, whereas, given the formal grammar, I assume a and b will be evaluated for side-effects, and c will be the argument.
On May 24, 2011, at 7:20 PM, Jon Zeppieri wrote:
On Tue, May 24, 2011 at 3:29 PM, Brendan Eich <brendan at mozilla.com> wrote:
BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( Expression )
The interleaved parenthesized expression worries me for two reasons:
- Waldemar's "currying hazard" doesn't strike me so much as a hazard as it does expected behavior. But that might just be me.
It's probably damned-if-you-do-or-don't, hence the desire to cage CallWithBlockArguments inside parentheses in most cases.
If the the parenthesized expression is a comma expression, e.g.,
f{|x| x}(a, b, c)
I might expect a, b, and c to be spliced into the arguments list, whereas, given the formal grammar, I assume a and b will be evaluated for side-effects, and c will be the argument.
Yes, that's a good point.
And your examples caused me to reflect, I realized that I maladroitly failed to close the loop with InitialValue, which can be AssignmentExpression (next loosest after [comma] Expression) or CallWithBlockArguments. We want arguments and other initial values to use InitialValue.
In other words, we should support
foo {|...| ...} (bar {|...| ...})
without requiring doubled parentheses around the last actual.
So revising to this:
ElementList : // See 11.1.4 Elisionopt InitialValue ElementList , Elisionopt InitialValue
PropertyAssignment : // See 11.1.5 PropertyName : InitialValue
ArgumentList : // See 11.2 InitialValue ArgumentList , InitialValue
Initialiser : // See 12.2 = InitialValue
InitialiserNoIn : // See 12.2 = InitialValueNoIn
InitialValue : AssignmentExpression CallWithBlockArguments
Statement : ... CallWithBlockArguments LeftHandSideExpression = CallWithBlockArguments LeftHandSideExpression AssignmentOperator CallWithBlockArguments
PrimaryExpression : ... ( CallWithBlockArguments )
CallWithBlockArguments : MemberExpression [no LineTerminator here] BlockArguments
BlockArguments : BlockLambda BlockArguments [no LineTerminator here] BlockLambda BlockArguments [no LineTerminator here] ( InitialValue )
BlockLambda : { | BlockParameterListopt | StatementListopt }
BlockParameterList : BlockParameter BlockParameterList , BlockParameter
BlockParameter : Identifier BlockParameterInitialiseropt Pattern BlockParameterInitialiseropt
BlockParameterInitialiser : = BitwiseXorExpression
Thanks,
On May 24, 2011, at 7:20 PM, Jon Zeppieri wrote:
- Waldemar's "currying hazard" doesn't strike me so much as a hazard as it does expected behavior. But that might just be me.
One last thought: if you want to curry, always parenthesize all calls. Space-separated argument lists just do not look sufficiently "high precedence" when followed by (...) after, to treat
foo {|x| x} (bar)
as
foo({|x| x})(bar)
instead of
foo({|x| x}, bar)
On Wed, May 25, 2011 at 11:06 AM, Brendan Eich <brendan at mozilla.com> wrote:
One last thought: if you want to curry, always parenthesize all calls. Space-separated argument lists just do not look sufficiently "high precedence" when followed by (...) after, to treat foo {|x| x} (bar) as foo({|x| x})(bar) instead of foo({|x| x}, bar) /be
Agreed. My comment was off-base. I don't expect currying here. I'm more worried about the foo({|x| x}(bar)) interpretation.
That is to say, the different meanings of "(bar)" in
{|x| x}(bar) and foo {|x| x}(bar)
make me wince a bit.
On May 25, 2011, at 8:44 AM, Jon Zeppieri wrote:
On Wed, May 25, 2011 at 11:06 AM, Brendan Eich <brendan at mozilla.com> wrote:
One last thought: if you want to curry, always parenthesize all calls. Space-separated argument lists just do not look sufficiently "high precedence" when followed by (...) after, to treat foo {|x| x} (bar) as foo({|x| x})(bar) instead of foo({|x| x}, bar) /be
Agreed. My comment was off-base. I don't expect currying here. I'm more worried about the foo({|x| x}(bar)) interpretation.
That is to say, the different meanings of "(bar)" in
{|x| x}(bar)
The new grammar forbids that, though.
You must parenthesize the block-lambda to invoke it with a parenthesized argument list:
({|x| x})(bar)
Block-lambdas are most commonly used as actual arguments, not as literal callees in call expressions. To avoid trouble of the kind Waldemar pointed out, they must be caged by parentheses on the outside to be used that way.
and foo {|x| x}(bar)
make me wince a bit.
The latter is invoking foo with two arguments, the first (which must be for the space-based calling syntax to be legal) a block-lambda.
On Wed, May 25, 2011 at 4:01 PM, Brendan Eich <brendan at mozilla.com> wrote:
On May 25, 2011, at 8:44 AM, Jon Zeppieri wrote:
{|x| x}(bar)
The new grammar forbids that, though. You must parenthesize the block-lambda to invoke it with a parenthesized argument list: ({|x| x})(bar) Block-lambdas are most commonly used as actual arguments, not as literal callees in call expressions. To avoid trouble of the kind Waldemar pointed out, they must be caged by parentheses on the outside to be used that way.
Ok, I don't see a place for BlockLambda outside of call contexts in your grammar, so I didn't get that. Or is your addition to PrimaryExpression
( CallWithBlockArguments )
suposed to be
( BlockLambda )
?
And examples like
let empty = {| |};
are now
let empty = ({| |});
On May 25, 2011, at 1:47 PM, Jon Zeppieri wrote:
Ok, I don't see a place for BlockLambda outside of call contexts in your grammar, so I didn't get that. Or is your addition to PrimaryExpression
( CallWithBlockArguments )
suposed to be
( BlockLambda )
?
Oops, I lost the PrimaryExpression : ( BlockLambda ) in an earlier edit.
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
As promised/threatened: strawman:block_lambda_revival
The examples on the wiki page feature calling a function that takes one block argument and also feature Tennent's correspondence principle nicely; however, I don't see examples of how to define functions taking one or more block arguments or how to call a function with two or more block arguments. Could someone add an example? Perhaps a Boolean.prototype.ifTrueIfFalse definition equivalent to Smalltalk's ifTrue:ifFalse: would fill these example gaps?
Thanks, Peter
On Jun 4, 2011, at 6:32 AM, Peter Michaux wrote:
On Fri, May 20, 2011 at 5:54 PM, Brendan Eich <brendan at mozilla.com> wrote:
As promised/threatened: strawman:block_lambda_revival
The examples on the wiki page feature calling a function that takes one block argument and also feature Tennent's correspondence principle nicely; however, I don't see examples of how to define functions taking one or more block arguments or how to call a function with two or more block arguments. Could someone add an example? Perhaps a Boolean.prototype.ifTrueIfFalse definition equivalent to Smalltalk's ifTrue:ifFalse: would fill these example gaps?
Sure; the grammar support is there in the strawman, see CallWithBlockArguments. There's nothing special about this restricted kind of call expression on the callee's definition side. Any function can be invoked this way.
Object.defineProperty( Boolean.prototype, 'ifElse', { value: function (ifTrue, ifFalse) { return this ? ifTrue() : ifFalse(); } } );
let aBool = Math.random() >= 0.5;
print(aBool.ifElse {|| "true"} {|| "false"});
Peter Michaux encouraged me to write my thoughts on es-discuss so here I am.
Out of the various new function syntaxes proposed ( ->, #, {||} ) I
have really taken a liking to the block-lambda revival strawman. I think in general they all address similar wishes (e.g. implicit return, this, shorter). I prefer the block-lambda because of some use cases:
let a = [1,2,3,4]; let b = a.reduce((a,b) -> a + b) // didn't see an example for this one
assuming this is how it looks let b = a.reduce {|a,b| a + b} // less cluttery, fn bound by braces, couple characters less to type
Self executing function expressions
(-> { // multi-line }())
{|| // multi-line -- i'm assuming there wouldn't need to be parens around the whole thing because block lambdas are expressions not statements (is this the case with arrow?) }()
I like block-lambda because:
- From my understanding braces aren't going away, lets just embrace them (use them as part of the syntax)
- Arrow gets too bulky with braces (although worked good for CoffeeScript)
- Seems to be terse yet clear
- Encloses the function
It may be nice to have side by side comparisons of the different proposals doing the same operation.
Anyway, my 2 cents.
On 23/06/2011, at 23:01, Marc Harter wrote:
Peter Michaux encouraged me to write my thoughts on es-discuss so here I am.
Out of the various new function syntaxes proposed ( ->, #, {||} ) I have really taken a liking to the block-lambda revival strawman. I think in general they all address similar wishes (e.g. implicit return, this, shorter). I prefer the block-lambda because of some use cases:
let a = [1,2,3,4]; let b = a.reduce((a,b) -> a + b) // didn't see an example for this one assuming this is how it looks let b = a.reduce {|a,b| a + b} // less cluttery, fn bound by braces, couple characters less to type
Self executing function expressions
(-> { // multi-line }())
{|| // multi-line -- i'm assuming there wouldn't need to be parens around the whole thing because block lambdas are expressions not statements (is this the case with arrow?) }()
I like block-lambda because:
- From my understanding braces aren't going away, lets just embrace them (use them as part of the syntax)
- Arrow gets too bulky with braces (although worked good for CoffeeScript)
- Seems to be terse yet clear
- Encloses the function
It may be nice to have side by side comparisons of the different proposals doing the same operation.
Here: jorgechamorro.com/blocks.html
Anyway, my 2 cents. Thanks!
{|| ... } for shorter function syntax is my favorite too. +1(e9)
Also, if any { block } could be a lambda, perhaps we won't need that (nor any new) syntax for block-lambdas.
Also, I'd prefer to know/see clearly when a function is being call()ed, so I'm not very fond of paren-free calls: foo(bar) is clearly an invocation, unlike foo bar, and readability is more important than saving a few keystrokes.
The C language is still (and -ISTM- will be for a long time) important, so -IMO- every little bit of JS's C-like syntax is a plus: less to learn: an old, popular, widely used, well-known, and familiar syntax.
JS -unlike other languages- is important enough that it does not need to follow these (dubious) trendy fashions to become popular. Nor to survive.
Proper punctuation aids comprehension and we're programming, not writing quick SMSs.
Self executing function expressions
Terminology nit: I think Ben Alman's right that "self-executing" is confused terminology. I like his proposed term IIFE (pronounced: "iffy"):
benalman.com/news/2010/11/immediately-invoked-function-expression
(-> { // multi-line }())
{|| // multi-line -- i'm assuming there wouldn't need to be parens around the whole thing because block lambdas are expressions not statements (is this the case with arrow?) }()
It might work with an extra token of lookahead.
But I don't find that argument particularly compelling. Given the addition of `let' and modules, IIFE's shouldn't be nearly as important anymore.
(For the record, I'm undecided on the various shorter-function syntaxes. Just not particularly convinced by this argument.)
On Jun 23, 2011, at 3:14 PM, Jorge wrote:
Anyway, my 2 cents. Thanks!
{|| ... } for shorter function syntax is my favorite too. +1(e9)
Thanks -- I am continuing to maintain arrow function syntax and block lambda revival as strawmen.
Arrows now require only two-token lookahead, ignoring the #!~ prefixes proposed for non-configurable, non-writable, and non-enumerable property assignments in object initialisers. This is in order to support either an object literal body or a braced non-empty block body where the block's first statement is not a labeled statement.
Block lambda revival has more grammar changes, but so far they check out.
Also, if any { block } could be a lambda, perhaps we won't need that (nor any new) syntax for block-lambdas.
We would need new syntax still, for formal parameters.
Making blocks be expressions requires unifying the ObjectLiteral and Block productions. I don't know how to do this in without at least two-token lookeahead, and it is not a backward compatible change if done for all places where Statement : Block in the current grammar.
Also, I'd prefer to know/see clearly when a function is being call()ed, so I'm not very fond of paren-free calls: foo(bar) is clearly an invocation, unlike foo bar,
Your example is too abstracted to be fair. Concretely, the latter will always look like foo {|| bar} ... and never foo bar for any bar.
and readability is more important than saving a few keystrokes.
Readability arguments support the paren-free syntax too. You can't win this by selective arguing.
The C language is still (and -ISTM- will be for a long time) important, so -IMO- every little bit of JS's C-like syntax is a plus: less to learn: an old, popular, widely used, well-known, and familiar syntax.
C by way of Java, and both are boat anchors. Again, where pray tell is 'function' in C?
JS -unlike other languages- is important enough that it does not need to follow these (dubious) trendy fashions to become popular. Nor to survive.
Nothing trendy about Smalltalk blocks unless you are a Rubyist.
Proper punctuation aids comprehension and we're programming, not writing quick SMSs.
This is silly, you're making vague arguments that cut both ways.
I'm liking the block-lambda syntax, I think more than the arrow. One possible shortening could be to exclude the second bar (|) if no argument variables are specified.
{| // some block of code };
I don't think we'd ever start an expression with a single | operator, so there should be no ambiguity.
On Jun 23, 2011 6:14 PM, "Jorge" <jorge at jorgechamorro.com> wrote:
The C language is still (and -ISTM- will be for a long time) important, so -IMO- every little bit of JS's C-like syntax is a plus: less to learn: an old, popular, widely used, well-known, and familiar syntax.
The fraction of JS programmers who have ever used C is small and shrinking fast. Even for other Algol suburbs.
JS -unlike other languages- is important enough that it does not need to
follow these (dubious) trendy fashions to become popular. Nor to survive.
Do you really believe that these proposals are motivated by fashion?
Proper punctuation aids comprehension and we're programming, not writing
quick SMSs.
Punctuation can be valuable, especially for indicating the relationship between words and phrases, or disambiguating (c.f. uncle Jack and the horse).
But we shouldn't make authors illuminate their leading caps, or write !!!1! whenever they want to exclaim. Abbreviations and are popular in speech because they make expression easier as well as shorter.
Mike
[+stay]
Another bit of evidence for lexical this, and thus for block lambdas over arrow functions or simple # functions.
At < doku.php?id=harmony:proxies&rev=1308499413&do=diff>
I corrected a bug noticed just now by Mike Stay (cc'ed). The bug dates from < doku.php?id=harmony:proxies&rev=1280925681#trap_defaults>
in August of 2010, more than 10 months ago, when Tom van Cutsem wrote:
keys: function() {
return this.getOwnPropertyNames().filter(
function (name) { return
this.getOwnPropertyDescriptor(name).enumerable }); }
This bug was written by one of the most talented and careful programmers I have ever met. The page it appears in has been carefully examined by many experts (not just official experts -- real experts) and a large community of talented people, guiding at least three implementations of the proxy system. The bug was only noticed by Mike Stay while starting the newest of these implementation efforts, emulating proxies on old browsers within ES5/3.
Most JavaScript code will be much less reviewed than this, and by a community less expert than ourselves in the peculiarities of the language. We need to ask, why was this bug so easy for an expert to make and so hard for a community of experts to catch? My guess is that it's because Array.prototype.filter "feels" like a control structure and the anon function we're calling it with "feels" like a control structure block, even though we know that we know otherwise. When we see the "this" inside and try to figure out what it means, we search outward for a "function" and our eye often catches the wrong one.
Let's see what Tom might have written with three of the other function-like proposals. To be fair, I'll assume here that "return" can be omitted in all.
Arrow functions:
keys: function() {
return this.getOwnPropertyNames().filter(
(name) -> this.getOwnPropertyDescriptor(name).enumerable );
}
Simple # functions
keys: function() {
return this.getOwnPropertyNames().filter(
#(name) { this.getOwnPropertyDescriptor(name).enumerable });
}
block lambdas
keys: function() {
return this.getOwnPropertyNames().filter { |name|
this.getOwnPropertyDescriptor(name).enumerable
}
}
The first and obvious point I'm making is that Tom would not have made this particular mistake with block lambdas, but I recognize that this argument cuts both ways. Other uses may have the opposite hazard, and we need to examine this as well. The new point is that arrow functions and simple # functions make the original hazard worse, because, like "function", they still rebind this. But by being less verbose than "function", it's even easier for the eye to miss them when seeing a "this" in their body and trying to determine what it means.
The more I think about this, the more I'm inclined to believe that, for functions which rebind "this", the verbosity of "function" is a virtue. As fallible programmers, we can only afford a more compact function notation for defining functions with lexical "this" -- like block lambdas.
On Jun 23, 2011, at 4:07 PM, Rob Campbell wrote:
I'm liking the block-lambda syntax, I think more than the arrow. One possible shortening could be to exclude the second bar (|) if no argument variables are specified.
{| // some block of code };
This does not work. How do you know that what follows the first | is not a formal parameter?
Formal parameter syntax looks like expression syntax -- currently Expression covers FormalParameterList. Arrow function syntax relies on this.
On Jun 23, 2011, at 5:16 PM, Mark S. Miller wrote:
Arrow functions:
keys: function() { return this.getOwnPropertyNames().filter( (name) -> this.getOwnPropertyDescriptor(name).enumerable ); }
This is not giving arrow functions their due, though. Just use => and all is well.
Yes, you have to remember, but as you note, the "which this?" cuts both ways.
Simple # functions
keys: function() { return this.getOwnPropertyNames().filter( #(name) { this.getOwnPropertyDescriptor(name).enumerable }); }
Aside: we don't have a live proposal to use # this way, with completion value as implicit return value. Full 'return' or something like the empty-label hack I sketched (prefix : followed by tail position) would be needed. Also, # for frozen value counterparts (records and tuples, as well as functions) seems a better use of that character.
block lambdas
keys: function() { return this.getOwnPropertyNames().filter { |name| this.getOwnPropertyDescriptor(name).enumerable } }
The first and obvious point I'm making is that Tom would not have made this particular mistake with block lambdas, but I recognize that this argument cuts both ways. Other uses may have the opposite hazard, and we need to examine this as well. The new point is that arrow functions and simple # functions make the original hazard worse, because, like "function", they still rebind this.
While -> does bind |this| dynamically, => does not.
But by being less verbose than "function", it's even easier for the eye to miss them when seeing a "this" in their body and trying to determine what it means.
You're right that it's easy to miss the bad thing, even if there's a good thing.
The more I think about this, the more I'm inclined to believe that, for functions which rebind "this", the verbosity of "function" is a virtue.
But it didn't help in the story you led with :-).
As fallible programmers, we can only afford a more compact function notation for defining functions with lexical "this" -- like block lambdas.
Or =>?
I'm leaning toward block lambdas myself, but I feel the need to play umpire here a bit -- arrows need fair play including =>, not just ->.
2011/6/23 Brendan Eich <brendan at mozilla.com>:
On Jun 23, 2011, at 4:07 PM, Rob Campbell wrote:
I'm liking the block-lambda syntax, I think more than the arrow. One possible shortening could be to exclude the second bar (|) if no argument variables are specified. {| // some block of code };
This does not work. How do you know that what follows the first | is not a formal parameter? Formal parameter syntax looks like expression syntax -- currently Expression covers FormalParameterList. Arrow function syntax relies on this. /be
Are you thinking that
{| a | a }
it is ambiguous whether
function (a) { return a; }
or
function () { return a | a; }
On Jun 23, 2011, at 7:04 PM, Mike Samuel wrote:
2011/6/23 Brendan Eich <brendan at mozilla.com>:
On Jun 23, 2011, at 4:07 PM, Rob Campbell wrote:
I'm liking the block-lambda syntax, I think more than the arrow. One possible shortening could be to exclude the second bar (|) if no argument variables are specified. {| // some block of code };
This does not work. How do you know that what follows the first | is not a formal parameter? Formal parameter syntax looks like expression syntax -- currently Expression covers FormalParameterList. Arrow function syntax relies on this. /be
Are you thinking that
{| a | a }
it is ambiguous whether
function (a) { return a; }
or
function () { return a | a; }
Precisely.
On Thu, Jun 23, 2011 at 6:44 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 23, 2011, at 5:16 PM, Mark S. Miller wrote:
Arrow functions:
keys: function() { return this.getOwnPropertyNames().filter( (name) -> this.getOwnPropertyDescriptor(name).enumerable ); }
This is not giving arrow functions their due, though. Just use => and all is well.
[...]
As fallible programmers, we can only afford a more compact function notation for defining functions with lexical "this" -- like block lambdas.
Or =>?
I'm leaning toward block lambdas myself, but I feel the need to play umpire here a bit -- arrows need fair play including =>, not just ->.
Hi Brendan, I appreciate the umpire role. You do it well.
I had indeed forgotten that => was part of the arrow function proposal. This
does make it much less likely that Tom would have made the mistake if he'd been programming in JS-with-arrow-functions. But my real point is, if he had made the mistake using "->", would we have been more or less likely to catch
it than in the code he wrote using "function"?
When we talk about syntax, we often focus on the difficultly of writing code, somewhat on the hazards of writing code correctly, and on the difficulty of reading and comprehending correct code. What this thread clarified for me is how little we talk about syntax's influence on our ability to spot problems when reading code, and that we need to focus on that more.
Presently in JS, when seeing a "this", to understand what it means, the eye needs to scan backwards for the closest enclosing occurrence of "function" as a keyword. As this example shows, even this is accident prone. If we introduce multiple special forms that can introduce a this-rebinding boundary, where the new ones are easily missed, we make this burden worse. Is "->" easily missed? (I would guess so.) And if it is, what are we getting
in exchange, and is it worth it?
On Jun 23, 2011, at 9:49 PM, Mark S. Miller wrote:
Presently in JS, when seeing a "this", to understand what it means, the eye needs to scan backwards for the closest enclosing occurrence of "function" as a keyword. As this example shows, even this is accident prone. If we introduce multiple special forms that can introduce a this-rebinding boundary, where the new ones are easily missed, we make this burden worse. Is "->" easily missed? (I would guess so.) And if it is, what are we getting in exchange, and is it worth it?
That's a good point if the special forms are not "just syntax" (including some .bind or var self = this rewriting). Arrows at least do not add semantically to the function-like menagerie, so the problem is not different in kind from the one that bit the 'function'-based code you showed. But block lambdas do not have the hazard at all.
If you take your argument to the limit, it wants to kill 'this' (something I believe you wouldn't mind ;-). This could be just another reason to prefer block lambda revival to arrow function syntax.
However, I recall from the last TC39 meeting that some folks on the committee seemed outraged that block lambda revival (because of TCP conformance -- no way around this) lexically binds 'this', always. I believe the problem they had was that they believed shorter function syntax should be neutral on 'this' binding, to make functions in all their current use-cases more convenient.
This is a difference in risk evaluation or tolerance, perhaps. We should try to get to the bottom of it.
I agree. Furthermore, it seems like newbies would automatically do the right thing with lambdas, whereas with functions, they would have to think about lexical versus dynamic |this| which everyone that I have explained it to so far had difficulty with.
I hope I’m not repeating myself, but if the following rule of thumb holds, block lambdas do indeed offer a simple way of always doing the right thing:
- Methods: dynamic |this|.
- All other functions (callbacks, custom control/looping constructs, etc.): lexical |this|.
Then you can just tell newbies the following best practice:
- Methods => use object literals (where the function operator disappears thanks to Allen’s proposal) => needed for |super|, anyway
- Everywhere else: use block lambdas
If, on the other hand, you know what you are doing, you can always fall back to old-style functions.
From: Brendan Eich <brendan at mozilla.com> Date: June 24, 2011 7:00:39 GMT+02:00 Subject: Re: block-lambda revival
However, I recall from the last TC39 meeting that some folks on the committee seemed outraged that block lambda revival (because of TCP conformance -- no way around this) lexically binds 'this', always. I believe the problem they had was that they believed shorter function syntax should be neutral on 'this' binding, to make functions in all their current use-cases more convenient.
I take it their complaint was not about performance? I would think that block lambdas would not use a function + bind to achieve lexical this, but some kind of variable renaming/alpha conversion. Thus, performance should not be an anti-lambda argument. On the other hand, |this| not being stored in an environment, alpha conversion might not work (move |this| from the Execution Context State to an the environment???).
This is a difference in risk evaluation or tolerance, perhaps. We should try to get to the bottom of it.
Maybe there should be a list of “functions in all their current use-cases”. I keep thinking that dynamic |this| only matters for methods, but you provided a counter-example.
On Jun 23, 2011, at 11:33 PM, Axel Rauschmayer wrote:
I take it their complaint was not about performance? I would think that block lambdas would not use a function + bind to achieve lexical this, but some kind of variable renaming/alpha conversion. Thus, performance should not be an anti-lambda argument. On the other hand, |this| not being stored in an environment, alpha conversion might not work (move |this| from the Execution Context State to an the environment???).
No, block-lambda this access would be like any other upvar, well-optimized.
Performance was not the objection.
This is a difference in risk evaluation or tolerance, perhaps. We should try to get to the bottom of it.
Maybe there should be a list of “functions in all their current use-cases”. I keep thinking that dynamic |this| only matters for methods, but you provided a counter-example.
You are right to push on the question of use-cases. Will try to get a survey done -- Mozilla has several practical analyze-the-web irons in the fire.
On Thu, Jun 23, 2011 at 10:00 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 23, 2011, at 9:49 PM, Mark S. Miller wrote:
Presently in JS, when seeing a "this", to understand what it means, the eye needs to scan backwards for the closest enclosing occurrence of "function" as a keyword. As this example shows, even this is accident prone. If we introduce multiple special forms that can introduce a this-rebinding boundary, where the new ones are easily missed, we make this burden worse. Is "->" easily missed? (I would guess so.) And if it is, what are we getting in exchange, and is it worth it?
That's a good point if the special forms are not "just syntax" (including some .bind or var self = this rewriting). Arrows at least do not add semantically to the function-like menagerie, so the problem is not different in kind from the one that bit the 'function'-based code you showed. But block lambdas do not have the hazard at all.
If you take your argument to the limit, it wants to kill 'this' (something I believe you wouldn't mind ;-). This could be just another reason to prefer block lambda revival to arrow function syntax.
However, I recall from the last TC39 meeting that some folks on the committee seemed outraged that block lambda revival (because of TCP conformance -- no way around this) lexically binds 'this', always. I believe the problem they had was that they believed shorter function syntax should be neutral on 'this' binding, to make functions in all their current use-cases more convenient.
This is a difference in risk evaluation or tolerance, perhaps. We should try to get to the bottom of it.
I missed the risk issue until today. I suspect others have as well.
Until I started thinking about the implications of Tom's bug[1], I was also enthusiastic for shorter function syntax. Yes, I have always been even more enthusiastic about TCP-respecting lambdas, but put that aside for the moment. Assuming I couldn't have those or any other semantic improvement (const functions, etc), until today, my dominant reaction to JS functions has always been "But they're so darn verbose! Can I please have a shorter way to say these? Please?". Be careful what you wish for ;).
The verbosity is a genuine cost. However, I didn't realize the hidden cost I was unknowingly willing to pay in exchange. If the tradeoff is against making it harder to spot errors when reading code, then I'd prefer to pay the current "function" verbosity cost. If you offered me "=>" in exchange
for also accepting "->", I may have agreed yesterday. Today, not.
[1] And that Tom made it. And that we all missed it for over 10 months.
My 2 cents here.
Some people today refuse use of this
entirely to avoid this type of bugs. To do so they start each method with:
var self = this;
Now with arrow functions this pattern can become even easier. Just use fat arrows => everywhere with in a method body, problem solved.
In addition lints
Well, at least I'm consistent in producing this type of bug since I just fixed another one: < doku.php?id=harmony:proxy_defaulthandler&rev=1307082200&do=diff
Clearly, I have been spoiled, programming in languages with similar higher-order functions but with proper block lambda's for too long! ;-)
2011/6/24 Mark S. Miller <erights at google.com>
On 2011-06-23, at 22:37, Brendan Eich wrote:
On Jun 23, 2011, at 4:07 PM, Rob Campbell wrote:
I'm liking the block-lambda syntax, I think more than the arrow. One possible shortening could be to exclude the second bar (|) if no argument variables are specified.
{| // some block of code };
This does not work. How do you know that what follows the first | is not a formal parameter?
Ah, right. I guess you'd need to scan the full block first and look for the second | and then do the right thing for each case. Messy.
On 24/06/2011, at 01:31, Mike Shaver wrote:
On Jun 23, 2011 6:14 PM, "Jorge" <jorge at jorgechamorro.com> wrote:
JS -unlike other languages- is important enough that it does not need to follow these (dubious) trendy fashions to become popular. Nor to survive.
Do you really believe that these proposals are motivated by fashion?
No. That's not what I meant.
There's other (new) languages that need to sell "something" to gain traction and become popular. But JavaScript just does not need to do that.
On Jun 24, 2011, at 7:18 AM, Jorge wrote:
On 24/06/2011, at 01:31, Mike Shaver wrote:
On Jun 23, 2011 6:14 PM, "Jorge" <jorge at jorgechamorro.com> wrote:
JS -unlike other languages- is important enough that it does not need to follow these (dubious) trendy fashions to become popular. Nor to survive.
Do you really believe that these proposals are motivated by fashion?
No. That's not what I meant.
There's other (new) languages that need to sell "something" to gain traction and become popular. But JavaScript just does not need to do that.
Let's get back to the problems with JS functions:
-
Overlong syntax, mainly due to 'function' and 'return' keywords, but also due to lack of expression body alternative or combination.
-
Dynamic 'this' parameter binding.
-
Tennent's principle of abstraction violations other than 'this'.
Arrow function syntax addresses 1 and (via =>) 2. Block lambda revival addresses all three. I think both are worth considering carefully, never mind fashion, for these reasons.
On Sat, 25 Jun 2011 05:50:05 +0200, Brendan Eich <brendan at mozilla.com>
wrote:
Let's get back to the problems with JS functions:
...
- Tennent's principle of abstraction violations other than 'this'.
Why is this a problem?
I see having fully general non-local returns as a bigger problem than not
having them.
If you can declare a function inside another, let the inner function
escape and survive the outer, and is then allowed to try to return from
the outer function again ... that's simply bad. Just throwing an exception
seems a meek response to a fatal logical flaw.
Closures capture the variable environment of their point of declaration.
That's possible because the environment chain (if not the content of it)
is actually statically defined at the time the function closure is
created. It will keep making sense, no matter what happens later.
The stack frames/program counter/other dynamic behavior cannot be
meaningfully captured in the same way. It's not guaranteed to be the same
when the inner function is eventually called, or to even exist any more.
It's possible to define the language in such a way that this would make
sense, by snapshotting the dynamic state, but that would effectively
introduce call/cc, which seems like a overdoing it.
Exceptions work because they are defined in terms of the dynamic control
flow, not syntactic inclusion. Making a non-local return from an inner
function is just as senseless as expecting a syntactically containing
try/catch to catch a thrown exception.
The usecase generally used for non-local returns are user-implemented
control flow operators. This use would typically require only down-calls,
where all calls to the closure are performed inside the same invocation of
the calling function.
That's actually just a hack for emulating real call-by-name (which is what
best corresponds to the behavior of the built-in
control from operators like "for" and "while"). But even that can be
foiled, if you can capture a call-by-name argument
in a closure.
The current abstraction: function, function call and return, works so well
because it does what it does well - abstract
a statement with a single entry and a single exit (excepting exceptions
ofcourse :), which means that it can be used
wherever a statement can be used. Or an expression, if it returns a value.
It's a clean abstraction.
Adding non-local control-flow to a function allows it to have more than
one exit. It means that expressions can now
break or continue a loop, or return from the local function, where before
only statements could.
I think this got more confusing than I had hoped it would.
I guess the summary would be: I don't think Tennent's principle of
abstraction can be meaningfully applied to control flow when your
abstraction can survive the control flow that created it.
/L 'but "this" is a problem that is fixable'
On Jun 27, 2011, at 4:49 AM, Lasse Reichstein wrote:
On Sat, 25 Jun 2011 05:50:05 +0200, Brendan Eich <brendan at mozilla.com> wrote:
Let's get back to the problems with JS functions:
...
- Tennent's principle of abstraction violations other than 'this'.
Why is this a problem?
We've been over this, see Dave Herman's recent reply where he talked about macros. Now Dave was careful not to endorse block-lambdas ahead of having macros, but the reason macros want lambdas applies just as well to many hand-coding "human macro processors", too.
Tennent's observation is really about refactoring, eta-conversion if you will. JS today makes that hard. With block-lambdas it is easy.
I guess the summary would be: I don't think Tennent's principle of abstraction can be meaningfully applied to control flow when your abstraction can survive the control flow that created it.
Closures survive control flows that create them, and they can throw exceptions. That's different in some ways from the situation with return from a block-lambda whose containing function activation has died, but not materially in practice, from all I hear (from Smalltalk, Ruby, and E users).
It's easy to make this a "problem" in advance of evidence. I'd rather not argue hypotheticals. This may mean prototyping block-lambdas and user-testing them, although that will not be conclusive unless there are strong negative results (which could be worth it).
On Jun 27, 2011, at 12:49 PM, Lasse Reichstein wrote:
I guess the summary would be: I don't think Tennent's principle of abstraction can be meaningfully applied to control flow when your abstraction can survive the control flow that created it.
The success of such an abstraction for building control flow abstractions in Smalltalk is a good demonstration that, in practice, your conclusion is not universally true.
For a view of the early origins of this see "Building Control Structures in the Smalltalk-80 System" By L. Peter Deutsch in the PDF at www.em.net/portfolio/2010/08/smalltalk_in_byte_magazine.html starting at page 125 of the PDF. But be warned that some of this goes beyond what the use of block-lamda's and is not totally relevant. Early Smalltalk blocks weren't true closures but over time they became so.
I guess the summary would be: I don't think Tennent's principle of abstraction can be meaningfully applied to control flow when your abstraction can survive the control flow that created it.
The success of such an abstraction for building control flow abstractions in Smalltalk is a good demonstration that, in practice, your conclusion is not universally true.
For a view of the early origins of this see "Building Control Structures in the Smalltalk-80 System" By L. Peter Deutsch in the PDF at www.em.net/portfolio/2010/08/smalltalk_in_byte_magazine.html starting at page 125 of the PDF. But be warned that some of this goes beyond what the use of block-lamda's and is not totally relevant. Early Smalltalk blocks weren't true closures but over time they became so.
Nice, thanks!-)
However, we should be aware that ideas evolve over time. It is great that this list has contributors with direct experience in so many major languages. With the general open-mindedness here, this bodes well for JS evolution, even in the face of design by committee and massive deployed code base.
But experience with a major language shapes mindsets - what one language's programmers expect from a lambda differs from what those of other languages expect. Please take the following overview with a pinch of smileys - though I hope it isn't totally misrepresenting the differences. Dave has already raised the closely related question of macros, so I'll focus on macros, blocks, and control structure abstractions:
Lispers had it all first (just kidding, of course, you could do it
before Lisp, by tweaking the rules of your universe so that
Lispers would evolve in it;-), but they did have an AST instead
of a syntax, wouldn't leave home without macros, and were
expected to code around little dangers like side-effects, dynamic
scope, and scope-breaking macros. In those optimistic times,
there was hardware, not just IDEs, specifically for the language,
and at least one Lisp environment had a Do-What-I-Mean tool.
Schemers thought side-effects and macros necessary, but
uncontrolled scope manipulation dangerous, so they insisted
on lexical scope and hygienic macros and found that those
constraints required some thought, but were not really limiting.
Lispers and Schemers have adapted to more recent fads mostly
by demonstrating that each could be done in their existing systems,
usually with very little effort, if one ignores the syntax. This
includes control structures, (meta-)object, aspect, and other systems. They also enjoy porting their systems to any new platform that comes along.
Smalltalkers sometimes do not distinguish between language,
program, and IDE, or between programming and reflection, but
since their model of computation goes back to communicating
computers (each with code and data, and its own interpretation
of incoming messages), objects, messages and blocks of operations
are natural building blocks to them.
So everything is decomposed into objects, and can be redefined
while the system is running, but blocks are not decomposed into
statements - though message cascading (aka method chaining)
could be used to that effect, that is separate from blocks.
Smalltalkers do not adapt to fads, they just port their system
to anything new that comes along, or use something else if
-and only as long as- they must.
Haskellers can go so far without macros that they sometimes
forget these are useful, ban side-effects to the communication
boundaries of their code, and treat statements as just another
form of expression to be computed and manipulated. They
broke down blocks into statements composed by re-definable
semicolon long ago, and have been building finer-grained
control structure libraries ever since.
They define control structures, re-interpret code over different
kinds of effect, combine and control effects, and argue about
what kinds of effect what kinds of code should be given access
to. They think that being able to reason about code is usually
more important than it being able to modify itself at will.
They do have several levels of macro processing, including
syntax-based (Template Haskell) and string-based (QuasiQuoting)
compile-time meta-programming, combined with Haskell-in-
Haskell parsers as well as API access to compiler and runtime
internals, not to forget rebindable syntax. None of which has
reached the level of standards yet.
Haskellers are only slowly adapting to the thought that
there could be fads after Haskell.
As you can see, my own mindset is also influenced by my experience, though I am aware that there have been gains and losses in this evolution (and I've omitted important languages, to focus on the lambda-related aspects). Also, Javascript can not equally easily adopt ideas from all these languages - some are closer than others. But all of these languages support "building control flow abstractions", each in its own way. Not all use macros, not all use blocks.
It seems undisputed here that it is important to separate reflection from normal operation, and I think that macros (fairly free functions from syntax to syntax, only limited by respecting scope) should be separate from more restrained "normal-mode" functions (functions between semantically meaningful syntactic categories, to use the phrase from Tennent's principle of abstraction).
In languages with non-trivial syntax (like Javascript), a macro system also has to offer more than just functions from ASTs to ASTs. Macros should not be used where normal functions are sufficient, such as control-structure abstraction libraries, but macros as programmable syntactic sugar could make such abstractions more readable (and reduce the pressure to standardize convenient language constructs like classes).
Also, I think that the statement block is too coarse a building block for control-structure abstractions, however nice it looks syntactically, for selected examples (even that advantage can be nullified by suitable generic control-structure sugar, as shown by F#'s computation expressions or Haskell's do-notation).
Take just one code example that was mentioned here earlier - search for 'eval' on this page:
www.cs.rit.edu/~ats/projects/jsm/paper.xml
How would block lambdas help to make monadic interpreter code like that more readable, or -since monads are a central control-structure abstraction - how would block lambdas achieve the same flexibility by other means?
Block-lambdas have interesting uses, so if those with experience think their unusal aspects can be handled, I won't argue against that, but I don't see how block-lambdas could replace either of macros or better functions, both of which I consider important. So I do have to argue against dropping work on better functions (in favour of block-lambdas).
Claus
As promised/threatened: strawman:block_lambda_revival
No GLR parser required (destructuring and left-hand side of assignment still want it for a precise grammar).
I'll work on semantics for the new productions and builtin Block object.
As noted in the prologue, I think we should do arrow function syntax and not (ever) this strawman, or block lambda revival and not (ever) arrow function syntax. Comments welcome.