Expression closures - use-cases for shortcut lambda syntax (blocks)
On Mar 15, 2007, at 5:37 PM, Vassily Gavrilyak wrote:
In our team we currently using modified SpiderMonkey for JS and
MTASC for AS with modified lexer (not parser) for support shortcut syntax. We use this syntax
quite heavily and found it very useful and not a false economy. Modification is VERY simple (2 lines in both cases) - when lexer
found "(" replace it with "function("
Don't you mean ( instead of (\ above?
I am warming up to this use of backslash. What do others think?
This way ES could almost for free have a feature that many
languages (C# 3, Ruby, Smalltalk, Perl) already have and developers found this feature useful. Java seems going there
too with closures proposal.And yes, this is just syntax sugar and nothing new for ES that
aren't already present. But developers will fail to recognize that ES actually have blocks
just because of syntax. I am the example of such developer :-).Hope it's probably not to late to address those issues.
Syntax matters, it's the user interface. It is not too late, but it
is late. We should discuss pros and cons here in a thread.
On 3/15/07, Brendan Eich <brendan at mozilla.org> wrote:
I am warming up to this use of backslash. What do others think?
+1
Syntax matters, it's the user interface. It is not too late, but it is late. We should discuss pros and cons here in a thread.
PLT Scheme allows λ for 'lambda'. JS could allow λ for 'function' and backslash as a poor-man's λ. I know it's lame to add two synonyms for function, but it's also lame to use backslash explicitly as a lambda-lookalike in a language that already accepts lambdas in identifier syntax.
On Mar 15, 2007, at 6:35 PM, Jon Zeppieri wrote:
On 3/15/07, Brendan Eich <brendan at mozilla.org> wrote:
I am warming up to this use of backslash. What do others think?
+1
Syntax matters, it's the user interface. It is not too late, but it is late. We should discuss pros and cons here in a thread.
PLT Scheme allows λ for 'lambda'. JS could allow λ for 'function'
and backslash as a poor-man's λ. I know it's lame to add two synonyms
for function, but it's also lame to use backslash explicitly as a lambda-lookalike in a language that already accepts lambdas in identifier syntax.
My Unicode implant is on back-order -- what code point is that? Any
clue on how to type it on my Macbook Pro? Thanks,
I think, once I've figured out how to actually type "λ" on my UK keyboard (I actually don't know how to enter 0x03BB in my keyboard, I had to paste from charmap), I might as well have typed "function(".
Also, "λ" is a perfectly permissable character for an identifier. Wouldn't it cause problems to give it special meaning? (for Greeks especially)
Peter
On 3/15/07, Peter Hall <peter.hall at memorphic.com> wrote:
I think, once I've figured out how to actually type "λ" on my UK keyboard (I actually don't know how to enter 0x03BB in my keyboard, I had to paste from charmap), I might as well have typed "function(".
Right -- which is why I also like backslash.
Good programming editors, of course, allow you to bind characters to whatever keys you want. In DrScheme, on the Mac, λ is mapped to command-\ by default. Mail clients tend not to be good programming editors...
Also, "λ" is a perfectly permissable character for an identifier. Wouldn't it cause problems to give it special meaning? (for Greeks especially)
Good point. It could break some code.
I am warming up to this use of backslash. What do others think?
+1
It seems to me that ES4 is rapidly using up every combination of easily typable non-identifier characters. Apart from reducing the choice of possible syntax for ES in the future (look at the type parameters discussion), I think a lot of developers who are comfortable with existing JS and AS versions will have a number of wtf moments when they see this stuff used for the first time. Does it really offer enough reward?
Peter
Peter Hall scripsit:
I think, once I've figured out how to actually type "?" on my UK keyboard (I actually don't know how to enter 0x03BB in my keyboard, I had to paste from charmap), I might as well have typed "function(".
Hence the desire to allow \ as a synonym.
Also, "?" is a perfectly permissable character for an identifier. Wouldn't it cause problems to give it special meaning? (for Greeks especially)
Not really, no more than it causes problems to give "if" special meaning.
Also, DrScheme has a keybinding that allows you to type Ctrl-\ and it inserts a lambda, which might make a nice tie-in between the two different syntaxes.
I am warming up to this use of backslash. What do others think?
I like it.
Although (prepare for gratuitous land grab), I have to say one of my
top coding typos with lambda-as-argument is failing to remember the
closing paren. It would be really nice if by syntactic sugar a
block following a function call was converted into a lambda parameter
at the end of the arguments list, so that:
ajax(url, \(response) {
// do something
});
could become:
ajax(url) {
var [response] = arguments;
// do something
}
Even though my version has more characters due to the arguments
destructuring, I'd gladly trade that off for the enhanced readability.
ducks
On Mar 15, 2007, at 6:55 PM, Peter Hall wrote:
I think, once I've figured out how to actually type "λ" on my UK keyboard (I actually don't know how to enter 0x03BB in my keyboard, I had to paste from charmap), I might as well have typed "function(".
Or at least "" ;-).
Also, "λ" is a perfectly permissable character for an identifier. Wouldn't it cause problems to give it special meaning? (for Greeks especially)
Yes, you are right -- ES3 allows Unicode identifiers, so this would
not be a backward compatible change. It seems excessive anyway, if \
(x)x is acceptable for (lambda (x) x).
On 16/03/07, Jon Zeppieri <zeppieri at gmail.com> wrote:
On 3/15/07, Brendan Eich <brendan at mozilla.org> wrote:
My Unicode implant is on back-order -- what code point is that? Any clue on how to type it on my Macbook Pro? Thanks,
Code point: 0x03bb
There are at least three ways to enter the character on the Mac.
In addition to those, editors written specifically for editing ECMAScript 4 will also be able supply an easy method on inserting it via either text replacement (insert ( and it magics that into λ) or via some keypress combination.
One problem here is that text documents default to non-unicode code pages on Windows (no idea about other OSes), and servers don't usually serve javascript as utf-8 or utf-16.
On Mar 15, 2007, at 7:38 PM, John Cowan wrote:
Also, "?" is a perfectly permissable character for an identifier. Wouldn't it cause problems to give it special meaning? (for Greeks especially)
Not really, no more than it causes problems to give "if" special meaning.
js> o = {if: 42} [object Object] js> o.if
42 js> function if(x){return 1+x}
js> if(42) /* oops */; js> function::if(42)
43
The scenario to consider is: existing ES3 content uses the lambda
unichar as a single-character identifier of a function. Then λ(x) +x
looks like a call to the λ function, then an evaluation of a variable
x, then an add operator evaluation. If λ is short for function, then
the example sentence is ambiguous.
why not
{(x,y) \ x + y }
then it would be
ajax(url, { (response)
// do something });
... you can then consider introducing
{ (x,y) | x % 2 == y}
which just constrains the body to be a predicated!
Ok, this is getting out of hand ;-).
Seriously, we are not going to inject any alien syntax. Vassily
proposed a shorthand in expression closures: \ for function. Let's
try to get back to that.
On Mar 16, 2007, at 1:49 AM, Daniel C. Wang wrote:
why not {(x,y) \ x + y } then it would be ajax(url, { (response)
// do something });... you can then consider introducing
{ (x,y) | x % 2 == y}
That's ambiguous: , | % and == are all dyadic operators.
which just constrains the body to be a predicated!
This isn't a comprehension. Not sure why the syntax constrains the
expression to be boolean.
On 2007-03-16, at 04:58 EDT, Brendan Eich wrote:
Seriously, we are not going to inject any alien syntax. Vassily proposed a shorthand in expression closures: \ for function. Let's try to get back to that.
I don't see the value, except for the 'obfuscated Javascript
contest'. If you make that many closures in a day, why not bind c-\
to insert function
in your editor? I would rather programs were
easy to read than easy to write, since I spend a lot more time doing
the former. [Another pet peeve: I wish !
were spelled not
. It
is too easy to overlook.]
On 16/03/07, Brendan Eich <brendan at mozilla.org> wrote:
Yes, you are right -- ES3 allows Unicode identifiers, so this would not be a backward compatible change. It seems excessive anyway, if
(x)x is acceptable for (lambda (x) x).
And if farther (args) expression would stand for function(args) { return expression; }
then JS would be close to beating ML and friends in shortness of defining lambdas.
, Igor
On 16/03/07, P T Withington <ptw at pobox.com> wrote:
On 2007-03-16, at 04:58 EDT, Brendan Eich wrote:
Seriously, we are not going to inject any alien syntax. Vassily proposed a shorthand in expression closures: \ for function. Let's try to get back to that.
I don't see the value, except for the 'obfuscated Javascript contest'. If you make that many closures in a day, why not bind c-
to insertfunction
in your editor? I would rather programs were easy to read than easy to write, since I spend a lot more time doing the former.
For me function(args) { .... } is just too verbose and makes code harder to read especially in the case of many short lambdas.
, Igor
On 3/16/07, Igor Bukanov <igor at mir2.org> wrote:
On 16/03/07, Brendan Eich <brendan at mozilla.org> wrote:
Yes, you are right -- ES3 allows Unicode identifiers, so this would not be a backward compatible change. It seems excessive anyway, if
(x)x is acceptable for (lambda (x) x).And if farther (args) expression would stand for function(args) { return expression; }
then JS would be close to beating ML and friends in shortness of defining lambdas.
Shortness isn't a reasonable metric by itself. (Beating ML and Haskell even less so.) The language has to balance compactness and clarity. And though ES has aspects of a functional language (and ES4 will be even more so than ES3), it is more fundamentally imperative. No doubt many uses will be found for the expression function syntax, but going to this level of terseness does not seem to be justified. As Tucker said, programs are read much more often than they're written. "function" provides all readers with a reasonable idea about what follows. "" is overly terse, and makes sense only if you think "lambda" would make sense in the same context (I don't, particularly -- it's bad UI, to use Brendan's terminology).
Just to recap
Vassily wrote:
- Some simplest idioms like 10.times do {} from Ruby. (10).times(function(){ print("Hi"); })
Now, all those "function" just doesn't look like functions, bat rather blocks. So word function is meaningless here and unnecessary complicates the
code.
Brendan wrote:
Syntax matters, it's the user interface. It is not too late, but it is late. We should discuss pros and cons here in a thread.
Graydon wrote:
A little haskell-loving part of me likes this (the backslash, not the lambda), but I also don't think it reads very well. Actually, no: I think "function(x) x" reads poorly, and this makes it either slightly better or slightly worse depending on your skill level. New users will balk, experienced users will like it.
Brendan wrote:
Seriously, we are not going to inject any alien syntax. Vassily proposed a shorthand in expression closures: \ for function. Let's try to get back to that.
Lars wrote:
I think "function" is significantly more self-explanatory to everyone not coming from the functional programming camp, and probably even to some of those (I include myself). Anyone from the C/C++/Java/C#(?) camp will read "" as meaning "the following character has an unusual meaning", which is not right here. So you're overloading "". This is not the end of the world, but a consideration.
The committee has already decided not to use "fun" as a shorter keyword for "function" because the savings were not worth the cognitive load. I think the same argument applies for "".
In fact, the experience I've had with using the expression-function form in the standard libraries suggests that it is really only useful for fairly trivial stuff -- a couple of lines of code at most. Beyond that you'll be much happier going to a multi-statement form, because the expression language is so limited -- it's not like Scheme, say, where everything is an expression. So I would suggest that we not overestimate the value of the expression-function form. (And once you go beyond a couple of lines, the extra "{", "}", and "return" make little difference in the amount you have to type and read, and generally make things more readable.)
P. T. Withington wrote:
I would rather programs were easy to read than easy to write, since I spend a lot more time doing the former.
Igor wrote:
For me function(args) { .... } is just too verbose and makes code harder to read especially in the case of many short lambdas.
Lars wrote:
Shortness isn't a reasonable metric by itself. (Beating ML and Haskell even less so.) The language has to balance compactness and clarity. And though ES has aspects of a functional language (and ES4 will be even more so than ES3), it is more fundamentally imperative. No doubt many uses will be found for the expression function syntax, but going to this level of terseness does not seem to be justified. As Tucker said, programs are read much more often than they're written. "function" provides all readers with a reasonable idea about what follows. "" is overly terse, and makes sense only if you think "lambda" would make sense in the same context (I don't, particularly -- it's bad UI, to use Brendan's terminology).
Dave wrote:
FWIW, I think I agree with Tucker and Lars. I don't see much value in introducing single-token synonyms. If it's a writeability issue,
mumble
mumble keyboard shortcuts and IDE's mumble mumble.
Re: readability, I just don't see the 5 extra characters as that huge
a
hardship. In a perfect world maybe the operator would have been "fun", but I think programs can survive a few gratuitous "-ction"s.
Remember (Igor) that ES4 introduces a shorter form of function expression which avoids the need for '{', 'return', and '}' -- not a small step forward in saving characters, and in expressing more naturally the idea of functions as expressions. As Brendan and Dave remind us, what is being proposed is to have a shorter spelling for 'function' in this case. As Lars and Dave point out, the working group has tried to save shave some of those eight characters to no avail.
So here is where we stand:
The primary arguments for the proposal are based on ease of typing and aesthetics for some (expert?) programmers. The primary arguments against are based on ease of reading and aesthetics for some (non-expert?) programmers. Lars also points out that the use of '' in this way is lexically inconsistent with the rest of the language and other C like languages where it means "escape the next character". This contributes to the readability problem for users with less sophisticated mental grammars.
The way forward is to identify who are the users at the interface we are designing? If they are primarily the sophisticated kind who read and write lambda expressions without missing a character, then I say the proposed feature pays for itself. If not then I say the cost to readability is too high for too many users and the proposal should be rejected.
Does anyone want to argue that most of our users are of the sophisticated kind?
I don't see the value, except for the 'obfuscated Javascript contest'. If you make that many closures in a day, why not bind c-
to insertfunction
in your editor? I would rather programs were easy to read than easy to write, since I spend a lot more time doing the former. [Another pet peeve: I wish!
were spellednot
. It is too easy to overlook.]
+1 on the -considered-harmful position.
If the desire is to save keystrokes, then we're optimizing for the wrong prolem.
To be sure, terseness is a desirable quality in code, but never at the expense of clarity.
From my position (C/C++/Java-ish background), the terseness is far
outweighed by the sheer "wtf?" factor; I look at the code and try to figure out why ( is being escaped by . The sorta-looks-like-a-lambda connection is (IMHO) gonna be very tenuous to the typical JS programmer.
I find this construct to be extraordinarily hard to read, and it was legal, would probably try to institute a coding convention on my projects that its use should be avoided.
Steven Johnson scripsit:
To be sure, terseness is a desirable quality in code, but never at the expense of clarity.
Clarity, in this case, is nothing but familiarity.
Brendan: No, I didn't meant (, I meant exactly (
It looks like lambda, reads nicely and works good with current lexers (at
least for our code base with SpiderMonkey and MTASC)
All we did in jsscan.c is
case '(':
if (MatchChar(ts, '\')) {
UngetChar(ts,'(');
tt = TOK_FUNCTION;
tp->t_op=JSOP_NOP;
} else {
tt = TOK_LP
}
Next about readability. My primary concern IS exactly readability, I never mind typing additional characters. But I want to clearly show the intent in my code. I want to see word 'function' when it means exactly function. And I want to read 'block' when it means block. so function someFunction(){ // this is a FUNCTION using(File("hello". "r"), (\ file){ // this is a block that uses file. process(file); }) // file will be closed here. } We would be probably happier with clearer syntax (without \ and some parenteses) but such "feature" can break old code and introduce corner cases.
For me even using word 'block' will be good, but why we need more keywords if we can do without them.
About C/C++/Java background. Well, everybody in our team has exactly this background. We dumped all those 3 'bigs' in favor of JS and never looked back since.
And everybody accepted the proposal to hack the lexers only to show the clear intent in the code, so everybody will understand what's going on. And we did it cause we have a lot of above provided use-cases in our code (web and game development). That was hard decision, cause nobody want to maintain foreign code base and apply diffs with every version. But we did it and now we are using Spidermonkeys eval/uneval trick just to make legacy browsers be happy with our code. That's the only 'fork' we did, cause JS is just 'right' for us. We do not want operators, overloading, multiply inheritance and any other 'cool' staff (And that's with C++ background :-) ). But we need to write async code, use non-GC resources and do transactions in database all the time. Java-way is too complex and error prone here (from our past experience).
About project policy. Adobe project policy probably should disallow lambda and use OO. Our project policy disallowed AS2 OO-extensions and favored lambda. People are just different and projects are different and that's ok. Language should provide mechanism, not policy. That's why JS won for us.
Now, not everybody has C/C++ background. Ruby and Perl developers will be just HAPPY with block syntax. Ruby is success because of Rails. Please look at Rails examples and count number of usage of blocks here. Lots of Java/C# people escaped to Ruby (sadly, not to JS) and are just happy. There are rumors that all ex-Perl hackers are becoming JS-hackers right now and found a good language. C# coders will follow shortly. They have using(resource) {} now and will have blocks in 3.0. Scala have only one behavior pattern - 'loan', that looks exactly like first thow use-cases for resource and transaction management.
Maybe we can emulate some other language here, e.g Ruby with '|' instead of . For us Haskell's lambda is nice enough.
To conclude: -Proposal is about clarity, not terseness, please do not accept it as 'just to save 6 keystrokes' -Not everybody has C/C++/Java background. Lot of people have Perl, Ruby, PHP background. -Blocks with proposed syntax are simple to implement and do not break current code. -Provided use-cases are widespread in web-programming and are currently hard to write and read clearly in ES3. Blocks can fix this.
Thanks for all interested , Vassily Gavrilyak
Jeff, concerning who are 'sophisticated' users.
Ruby and Smalltalk and Perl users found nothing sophisticated with blocks. And most of those users are web-developers, so they will need to deal with ES anyway. They are even considered less sophisticated then 'real' Java/C++ developers, cause of scripting nature.
About current ES user base. Is seams that most JS libs use (function(){}) trick for namespaces and function callbacks for AJAX calls. People are already using the closures and carefully counting the ()s. Well, actually text editors count them :-). But that's about writing. When reading I do not look at the separators, just the words and indentation, and ocasional meaningless 'function' keyword in 'wrong' just breaks the words reading process.
Syntax could be much clearer and I (and most scripters) would be very happy to have clear blocks like in Ruby.
File.open("file.txt") do | file file.process() end
or Groovy's
File.open("file"){ file-> //process } Or C# 3 C# 3.0 Specification File.open("file", (file))=>{ //process });
But first is not-ES and second is broken with auto semicolon insertion. May be C# way will be good, and C# is ECMA too. It has the same superfluous () as ES3, but looks good. It seems harder to implement and probably can break some syntax, but C# user base is huge. Unfortunately I have no better ideas of clear syntax. Seems any nice idea will break something in current syntax. Hope it will be born is this discussion.
Vassily Gavrilyak
On Mar 16, 2007, at 2:17 PM, Vassily Gavrilyak wrote:
Brendan: No, I didn't meant (, I meant exactly (\
Sorry, I mis-remembered your blog post (http://
weblogs.mozillazine.org/roadmap/archives/2006/05/
javascript_2_ecmascript_editio.html#comments). Indeed you have always
written (\ formals ) expression -- but now I'm cooling, not warming,
to the (\ proposal.
We dumped all those 3 'bigs' in favor of JS and never looked
back since. And everybody accepted the proposal to hack the lexers only to show
the clear intent in the code, so everybody will understand what's
going on.
I agree with an earlier poster that clarity depends on one's
background, therefore familiarity can make idioms clear.
Digression: sometimes "idiom" is abused to mean "cliché", but the
latter is just a common phrase or construct, whose meaning may or may
not be clear from denotation and connotation of component words or
symbols. "Idiom" is something like the English phrase "kick the
bucket" (meaning to die), whose definition cannot be deduced from its
components. With your proposed lambda syntax, (\ formals )
expression, we definitely have another idiom for JS hackers to learn.
This idiom would not re-use brain-print from any other popular
language. So familiarity would have to be acquired at some cost. That
cost may be no lower or higher than the cost of learning expression
closures as proposed (function (formals) expression), but it seems
higher to me given the oddness of (.
To conclude: -Proposal is about clarity, not terseness, please do not accept it
as 'just to save 6 keystrokes'
Seven keystrokes, by my count.
-Not everybody has C/C++/Java background. Lot of people have Perl,
Ruby, PHP background.
But (\ is unclear to users of those languages, it is a new idiom.
-Blocks with proposed syntax are simple to implement and do not
break current code.
True, but same goes for expression closures.
-Provided use-cases are widespread in web-programming and are
currently hard to write and read clearly in ES3. Blocks can fix this.
"Hard to write" must mean those seven extra keystrokes. I'm
sympathetic to a point, but readability trumps writability.
"Hard to read" must mean hard on account of the repeated "function"
word, which becomes noise to signal at scale. I'm sympathetic to
this, I often comment on it (e.g. when I show the Y combinator in JS
in presentations such as the one I gave at ICFP 2005).
However, (\ is really an abuse of backslash, as others have noted.
It looks less like a lambda every time I see it (subjective, but I'm
just giving my opinion).
I agree that there's no point in adding a "block" keyword.
So without an idiom from another language, I'm cool again. Sorry, but
feel free to propose a better syntax. We are not out of room in the
grammar yet!
So without an idiom from another language, I'm cool again. Sorry, but
feel free to propose a better syntax. We are not out of room in the
grammar yet!
How about a slightly-modified version of Ruby's block syntax?
(10).times( |number| { console.log(number); });
The pipe character delimits the arguments, with the only difference being that the arguments occur outside of the braces. In Ruby this would be
10.times { |number| puts number }
On 16/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
Next about readability. My primary concern IS exactly readability, I never mind typing additional characters. But I want to clearly show the intent in my code. I want to see word 'function' when it means exactly function. And I want to read 'block' when it means block. so function someFunction(){ // this is a FUNCTION using(File("hello". "r"), (\ file){ // this is a block that uses file. process(file); }) // file will be closed here. }
You do not need function closure here in ES4, use generators instead (that already works in SpiderMonkey since Firefox 2.0):
for (file in autoFile("hello", "r")) { process(file); }
where autoFile is a generator:
function autoFile(name, mode) { File f = new File(name, mode) try { yield f; } finally { f.close(); } }
That is, using idiom from C# is already available in JS with generators so it can not be used as an argument for () expression.
, Igor
I agree that (\ is not familiar for Perl or Ruby developers. It's Haskell's idiom and the combination (not a slash) just looks like lambda, that's why we choose it. Maybe it looks for good me because cyrillic analog of letter lambda looks exactly this way :-) (\ x -> 3*x + x/4) [1,2,3,4].
Parenthesis are not mandatory in Haskell and I think that's why you thought about slash before and not after. But I agree that it is new for JS users. What's left from other languages | | from Ruby -> or => from ML, Haskell again, haXe, Groovy and C#.
| | loolks better for me and I like Andrew's proposal. It clears the parenthesis and seems to work nicely with current syntax and functions expressions () too. The only thing worried me - without arguments it will be exactly the || operator.
-Blocks with proposed syntax are simple to implement and do not break current code. True, but same goes for expression closures.
Expressions closures are genius idea and I like it very much. It just covers different use cases.
Vassily Gavrilyak
Well, I agree, it is available such way too. And it is avalivaible with try {} finally{} from long time ago. It's just the same issue of readability. The question - what is for doing here, where is the loop immediately come to mind when reading such construct. The same for try - what this code is supposed to catch here? 20 lines below
- ah, nothing, that was for closing resource. We can even do this with if, goto, while, switch anything. But there are better ways :-)
Vassily Gavrilyak
On 17/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
File.open("file.txt") do | file file.process() end
and
Well, I agree, it is available such way too. And it is avalivaible with try {} finally{} from long time ago. It's just the same issue of readability. The question - what is for doing here, where is the loop immediately come to mind when reading such construct.
Ruby's do construction is also a loop, not a lambda, almost exactly corresponding to the generator example in JS, yet it was given as an example to support the need for short lambdas in JS.
I think the issue here is that the generators are not yet widely used in JS so the code like for (var f in autoFile("file.txt")) processFile(f); still looks strange.
But, compared with lambda proposal, that code is in fact shorter, much more clear IMO and can be optimized better.
For that reason I think examples like this can not be used to support the lambda shorthand. What can be used is a case like:
var filtered = array.filter((\ elem) elem > 0)
versus
var filtered = array.filter(function(elem) { return elem > 0; })
, Igor
On Mar 16, 2007, at 5:34 PM, Vassily Gavrilyak wrote:
Well, I agree, it is available such way too. And it is avalivaible with try {} finally{} from long time ago. It's just the same issue of readability. The question - what is for doing here, where is the loop
immediately come to mind when reading such construct. The same for try - what this code is supposed to catch here? 20
lines below - ah, nothing, that was for closing resource. We can even do this with if, goto, while, switch anything. But there are better ways :-)
There are duals competing in language design. You see this in Python,
where comprehensions and generators are favored over more functional
programming idioms (hence the crippled one-line lambda). Some
languages start with lambdas and grow sugar (comprehensions) and
iterable coroutines (generators). I'm hesitant to pick a winner, and
we have already said that we don't believe in minimizing JS/ES into a
shiny, Scheme-like jewel that requires users from beginner to
intermediate skill level to become too expert too soon.
We want conveniences, especially where they reflect existing practice
in JS and "nearby" languages (I argue Python is one such language).
We will always have first-class functions.
Given all this generalization, I would like to make a particular
suggestion: keep focusing on expression closure use-cases, and show
"A" vs. "B" examples with the different suggested syntaxes (all
suggestions). Pick use-cases that come from real code, Ruby or
whatever. Don't use miniature fakes. Then perhaps we can come to
agreement quickly.
Igor Bukanov wrote:
You do not need function closure here in ES4, use generators instead (that already works in SpiderMonkey since Firefox 2.0):
for (file in autoFile("hello", "r")) { process(file); }
This is a bit of a side note, but this type of usage of generators can be misleading to novice programmers, so I'm not sure if it's any better. If I were new to the language, I would interpret this as some type of for loop that can process multiple files. The intent that you'd like the code to show is that process(file) is called only once and is cleaned up afterwards.
The Python guys have in fact talked a lot about this specific construct:
www.python.org/dev/peps/pep-0343, www.python.org/dev/peps/pep-0340 (rejected in favor of PEP 343) www.python.org/dev/peps/pep-0310 (rejected in favor of PEP 343)
- Yuh-Ruey Chen
On 3/16/07, Brendan Eich <brendan at mozilla.org> wrote:
Pick use-cases that come from real code, Ruby or whatever. Don't use miniature fakes. Then perhaps we can come to agreement quickly.
Here are some examples from culled from the web for MochiKit's addLoadEvent function:
addLoadEvent(function () { var elems = getElementsByTagAndClassName("A", "view-source"); var page = "rounded_corners/"; for (var i = 0; i < elems.length; i++) { var elem = elems[i]; var href = elem.href.split(///).pop(); elem.target = "_blank"; elem.href = "../view-source/view-source.html#" + page + href; } });
addLoadEvent(function() { $("debug").innerHTML = repr(getElementPosition($("street"))); $("debug").innerHTML += "<br>"; $("debug").innerHTML += repr(quirksmode.findPos($("street"))); });
addLoadEvent(function(){ for(var _70 in DomDeco.registry){ for(var i=0;i<DomDeco.registry[_70].length;i++){ DomDeco.apply(_70,DomDeco.registry[_70][i]); } } });
addLoadEvent(function () { var d = wait(0.5, {data: [["getUsers", "getUsers"], ["foo", "bar"]]}); d.addCallback(showSelectData); d.addErrback(showError); });
Nested anonymous functions:
addLoadEvent(function(){ connect('pagelist','onclick', function (e) { e.preventDefault(); var d = loadJSONDoc("${std.url('/pagelist', tg_format='json')}"); d.addCallback(showPageList); }); });
addLoadEvent(function(){ connect('bad_example','onclick', function(e){signal('bad_example','showvalue',"explicit_value")}); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick',
function(e){signal(randomObj,'showvalue',"explicit_value")});
connect(randomObj,'showvalue',window,'alert');
});
Seems to me that "function(){" is a little more painful when nested.
I agree that the nested "function () { ... }" syntax gets messy (and this can be seen especially within complex event-driven scripts), but trying to define blocks as fundamentally different constructs is, in my opinion, only more confusing for the ES novice.
I am not inclined to support any of the alternate proposals so far; "(" because it is a radical departure from the meaning of '' in ES and is the first step on the path to Perl's magic $... madness, "File.open("file", (file))=>{ ... });" because it is terribly dense
and confusing syntax, and the Ruby-esque "(10).times( |number| { ... });" because it's yet another nonsensical (and unnecessary) idiom for the ES coder to learn and remember (though the least confusing of these three).
for (file in autoFile("hello", "r")) { process(file); }
This is a bit of a side note, but this type of usage of generators can be misleading to novice programmers, so I'm not sure if it's any better.
I completely agree -- this is confusing and not explicit in the least as to what it is actually doing.
I think it's also worth considering that the current (ES3) syntax allows the novice to mentally step through the process in a logical manner; e.g. "this is a lambda function that returns a reference to another lambda function which will inherit scope from the outer lambda and form a closure". Certainly in the use case of event-driven web scripts when preparing an event handler, it makes perfect sense that one would need to call a lambda to return a reference to a new lambda with a closure to be passed to the browser's event handling system. It is indirect and complex, but it is not obscure or magical.
I think the only sensible alternative if "typing seven characters" is a serious issue for the spec to handle is to offer a shortened function identifier that doesn't change terminology to "blocks" or the basic ES function syntax. Changing "function" to "fn" comes to mind. Of course this will be another trade-off with backwards compatibility: there will always be breakage of older scripts if a new identifier is introduced.
This would make one of the above examples:
(10).times( fn(number) { ... });
It's shorter, but I'm not convinced it's worth the negatives. You can bind "function(...) {...}" to a single key in any decent coding-centric text editor and mitigate the density of syntax with liberal use of newlines and indentation.
Thanks.
On Mar 16, 2007, at 8:57 PM, Robert Sayre wrote:
Seems to me that "function(){" is a little more painful when nested.
Great examples -- you've changed my mind about the ( syntax. I
would argue that the painfulness is a result of densely packed
alternating/nested curlies and parens and has little to do with the
function keyword.
On Mar 16, 2007, at 10:38 PM, Neil Mix wrote:
On Mar 16, 2007, at 8:57 PM, Robert Sayre wrote:
Seems to me that "function(){" is a little more painful when nested.
Great examples -- you've changed my mind about the ( syntax. I would argue that the painfulness is a result of densely packed alternating/nested curlies and parens and has little to do with the function keyword.
Test that claim: show those Mochikit examples rewritten to use
expression closures as proposed for ES4.
On Mar 17, 2007, at 12:44 AM, Brendan Eich wrote:
Test that claim: show those Mochikit examples rewritten to use expression closures as proposed for ES4.
Yes, of course. Here are Robert's examples using the ( syntax:
addLoadEvent(() { var elems = getElementsByTagAndClassName("A", "view-source"); var page = "rounded_corners/"; for (var i = 0; i < elems.length; i++) { var elem = elems[i]; var href = elem.href.split(///).pop(); elem.target = "_blank"; elem.href = "../view-source/view-source.html#" + page + href; } });
addLoadEvent(() { $("debug").innerHTML = repr(getElementPosition($("street"))); $("debug").innerHTML += "<br>"; $("debug").innerHTML += repr(quirksmode.findPos($("street"))); });
addLoadEvent((){ for(var _70 in DomDeco.registry){ for(var i=0;i<DomDeco.registry[_70].length;i++){ DomDeco.apply(_70,DomDeco.registry[_70][i]); } } });
addLoadEvent(() {
var d = wait(0.5, {data: [["getUsers", "getUsers"], ["foo",
"bar"]]});
d.addCallback(showSelectData);
d.addErrback(showError);
});
Nested anonymous functions:
addLoadEvent((){ connect('pagelist','onclick', (e) { e.preventDefault(); var d = loadJSONDoc("${std.url('/pagelist', tg_format='json')}"); d.addCallback(showPageList); }); });
addLoadEvent((){ connect('bad_example','onclick', (e){signal('bad_example','showvalue',"explicit_value")}); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick',
\(e){signal(randomObj,'showvalue',"explicit_value")});
connect(randomObj,'showvalue',window,'alert');
});
Now, to back up my assertion that the curlies and parens are
cluttering things up (and NOT to re-open my earlier proposal because
that door has already been shut, right?) here's the same code using
the block-following-call-becomes-lambda-arg syntax:
addLoadEvent() { var elems = getElementsByTagAndClassName("A", "view-source"); var page = "rounded_corners/"; for (var i = 0; i < elems.length; i++) { var elem = elems[i]; var href = elem.href.split(///).pop(); elem.target = "_blank"; elem.href = "../view-source/view-source.html#" + page + href; } }
addLoadEvent() { $("debug").innerHTML = repr(getElementPosition($("street"))); $("debug").innerHTML += "<br>"; $("debug").innerHTML += repr(quirksmode.findPos($("street"))); }
addLoadEvent() { for(var _70 in DomDeco.registry){ for(var i=0;i<DomDeco.registry[_70].length;i++){ DomDeco.apply(_70,DomDeco.registry[_70][i]); } } }
addLoadEvent() {
var d = wait(0.5, {data: [["getUsers", "getUsers"], ["foo",
"bar"]]});
d.addCallback(showSelectData);
d.addErrback(showError);
}
Nested anonymous functions:
addLoadEvent() { connect('pagelist','onclick') { e.preventDefault(); var d = loadJSONDoc("${std.url('/pagelist', tg_format='json')}"); d.addCallback(showPageList); } }
addLoadEvent() { connect('bad_example','onclick') { var [e] = arguments; signal('bad_example','showvalue',"explicit_value") } connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick') {
var [e] = arguments;
signal(randomObj,'showvalue',"explicit_value")
}
connect(randomObj,'showvalue',window,'alert');
}
I argue that both are confusing at first glance, but the latter is
more legible. (Again, not as a suggestion that it be adopted, but as
proof of my assertion that many dense curlies/parens are the
legibility problem rather than the function keyword.)
On 3/17/07, Neil Mix <nmix at pandora.com> wrote:
Again, not as a suggestion that it be adopted, but as proof of my assertion that many dense curlies/parens are the legibility problem rather than the function keyword.
Yes, I agree. Your examples actually drive me away from ''.
addLoadEvent((){
It looks like the sound of a skipping CD. :/
Neil Mix wrote:
addLoadEvent() { var elems = getElementsByTagAndClassName("A", "view-source"); var page = "rounded_corners/"; for (var i = 0; i < elems.length; i++) { var elem = elems[i]; var href = elem.href.split(///).pop(); elem.target = "_blank"; elem.href = "../view-source/view-source.html#" + page + href; } }
This syntax is whitespace-significant; the presence or absence of a line terminator between the closing parenthesis after the function name and the opening brace affects behavior. If a line terminator occurs, semicolon insertion transforms it into a method call of no arguments and a block statement. If a line terminator doesn't occur, your behavior works (in an ES4 implementation, that is). The existing no-line-terminator uses are annoying enough, particularly in the case when I want to return an object from a method, and I'd rather see as few new instances (which don't mimic the existing ones) as possible, personally.
I skimmed the thread too quickly to notice the note before the example I quoted in the last email saying it was dead syntax -- apologies for the extra email.
I skimmed the thread too quickly to notice the note before the example I quoted in the last email saying it was dead syntax -- apologies for the extra email.
Jeff Walden wrote:
Neil Mix wrote:
addLoadEvent() { var elems = getElementsByTagAndClassName("A", "view-source"); var page = "rounded_corners/"; for (var i = 0; i < elems.length; i++) { var elem = elems[i]; var href = elem.href.split(///).pop(); elem.target = "_blank"; elem.href = "../view-source/view-source.html#" + page + href; } }
This syntax is whitespace-significant; the presence or absence of a line terminator between the closing parenthesis after the function name and the opening brace affects behavior. If a line terminator occurs, semicolon insertion transforms it into a method call of no arguments and a block statement. If a line terminator doesn't occur, your behavior works (in an ES4 implementation, that is). The existing no-line-terminator uses are annoying enough, particularly in the case when I want to return an object from a method, and I'd rather see as few new instances (which don't mimic the existing ones) as possible, personally.
Jeff
Some musings...
We can avoid the auto-semicolon problem be requiring some sort of token right after the function call, e.g. "do" or "=>" or ":". Then the
brackets can go on the next line.
foo() do { ... }
foo() => // this isn't intuitive at all { ... }
However, only one lambda can be used with this syntax. To use more lambdas, we'd have to go back to the normal syntax for all lambdas except the last:
bar(function(...) ...) do { ... }
which IMO is a bit inelegant. Or maybe we could chain the blocks:
bar() do { } do // notice that this has to be on the same line as the closing "}" of the last block to avoid semicolon insertion { }
which still sucks and only works if there are no non-function arguments between the function arguments (e.g. how you would you convert bar(function() 1, "hi", function() 4) to the above chained block form?).
This goes for any other solution that resorts to putting the lambda body after the ")" in function call. While it does get rid of the closing ")" after the function arguments, it brings up way too many other problem IMO.
-Yuh-Ruey Chen
Let me see if I understand your proposal. I will try to make it concrete and semi-precise, but correct my misunderstandings:
-
add a production to the grammar allowing block statements to be used as expressions, roughly Expression ::= ... | Block
-
treat such a block <b> as syntactic sugar for function()<b>
If a block statement can be used as an expression, how do we disambiguate from object literals?
// empty object or empty block?
foo(1, 2, {}, 3, 4)
// block with stmt labelled "lab"? object with property named "lab"?
foo(1, 2, {lab: bar()}, 3, 4)
I'm sort of sympathetic to your idea as analogous to Smalltalk and Ruby blocks. But I'm not sure it's even workable syntactically. Also, the curly brace is very overloaded in this language, so there's a serious cost involved in overloading it yet again.
If, OTOH, you're only proposing such expressions be legal in more restricted syntactic contexts (you said something like "following a function call expression") then I think your case is even weaker. It doesn't make sense to have a syntactic sugar for function expressions that can only be used in a fraction of the contexts where the real syntax can be used. I suspect it'll just confuse matters and people will adopt a "don't use block expressions 'cause they don't always work and function expressions always do" policy.
I also don't like the asymmetry with lack of named arguments. This will prevent people from ever putting type annotations on these expressions.
On 17/03/07, Dave Herman <dherman at ccs.neu.edu> wrote:
- treat such a block <b> as syntactic sugar for function()<b> ... I'm sort of sympathetic to your idea as analogous to Smalltalk and Ruby blocks.
Note that Ruby's do-block is not a function but just a body of a loop and a roughly corresponds to for-in loop in JS executed over a generator.
, Igor
On 3/17/07, Igor Bukanov <igor at mir2.org> wrote:
Note that Ruby's do-block is not a function but just a body of a loop and a roughly corresponds to for-in loop in JS executed over a generator.
Note that Ruby's do-block is not a function but just a body of a loop and a roughly corresponds to for-in loop in JS executed over a generator.
Well, Ruby blocks are just anonymous functions. They are often used with loops, but that because they are good here. From Ruby language author www.artima.com/intv/closures.html
Yukihiro Matsumoto: Blocks are basically nameless functions. You may be familiar with the lambda from other languages like Lisp or Python. Basically, you can pass a nameless function to another function, and then that function can invoke the passed-in nameless function. For example, a function could perform iteration by passing one item at a time to the
nameless function.
This is a common style, called higher order function style, among
languages that
can handle functions as first class objects. Lisp does it. Python does it . Even C does it with function pointers.
ES has anonymous functions too and their semantics is the same as in Ruby. Only syntax is different. In Ruby do ... end is the same as {}. By convention, second is used for one-liners only. In while loops do can be omitted (just a sugar).
Another example of small DSL in Ruby with blocks from Rails
create_table :products do |t| t.column :title, :string t.column :description, :text t.column :image_url, :string
you can add your own code (not data) here, if needed
end
in ES it would be
createTable("products", function(t){ t.column("title", "string") t.column("description", "text") t.column("image_url", "string") // you can add your own code (not data) here, if needed })
I understand that this partial use-case is better to describe with plain data (JSON-like) but Rails gives you a possibility to define some table data with code too.
The same can be done about GUI or HTML forms, etc. So, just one more example - DSLs.
BTW, Igor, it seems that you are the only one who use the syntax exactly as I proposed. Just curios, is it because you know cyrillic (guessing from your name) and it looks to you like L or lambda letter?
, Vassily
Nice example of using some lib, that I do not know and can't see what code does. Let's take a particular function.
addLoadEvent(function() { var d = wait(0.5, {data: [["getUsers", "getUsers"], ["foo", "bar"]]}); d.addCallback(showSelectData); d.addErrback(showError); });
Question - "getUsers", "getUsers" - looks like calling 2 server methods. Are they executed sequantially or at in parallel, so second call will be after first is finished? Looks like at the same time, only code doesn't show this I would rather like to write such code directly to show my intent clearly.
Now about this addLoadEvent() { connect('bad_example','onclick') { var [e] = arguments; signal('bad_example','showvalue',"explicit_value") } connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick') {
var [e] = arguments;
signal(randomObj,'showvalue',"explicit_value")
}
connect(randomObj,'showvalue',window,'alert');
}
It it absolutely untyped and use strings for objects and methods. Also it is uses some signal-slot framework that is not needed in ES at all. Direct and typed way will look like this
addLoadEvent((){ var badExample = $("bad_example"). badExample.onClick = (\ e:Event){ badExample.showValue("explicit_value"); }
bad_example.showvalue = (\ value:String) { window.alert("badValue:"
-
value) };
good_example.onclick = (\ e:Event){ randomObj.showvalue(explicit_value); }
randomObj.showvalue = (\ value){ window.alert("goodValue:" + value) }; })
So, we typed the example and removed the need for framework. A bonus - showvalue for random obj and bad_example are now showing DIFFERENT values, as it will happen often in real code.
Such examples, BTW is clear indication that developers would rather use some framwork and pass their functions as strings then go with right and typed way.
Vassily Gavrilyak
Let me see if I understand your proposal.
Bleh. I should have known better.
Let me re-iterate: I'm not proposing that syntax. I was (poorly)
illustrating my assertion about the true cause of illegibility in the
use of anonymous functions as parameters.
If someone smarter than me can work up a non-alien syntax for
anonymous functions that reduces the number of parens and curlies,
more power to them. As for me, I'm now regretting that I've confused
this thread so thoroughly, and am distancing myself from the idea
altogether.
Back to the original topic, and speaking of reducing the number of
parens and curlies, one thing I forgot to do in my rewrite of
Robert's mochikit examples was to make use of the new expression
closure capabilities in es4. With those, we can change one of the
more "painful" examples from this:
addLoadEvent(function(){ connect('bad_example','onclick', function(e){signal ('bad_example','showvalue',"explicit_value")}); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick',
function(e){signal(randomObj,'showvalue',"explicit_value")});
connect(randomObj,'showvalue',window,'alert');
});
to this:
addLoadEvent(function(){ connect('bad_example','onclick', function(e) signal ('bad_example','showvalue',"explicit_value")); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick',
function(e) signal(randomObj,'showvalue',"explicit_value"));
connect(randomObj,'showvalue',window,'alert');
});
Now, adding in the ( lambda syntax under consideration, we get this:
addLoadEvent(function(){ connect('bad_example','onclick', (e) signal('bad_example','showvalue',"explicit_value")); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick',
\(e) signal(randomObj,'showvalue',"explicit_value"));
connect(randomObj,'showvalue',window,'alert');
});
I believe there's a correlation between the size of the lambda and
the improved legibility with this shortcut -- the smaller the lambda,
the more helpful the shortcut is. For example, change something like
this:
addLoadEvent(function(){ connect('bad_example','onclick', function(e){signal('bad_example)}); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick',
function(e){signal(randomObj)});
connect(randomObj,'showvalue',window,'alert');
});
to this:
addLoadEvent(function(){ connect('bad_example','onclick', (e) signal('bad_example')); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick', \(e) signal(randomObj));
connect(randomObj,'showvalue',window,'alert');
});
I defer to others for thoughts and opinions.
addLoadEvent(function(){ connect('bad_example','onclick', (e) signal('bad_example')); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick', \(e) signal(randomObj)); connect(randomObj,'showvalue',window,'alert');
});
Actually it should be this, with operator, functions expressions, types and lambda
addLoadEvent(function(){ bad_example.onclick += (\ _) bad_example.showvalue("explicit_value"); bad_example.showvalue += (\ s:String) window.alert(s); good_example.onclick += (\ _) randomObj.showvalue("explicit_value"); randomObj.showvalue += (\ s:String) window.alert(s); });
Now it's cleared from all the syntactic noise. Signal-slot framework in ES is something strange that should not be here at all. ES object properties are slots and functions calls are signals. Connection should be an assignment. ES is not C++, there is no needs for workarounds Thats the first thing bad with this example. Second thing is that is uses method names in order to actually shorten up code (avoid writing 'function' directly). With operators, expression closures and lambdas there will be no place left for such framework. Everything would be possible to write directly and use typing and compilation niceties.
Vassily.
On 3/17/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
addLoadEvent(function(){ connect('bad_example','onclick', (e) signal('bad_example')); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick', \(e) signal(randomObj)); connect(randomObj,'showvalue',window,'alert');
});
Actually it should be this, with operator, functions expressions, types and lambda
addLoadEvent(function(){ bad_example.onclick += (\ _) bad_example.showvalue("explicit_value"); bad_example.showvalue += (\ s:String) window.alert(s); good_example.onclick += (\ _) randomObj.showvalue("explicit_value"); randomObj.showvalue += (\ s:String) window.alert(s); });
Now it's cleared from all the syntactic noise. Signal-slot framework in ES is something strange that should not be here at all. ES object properties are slots and functions calls are signals. Connection should be an assignment. ES is not C++, there is no needs for workarounds Thats the first thing bad with this example. Second thing is that is uses method names in order to actually shorten up code (avoid writing 'function' directly). With operators, expression closures and lambdas there will be no place left for such framework. Everything would be possible to write directly and use typing and compilation niceties.
The signal/slot implementation in MochiKit is not at all superfluous. More than one callback can be connected to a slot, and when connected to DOM objects it presents a uniform event object regardless of the underlying implementation.
No needs for workarounds? Are you kidding? We are talking about web browsers.
On 3/17/07, Bob Ippolito <bob at redivi.com> wrote:
On 3/17/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
addLoadEvent(function(){ connect('bad_example','onclick', (e) signal('bad_example')); connect('bad_example','showvalue',window,'alert');
connect('good_example','onclick', \(e) signal(randomObj)); connect(randomObj,'showvalue',window,'alert');
});
Actually it should be this, with operator, functions expressions, types and lambda
addLoadEvent(function(){ bad_example.onclick += (\ _) bad_example.showvalue("explicit_value"); bad_example.showvalue += (\ s:String) window.alert(s); good_example.onclick += (\ _) randomObj.showvalue("explicit_value"); randomObj.showvalue += (\ s:String) window.alert(s); });
Now it's cleared from all the syntactic noise. Signal-slot framework in ES is something strange that should not be here at all. ES object properties are slots and functions calls are signals. Connection should be an assignment. ES is not C++, there is no needs for workarounds Thats the first thing bad with this example. Second thing is that is uses method names in order to actually shorten up code (avoid writing 'function' directly). With operators, expression closures and lambdas there will be no place left for such framework. Everything would be possible to write directly and use typing and compilation niceties.
The signal/slot implementation in MochiKit is not at all superfluous. More than one callback can be connected to a slot, and when connected to DOM objects it presents a uniform event object regardless of the underlying implementation.
No needs for workarounds? Are you kidding? We are talking about web browsers.
Additionally the method names are used because it's shorthand for:
function (object, method) { return function () { return object[method].apply(object, arguments); }; };
Really...
On 3/17/07, Bob Ippolito <bob at redivi.com> wrote:
The signal/slot implementation in MochiKit is not at all superfluous. More than one callback can be connected to a slot, and when connected to DOM objects it presents a uniform event object regardless of the underlying implementation.
No needs for workarounds? Are you kidding? We are talking about web browsers.
I am not criticizing Mochikit, I like it. It covers many workarounds for browsers. What I am telling that it shouldn't be here, such framework is possible to have in language directly and C# proves this. About more then one callback - that's exactly why I used += and not just =. Again, that is already solved in C#. And it should be as much simpler in ES, otherwise people will just move to C# and use compilers to JS.
Additionally the method names are used because it's shorthand for:
function (object, method) { return function () { return object[method].apply(object, arguments); }; };
Really...
That's I understand too, and used same approach in my code 2 years ago. Then I moved to functions, because methods are not that really needed. When I need to pass method to callback expecting function I do just setCallback(obj.method.delegate(obj)). This way only one argument (function) is needed in API, not two (object, method). Having 2 arguments complicates API, and built-in callbacks still provides one argument only (like setTimeout or Array.sort), so for me looks better to stay coherent with such callbacks.
Also there is no strings usage here, everything looks like it is "typed".
And direct call is still the best, especially with typed ES4.
-bob
, Vassily
On 3/17/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
On 3/17/07, Bob Ippolito <bob at redivi.com> wrote:
The signal/slot implementation in MochiKit is not at all superfluous. More than one callback can be connected to a slot, and when connected to DOM objects it presents a uniform event object regardless of the underlying implementation.
No needs for workarounds? Are you kidding? We are talking about web browsers. I am not criticizing Mochikit, I like it. It covers many workarounds for browsers. What I am telling that it shouldn't be here, such framework is possible to have in language directly and C# proves this. About more then one callback - that's exactly why I used += and not just =. Again, that is already solved in C#. And it should be as much simpler in ES, otherwise people will just move to C# and use compilers to JS.
Additionally the method names are used because it's shorthand for:
function (object, method) { return function () { return object[method].apply(object, arguments); }; };
Really...
That's I understand too, and used same approach in my code 2 years ago. Then I moved to functions, because methods are not that really needed. When I need to pass method to callback expecting function I do just setCallback(obj.method.delegate(obj)). This way only one argument (function) is needed in API, not two (object, method). Having 2 arguments complicates API, and built-in callbacks still provides one argument only (like setTimeout or Array.sort), so for me looks better to stay coherent with such callbacks.
Also there is no strings usage here, everything looks like it is "typed".
And direct call is still the best, especially with typed ES4.
The API in question supports either one argument or two, actually. We ended up supporting both because of syntax economy.
Even if MochiKit did add delegate to Function.prototype you still have to repeat the object twice. That's no fun. MochiKit.Base.bind(fn, object) is the same as your Function.prototype.delegate, but people prefer to use MochiKit.Base.method(obj, methodName) because they don't have to type as much.
The MochiKit.Signal.connect call in this case allows one or two arguments to specify the callback directly or object+method as an implicit call to method(obj, methodName).
The API in question supports either one argument or two, actually. We ended up supporting both because of syntax economy. ......... but people prefer to use MochiKit.Base.method(obj, methodName)
because they don't
have to type as much.
That's exactly what we are discussing here, just a syntax sugar for less typing and reading. In this particular case - (obj,method) - people will use it even in typed language (ES4). I saw also people providing sortBy(nameOfProperty) method, untyped, just to save keystrokes in C# and write people.SortBy("name", "desc"); instead of right way people.Sort(delegate(Person a, Person b){ return a.name > b.name ;})
The same method is present in ActionScript actually. Now this is easy to fix and C# 3 fixed, providing inference and anonymouse functions shortcuts
Now people can write shortly and typed people.OrderBy( it => it.name)
ES4 is almost as good, but still longer people.orderBy(function(it) it.name) So people would still use shorter untyped version.
Vassily
On 17/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote: ...
Now this is easy to fix and C# 3 fixed, providing inference and anonymouse functions shortcuts
Now people can write shortly and typed people.OrderBy( it => it.name) ES4 is almost as good, but still longer people.orderBy(function(it) it.name) So people would still use shorter untyped version.
So why not to propose C# syntax? IMO array.filter(elem => elem > 0)
looks better than array.filter((\elem) elem > 0)
, Igor
On 3/18/07, Igor Bukanov <igor at mir2.org> wrote:
On 17/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote: ...
Now this is easy to fix and C# 3 fixed, providing inference and anonymouse functions shortcuts
Now people can write shortly and typed people.OrderBy( it => it.name) ES4 is almost as good, but still longer people.orderBy(function(it) it.name) So people would still use shorter untyped version.
So why not to propose C# syntax? IMO array.filter(elem => elem > 0) looks better than array.filter((\elem) elem > 0)
, Igor
---------- Forwarded message ---------- From: Vassily Gavrilyak <gavrilyak at gmail.com>
Date: Mar 18, 2007 12:20 AM Subject: Re: Expression closures - use-cases for shortcut lambda syntax (blocks) To: Igor Bukanov <igor at mir2.org>
I already proposed it.
File.open("file", (file))=>{ //process }); May be C# way will be good, and C# is ECMA too. It has the same
superfluous () as ES3, >>but looks good.
More examples from C# spec x => x + 1 //
Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, statement body (int x) => x + 1 // Explicitly
typed, expression body (int x) => { return x + 1; } // Explicitly typed, statement body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters
, Vassily
Vassily Gavrilyak wrote:
I already proposed it.
File.open("file", (file))=>{ //process }); May be C# way will be good, and C# is ECMA too. It has the same superfluous () as ES3, >>but looks good.
More examples from C# spec x => x + 1 // Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, statement body (int x) => x + 1 // Explicitly typed, expression body (int x) => { return x + 1; } // Explicitly typed, statement body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters
I like it. As with any other syntax addition, I'd like the syntax to be as universal as possible so as to make it intuitive. Since it's equivalent to any other lambda syntax, it can be used within object literals as well. Using all 3 lambda syntaxes:
obj = { x: 0, add: y => this.x + y, increment: y => void (this.x += y) };
obj = { x: 0, add: function(y) { return this.x + y; }, increment: function(y) { this.x += y; } };
obj = { x: 0, add: function(y) this.x + y, increment: function(y) void (this.x += y) };
For non-void functions, the a => b syntax looks really nice. Since it is
equivalent to function(a) b, I expect it to the have the same operator precedence (higher than comma I think).
However, it can't be used for class methods, which I think is fine. But if you really want the syntax to be as universal as possible, I can imagine something like this:
class Klass { foo(x) => print(x) }
or if parenthesis aren't required:
class Klass { foo x => print(x) }
But that looks a bit to alien to me, since it's asymmetric with the call syntax: we do o.foo(x), not o.foo x.
-Yuh-Ruey Chen
On 3/18/07, Yuh-Ruey Chen <maian330 at gmail.com> wrote:
Vassily Gavrilyak wrote:
I already proposed it.
File.open("file", (file))=>{ //process }); May be C# way will be good, and C# is ECMA too. It has the same superfluous () as ES3, >>but looks good.
More examples from C# spec x => x + 1 // Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, statement body (int x) => x + 1 // Explicitly typed, expression body (int x) => { return x + 1; } // Explicitly typed, statement body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters
I like it. As with any other syntax addition, I'd like the syntax to be as universal as possible so as to make it intuitive. Since it's equivalent to any other lambda syntax, it can be used within object literals as well. Using all 3 lambda syntaxes:
obj = { x: 0, add: y => this.x + y, increment: y => void (this.x += y) };
obj = { x: 0, add: function(y) { return this.x + y; }, increment: function(y) { this.x += y; } };
obj = { x: 0, add: function(y) this.x + y, increment: function(y) void (this.x += y) };
For non-void functions, the a => b syntax looks really nice. Since it is equivalent to function(a) b, I expect it to the have the same operator precedence (higher than comma I think).
However, it can't be used for class methods, which I think is fine. But if you really want the syntax to be as universal as possible, I can imagine something like this:
class Klass { foo(x) => print(x) }
or if parenthesis aren't required:
class Klass { foo x => print(x) }
But that looks a bit to alien to me, since it's asymmetric with the call syntax: we do o.foo(x), not o.foo x.
-Yuh-Ruey Chen
Never thought about using shortcut syntax for named functions, but it's good here too. Parenthesis can be made mandatory, not a big deal, however we tried to avoid some parenthesis earlier in this list. They are optional only with one argument in C#, but that's the most common case. This syntax seed to work nice with getters and setters too. The only place where it doesn't work is type declaration. So something like this function map(mapper: (function (:, _:uint, _:Object):), thisObj:Object):Array will become this function map(mapper: (:*, _:uint, _:Object) => *, thisObj:Object):Array
, Vassily
Correction :-)
The only place where it doesn't work is type declaration.
should be read as: It also works nicely with type declarations
So something like this function map(mapper: (function (:, _:uint, _:Object):), thisObj:Object):Array will become this function map(mapper: (:*, _:uint, _:Object) => *, thisObj:Object):Array
, Vassily
On 3/17/07, Brendan Eich <brendan at mozilla.org> wrote:
On Mar 16, 2007, at 10:38 PM, Neil Mix wrote:
On Mar 16, 2007, at 8:57 PM, Robert Sayre wrote:
Seems to me that "function(){" is a little more painful when nested.
Great examples -- you've changed my mind about the ( syntax. I would argue that the painfulness is a result of densely packed alternating/nested curlies and parens and has little to do with the function keyword.
Test that claim: show those Mochikit examples rewritten to use expression closures as proposed for ES4.
/be
Here the examples rewritten uses functions and => syntax
// forEach used instead of for addLoadEvent(() => { var page = "rounded_corners/"; var elems = getElementsByTagAndClassName("A", "view-source"); elems.forEach(elem => { var href = elem.href.split(///).pop(); elem.target = "_blank"; elem.href = "../view-source/view-source.html#" + page + href; }) });
//usage of 2 different for's isn't very nice here //there should be some way to do it with comrehensions //, but forEach will do it too addLoadEvent(() => { DomDeco.registry.forEach((registry,key) => { registry.forEach(value => DomDeco.apply(key, it)) }) }
//here add*Callback API is not neccessary, CPS will do it nicely too. //bonus - no framework knowledge needed, just a convention addLoadEvent(() => { wait(0.5, {data: [["getUsers", "getUsers"], ["foo", "bar"]]}, res=>showSelectData(res), err=>showError(res)); })
//CPS again, example is actually not complete, //showPageList is already refactored out, that is not always so in real code addLoadEvent(()=>{ pagelist.onclick = e => { e.preventDefault(); loadJSONDoc("${std.url('/pagelist', tg_format='json')}", list=>showPageList(list)); } })
//here the whole slot-signal framework (untyped) //can be replaced with redable and typed version addLoadEvent(()=>{ bad_example.onclick = _:Event => bad_example.showvalue("explicit_value"); bad_example.showvalue = s:String => window.alert(s); good_example.onclick = _:Event => randomObj.showvalue("explicit_value"); randomObj.showvalue = s:String => window.alert(s); });
Regard Vassily Gavrilyak
We've been writing int -> int when discussing the type system,
because it's too painful (especially on a whiteboard) to write
function(int):int.
When Dave suggested function (formals) expression, Jeff Dyer wanted
something like => after the (formals) -- he suggested = at the time.
I think this clinches it. We have been circling around the design
point of (formals) => assign-expr (you're right about not wanting
comma to bind more tightly than =>) for a while. Using -> when C#
uses => would just be gratuitous.
I would require parentheses around the formals, even when there is
only one.
In a class, I'm not so keen on name(formals) => expression defining a
method, given the lack of a binding form elsewhere.
On 3/18/07, Brendan Eich <brendan at mozilla.org> wrote:
We've been writing int -> int when discussing the type system, because it's too painful (especially on a whiteboard) to write function(int):int.
Yes, I agree. I have the same issue when reading the specs. There is also one more issue. When ES4 will get IDE's with tooltips for methods declarations, it would be good to have nice way to show those tooltips.
When Dave suggested function (formals) expression, Jeff Dyer wanted something like => after the (formals) -- he suggested = at the time.
I think this clinches it. We have been circling around the design point of (formals) => assign-expr (you're right about not wanting comma to bind more tightly than =>) for a while. Using -> when C# uses => would just be gratuitous.
I would require parentheses around the formals, even when there is only one.
Probably I would too. That will be confusion for C# people. Not a big issue.
In a class, I'm not so keen on name(formals) => expression defining a method, given the lack of a binding form elsewhere.
Me too, better to write function name(){ } because it's exactly FUNCTION declaration. Shortcuts can be disallowed here at all. By language or by company policy.
So, does such syntax have a chance to be in ES?
On Mar 18, 2007, at 9:30 AM, Vassily Gavrilyak wrote:
So, does such syntax have a chance to be in ES?
I think so.
We've been writing int -> int when discussing the type system, because it's too painful (especially on a whiteboard) to write function(int):int.
When Dave suggested function (formals) expression, Jeff Dyer wanted something like => after the (formals) -- he suggested = at the time.
I think this clinches it. We have been circling around the design point of (formals) => assign-expr (you're right about not wanting comma to bind more tightly than =>) for a while. Using -> when C# uses => would just be gratuitous.
I would require parentheses around the formals, even when there is only one.
In a class, I'm not so keen on name(formals) => expression defining a method, given the lack of a binding form elsewhere.
I like it, and agree with the details as captured here. I could go without the parens around a single formal, but I've been writing a lot of ML code lately :) Maybe requiring parens is better; why have two ways to write something when one will do! Also, for now let's not extend this syntax to binding forms, inside or outside of a class. I have concerns about readability.
I'll update the parser in the next day or so to see if we've overlooked any issues. I don't expect we have.
So, does such syntax have a chance to be in ES?
I think so.
Me too.
Thanks to Vassily and others on es4-discuss for picking up the ball when we drop it.
On 3/18/07, Jeff Dyer <jodyer at adobe.com> wrote:
We've been writing int -> int when discussing the type system, because it's too painful (especially on a whiteboard) to write function(int):int.
When Dave suggested function (formals) expression, Jeff Dyer wanted something like => after the (formals) -- he suggested = at the time.
I think this clinches it. We have been circling around the design point of (formals) => assign-expr (you're right about not wanting comma to bind more tightly than =>) for a while. Using -> when C# uses => would just be gratuitous.
I would require parentheses around the formals, even when there is only one.
In a class, I'm not so keen on name(formals) => expression defining a method, given the lack of a binding form elsewhere.
I like it, and agree with the details as captured here. I could go without the parens around a single formal, but I've been writing a lot of ML code lately :)
Now you can rewrite this code directly in ES, it will look better ;-)
Maybe requiring parens is better; why have two ways to write something when one will do!
Maybe, maybe not, no clear winner here for me. Omitted parens looks better, but special case is worse.
Also, for now let's not extend this syntax to binding forms, inside or outside of a class. I have concerns about readability.
It seems everybody agreed on this.Probably getters-setters will look better with shortcut, not sure.
I'll update the parser in the next day or so to see if we've overlooked any issues. I don't expect we have.
So, does such syntax have a chance to be in ES?
I think so.
Me too.
Thanks to Vassily and others on es4-discuss for picking up the ball when we drop it.
Jd
My big thanks to you Jeff and everybody participated in discussion.
Vassily Gavrilyak
I like it, and agree with the details as captured here. I could go
I agree.
without the parens around a single formal, but I've been writing a lot of ML code lately :) Maybe requiring parens is better; why have two ways to write something when one will do! Also, for now let's not extend this syntax to binding forms, inside or outside of a class. I have concerns about readability.
In your opinion, should we allow both
function(formals) expression
(formals) => expression
with the former allowed as both a declaration and expression form and the latter allowed as only an expression form? Or do we only allow
(formals) => expression
and only as an expression form?
If we choose the latter, then there's no way to write function declarations with expression bodies, which seems like an unnecessary restriction.
From: Dave Herman [mailto:dherman at ccs.neu.edu]
without the parens around a single formal, but I've been writing a
lot
of ML code lately :) Maybe requiring parens is better; why have two
ways
to write something when one will do! Also, for now let's not extend
this
syntax to binding forms, inside or outside of a class. I have
concerns
about readability.
In your opinion, should we allow both
function(formals) expression (formals) => expression
with the former allowed as both a declaration and expression form and the latter allowed as only an expression form? Or do we only allow
(formals) => expression
and only as an expression form?
If we choose the latter, then there's no way to write function declarations with expression bodies, which seems like an unnecessary restriction.
This is a trick question! We already have function expressions with a 'function' head and a '{...}' body. Dropping the 'return' and braces as a convenience.
The cool new form being proposed here doesn't replace these functions with statement bodies, but does replace their expression bodied counterparts, except when they are used as declarations.
So, we either:
- drop the convenience altogether (sorry Brendan & Lars)
- keep the convenience in declaration contexts only
- keep the convenience in both contexts and have more than two ways to write function expressions
I'm less of a fan of function declarations with expression bodies than some other TG1 members, so I could live with the first option.
But given that we want to keep this convenience for function declarations, I actually think that the third option is the best one. It avoids the usability issue of special casing. I also think that some users will prefer the keyword form of function expressions over the punctuated form.
I say we do it all.
On 2007-03-19, at 15:01 EDT, Jeff Dyer wrote:
- drop the convenience altogether (sorry Brendan & Lars)
- keep the convenience in declaration contexts only
- keep the convenience in both contexts and have more than two ways to write function expressions
- make 'function' required for declarations, optional for
expressions, require '=>' for both
('function' identifier?)? '(' formals? ')' '=>' expression
This is a trick question! We already have function expressions with a 'function' head and a '{...}' body. Dropping the 'return' and braces as a convenience.
Not a trick question -- of course we already have functions that are expressions; the question is whether all the syntactic forms of functions (i.e., both function declarations and function expressions) have a variant where the body is an expression.
It's precisely this convenience I'm talking about. In fact, when writing in a functional style, this convenience adds up pretty quickly. Try writing a curried function with "return" and curlies -- it's painful, especially because of the sneaky requirement that return can't be followed by a newline!
function f(x) {
return function (y) {
return function (z) {
return x + y + z;
}
}
}
vs.
f(x) => (y) => (z) => (x + y + z)
or
function f(x) (y) => (z) => (x + y + z)
I didn't quite follow your summary. Indulge my OCD and let me be painfully explicit. :) Here are all the options I think we're talking about, individually labelled (a) through (f). (Ignoring some irrelevant details like named function expressions.)
function-expression
::= "function" id? "(" formals ")" block (a)
| "function" id? "(" formals ")" expression (b)
| "function" id? "(" formals ")" "=>" expression (c)
| "(" formals ")" "=>" expression (d)
function-declaration
::= "function" id "(" formals ")" block (e)
| "function" id "(" formals" ")" expression (f)
| "function" id "(" formals" ")" "=>" expression (g)
| id "(" formals ")" "=>" expression (h)
expression ::= ... | function-expression
declaration ::= ... | function-declaration
I'm in favor of allowing (a), (c), (d), (e), and (g). I think that was Tucker's proposal just now.
- make 'function' required for declarations, optional for expressions, require '=>' for both
('function' identifier?)? '(' formals? ')' '=>' expression
The '=>' is unnecessary and breaks symmetry with function expressions
with statement bodies. So I don't like it.
On Mar 19, 2007, at 1:43 PM, Dave Herman wrote:
function-expression ::= "function" id? "(" formals ")" block (a) | "function" id? "(" formals ")" expression (b) | "function" id? "(" formals ")" "=>" expression (c) | "(" formals ")" "=>" expression (d) function-declaration ::= "function" id "(" formals ")" block (e) | "function" id "(" formals" ")" expression (f) | "function" id "(" formals" ")" "=>" expression (g) | id "(" formals ")" "=>" expression (h) expression ::= ... | function-expression declaration ::= ... | function-declaration
I'm in favor of allowing (a), (c), (d), (e), and (g).
+1
This is a trick question! We already have function expressions with
a
'function' head and a '{...}' body. Dropping the 'return' and braces
as
a convenience.
Not a trick question -- of course we already have functions that are expressions; the question is whether all the syntactic forms of functions (i.e., both function declarations and function expressions) have a variant where the body is an expression.
Okay a tricky question then :)
It's precisely this convenience I'm talking about. In fact, when
writing
in a functional style, this convenience adds up pretty quickly. Try writing a curried function with "return" and curlies -- it's painful, especially because of the sneaky requirement that return can't be followed by a newline!
function f(x) { return function (y) { return function (z) { return x + y + z; } } }
vs.
f(x) => (y) => (z) => (x + y + z)
or
function f(x) (y) => (z) => (x + y + z)
I didn't quite follow your summary. Indulge my OCD and let me be painfully explicit. :) Here are all the options I think we're talking about, individually labelled (a) through (f). (Ignoring some
irrelevant
details like named function expressions.)
function-expression ::= "function" id? "(" formals ")" block (a) | "function" id? "(" formals ")" expression (b) | "function" id? "(" formals ")" "=>" expression (c) | "(" formals ")" "=>" expression (d) function-declaration ::= "function" id "(" formals ")" block (e) | "function" id "(" formals" ")" expression (f) | "function" id "(" formals" ")" "=>" expression (g) | id "(" formals ")" "=>" expression (h) expression ::= ... | function-expression declaration ::= ... | function-declaration
I'm in favor of allowing (a), (c), (d), (e), and (g). I think that was Tucker's proposal just now.
I'm in favor of (a), (b), (d), (e), (f). As I've already said, (c) and (g) are not as nice as (b) and (f) because the latter erase characters from the current function expression form, while the former adds "=>"
To be consistent with the current expression form
function id (x) { return x }
should be shortened to
function id(x) x
not
function id(x) => x
On 2007-03-19, at 16:43 EDT, Dave Herman wrote:
I'm in favor of allowing (a), (c), (d), (e), and (g). I think that was Tucker's proposal just now.
Yes. +1
On Mar 19, 2007, at 1:59 PM, Jeff Dyer wrote:
To be consistent with the current expression form
function id (x) { return x }
should be shortened to
function id(x) x
not
function id(x) => x
Your argument is not consistency, but brevity: "because the latter
erase characters from the current function expression form, while the
former adds '=>'". A different consistency argument, with the new
lambda syntax, favors =>.
How to decide between conflicting consistencies and brevity? One role
of syntax is to catch the reader's eye and make plain what may be
hard to see or visually acquire quickly. In this light, => is good
and we would not do without it, or shorten it, for the new lambda
syntax.
Lars and I have written a fair amount (Lars did most ;-) of the
builtins/*.es self-hosted standard library (ECMA-262 Edition 3
Chapter 15) may agree (I hope) that just
function (formals) expression
has been less readable than I think they should be. Stylistically, we
have always written
function (formals) expression;
in classes. Note the semicolon, which was not originally required by
the grammar. We've been loath to put both head and body on one line
because the closing formals) may be hard to see as distinct from the
expression. Anyway, I'd like to hear Lars's preference, but I don't
think consistency favors one outcome, and brevity can't always win
(we're talking JS here, not C/C++/Java/C#).
Your argument is not consistency, but brevity: "because the latter erase characters from the current function expression form, while the former adds '=>'". A different consistency argument, with the new lambda syntax, favors =>
I'm arguing for consistence too. The keyword forms of function are visually more closely related than the two expression forms. Let statements and expressions support this relationship. What we do for function expressions we should do for let expressions.
let (x=y) x
become
let (x=y) => x
But => has limited appeal to me. I'd like to use it only as we agreed
yesterday, in function expressions without a 'function' head. In all other contexts it just adds clutter.
On 2007-03-19, at 17:35 EDT, Jeff Dyer wrote:
let (x=y) x
become
let (x=y) => x
I thought it became:
(x) => x (y)
:P
On 3/19/07, P T Withington <ptw at pobox.com> wrote:
On 2007-03-19, at 17:35 EDT, Jeff Dyer wrote:
let (x=y) x
become
let (x=y) => x
I thought it became:
(x) => x (y)
...which brings up the issue of associativity. When I first looked at your example, I thought x was being applied to y, rather than (x) => x
being applied to y.
On Mar 19, 2007, at 2:35 PM, Jeff Dyer wrote:
Your argument is not consistency, but brevity: "because the latter erase characters from the current function expression form, while the former adds '=>'". A different consistency argument, with the new lambda syntax, favors =>
I'm arguing for consistence too. The keyword forms of function are visually more closely related than the two expression forms. Let statements and expressions support this relationship.
Oh, right -- sorry I didn't remember the let expression form's
influence here.
What we do for function expressions we should do for let expressions.
That's one way to be consistent with another form, yeah. It's not the
only way, in light of the new => based expression closure syntax.
let (x=y) x
become
let (x=y) => x
But => has limited appeal to me. I'd like to use it only as we agreed yesterday, in function expressions without a 'function' head. In all other contexts it just adds clutter.
I'm not so sure, having been through an implementation cycle (JS1.7
in Firefox 2) of let blocks and expressions. Users have not cried out
for => or = clutter, but there has been some confusion about lack of
braces meaning let expression.
It's true you did not feel the urge to use = between let head and
expression body, as you did when Dave proposed expression closures
via the 'function (formals) expr' syntax. But the same consistency
argument you make now might have applied to that desire for 'function
(formal) = expr'.
There's no hard or fast rule here. Aesthetics matter, but usability
should trump if in conflict. Alas we don't have much usability data.
I'll try to get some based on JS1.7.
On Mar 19, 2007, at 2:55 PM, Jon Zeppieri wrote:
On 3/19/07, P T Withington <ptw at pobox.com> wrote:
On 2007-03-19, at 17:35 EDT, Jeff Dyer wrote:
let (x=y) x
become
let (x=y) => x
I thought it became:
(x) => x (y)
...which brings up the issue of associativity. When I first looked at your example, I thought x was being applied to y, rather than (x) => x being applied to y.
Don't be tricked, Tucker was joking (note the :-P you deleted when
citing his post). He's an old Lisp and Dylan hacker.
On Mar 19, 2007, at 2:55 PM, Jon Zeppieri wrote:
On 3/19/07, P T Withington <ptw at pobox.com> wrote:
On 2007-03-19, at 17:35 EDT, Jeff Dyer wrote:
let (x=y) x
become
let (x=y) => x
I thought it became:
(x) => x (y)
...which brings up the issue of associativity. When I first looked at your example, I thought x was being applied to y, rather than (x) => x being applied to y.
Forgot to answer the associativity question. With ES3 function
expressions, which are primary expressions, you need not parenthesize
the function expression to call it. Just append (actuals) to invoke:
js> 1 + function(){return 2}()
3
Note the 1 + or some other left-expression-context is necessary.
Oherwise, with just:
js> function(){return 2}
function () { return 2; }
you have written a useless unnamed function expression statement that
ends at the } that closes the body, and any () after will provoke a
syntax error:
js> function(){return 2}()
typein:4: SyntaxError: syntax error: typein:4: function(){return 2}() typein:4: .....................^
This is due to
12.4 Expression Statement Syntax ExpressionStatement : [lookahead ∉ {{,function}] Expression ;
in ECMA-262 Edition 3.
Without the "clutter" => connecting the (formals) and assign-expr
body, we are free to match the ES3 behavior and not require
parentheses around the function expression in order to apply it.
So => could be considered an operator with high precedence, but then
almost all expression bodies would have to be parenthesized. If it is
low precedence, then you will need to parenthesize ((x) => x)(y). I
think Yuh-Ruey Chen already proposed that => be low precedence, but
higher than the comma operator, in an earlier post.
This seems to me another advantage of => -- that it facilitates
operator precedence parsing of expression closures.
There's no hard or fast rule here. Aesthetics matter, but usability should trump if in conflict. Alas we don't have much usability data. I'll try to get some based on JS1.7.
Great! And my sense of aesthetics might change in the mean time :)
While we are talking about function expressions. I assume that type parameters are allowed. For example,
var id = .<t>(x:t):t=>x
should work. It falls out naturally as the syntax on the left of => is a
FunctionSignature. Can't remember, maybe we already talked about this.
On Mar 19, 2007, at 3:36 PM, Brendan Eich wrote:
you have written a useless unnamed function expression statement
that ends at the } that closes the body, and any () after will
provoke a syntax error:js> function(){return 2}() typein:4: SyntaxError: syntax error: typein:4: function(){return 2}() typein:4: .....................^
This is due to
12.4 Expression Statement Syntax ExpressionStatement : [lookahead ∉ {{,function}] Expression ;
in ECMA-262 Edition 3.
But of course, the chapter and verse I cite, in conjunction with the
rest of the grammar, makes even
js> function () {}
a SyntaxError. But it's not in SpiderMonkey, due to an old
compatibility promise (see bugzilla.mozilla.org/show_bug.cgi?
id=303723). Probably it could be made a SyntaxError with more work
under the SpiderMonkey API.
Just wanted to avoid sowing more confusion. ES3's use of function name
(formals) block in several contexts, including in expressions where
name is optional, may be the root confusion causer here. If back in
1998 we had thought of the => alterative syntax for function
expressions (unnamed ones -- named ones could use function name (formals) => assign-expr), we should have introduced that form then.
It would have saved us the current trouble ;-).
On 17/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
File.open("file.txt") do | file file.process() end
Should be: File.open("file.txt") do |file| ... end
On Sat, 17 Mar 2007 02:37:13 +0100, Igor Bukanov <igor at mir2.org> wrote:
Ruby's do construction is also a loop, not a lambda
Not true at all; it's every bit a lambda and not at all a loop. The above
is the same as:
file_func = lambda {file.process()}
File.open("file.txt", &file_func)
Not really all that relevant, but just couldn't let it pass. :)
Chris
On 3/19/07, Brendan Eich <brendan at mozilla.org> wrote:
Anyway, I'd like to hear Lars's preference, but I don't think consistency favors one outcome, and brevity can't always win (we're talking JS here, not C/C++/Java/C#).
gmail threading is pretty much broken so I don't quite know where all of this stands as of now, but for the record I pretty much loathe these proposals :-)
As far as I'm concerned if it doesn't start with "function" it's a non-starter, because the terse syntax isn't worth the cost. The brevity of anything else doesn't actually pay off (none of the examples posted convince me).
I'm not even sure that expression functions of the form "function (a) a*2" are really worth it, but at least I'll concede that we agreed to put those in. I doubt it makes any difference at all whether one writes "function (x) 37" or "function (x) => 37"; I think they're both
slightly inferior to "function (x) { return 37 }".
Note I'm not opposed to punctuation / special syntax in general; the object/array initializers are obviously better than their more "general" counterparts. I'm just not convinced that that's the case for function expressions, which are already quite succinct in ES3.
(Also, unless I'm mistaken something like the proposed "(a,b,c,d,e,f,g,h,i,j) => 37" requires arbitrary lookahead in a
top-down parser, and I thought we were trying to avoid that. I understand it can be fixed in the same way we fix destructuring assignments, but it makes me uneasy, as the arbitrary lookahead is also required by the human reader. At least for destructuring the major use cases are in binding forms, not really in plain assignments.)
From: lars.t.hansen at gmail.com [mailto:lars.t.hansen at gmail.com]
As far as I'm concerned if it doesn't start with "function" it's a non-starter, because the terse syntax isn't worth the cost. The brevity of anything else doesn't actually pay off (none of the examples posted convince me).
I'm not even sure that expression functions of the form "function (a) a*2" are really worth it, but at least I'll concede that we agreed to put those in. I doubt it makes any difference at all whether one writes "function (x) 37" or "function (x) => 37"; I think they're both slightly inferior to "function (x) { return 37 }".
The => is a deal breaker for me. It looks foreign in the context of the
function keyword.
Note I'm not opposed to punctuation / special syntax in general; the object/array initializers are obviously better than their more "general" counterparts. I'm just not convinced that that's the case for function expressions, which are already quite succinct in ES3.
Let expressions strengthen the case for statement-less function expressions. But let's be clear, both new forms exist for aesthetics only. They do nothing that isn't already being done by ES3 function expressions.
(Also, unless I'm mistaken something like the proposed "(a,b,c,d,e,f,g,h,i,j) => 37" requires arbitrary lookahead in a top-down parser, and I thought we were trying to avoid that. I understand it can be fixed in the same way we fix destructuring assignments, but it makes me uneasy, as the arbitrary lookahead is also required by the human reader. At least for destructuring the major use cases are in binding forms, not really in plain assignments.)
Good point.
On Mar 20, 2007, at 4:38 PM, Jeff Dyer wrote:
The => is a deal breaker for me. It looks foreign in the context of
the function keyword.
Yeah, having let or function and the => is a bit much (but I still
recall you wanting = at first in the same role for expression
closures ;-).
As Lars points out it requires bottom up parsing or a trivial mod to
formal top-down parsing to cope. But we have other such syntactic
exceptions to our top-down C-like heritage. I'm not sure this should
matter.
Note I'm not opposed to punctuation / special syntax in general; the object/array initializers are obviously better than their more "general" counterparts. I'm just not convinced that that's the case for function expressions, which are already quite succinct in ES3.
Let expressions strengthen the case for statement-less function expressions. But let's be clear, both new forms exist for aesthetics only. They do nothing that isn't already being done by ES3 function expressions.
It's not just aesthetics. Aesthetics is the science of beauty, but
usability is what users grok -- not always what is beautiful. IOW,
the pile of "function" keywords in the Y combinator:
function Y(le) { return function (f) { return f(f); }(function (f) { return le(function (x) { return f(f)(x); }); }); }
may be both ugly and unusable, scaled to higher degree of lambda
usage. We can make it prettier by supporting expression closures as
proposed (no { return and } noise), and more usable. But would it be
perhaps most usable, even if uglier by some tastes, to use syntax
such as => instead of the leading function?
On 3/20/07, Brendan Eich <brendan at mozilla.org> wrote:
On Mar 20, 2007, at 4:38 PM, Jeff Dyer wrote:
The => is a deal breaker for me. It looks foreign in the context of the function keyword.
Yeah, having let or function and the => is a bit much (but I still recall you wanting = at first in the same role for expression closures ;-).
As Lars points out it requires bottom up parsing or a trivial mod to formal top-down parsing to cope. But we have other such syntactic exceptions to our top-down C-like heritage. I'm not sure this should matter.
I've started wondering how trivial that mod actually is; the parser must be prepared to cope with type annotations in there:
(a,b:int):int => a+b
so any paren expression will effectively be parsed twice: once to accumulate comma-separated possibly-annotated expressions, and a second time to figure it out once we know how to interpret it. Still not a show-stopper, I know.
Note I'm not opposed to punctuation / special syntax in general; the object/array initializers are obviously better than their more "general" counterparts. I'm just not convinced that that's the case for function expressions, which are already quite succinct in ES3.
Let expressions strengthen the case for statement-less function expressions. But let's be clear, both new forms exist for aesthetics only. They do nothing that isn't already being done by ES3 function expressions.
It's not just aesthetics. Aesthetics is the science of beauty, but usability is what users grok -- not always what is beautiful. IOW, the pile of "function" keywords in the Y combinator:
function Y(le) { return function (f) { return f(f); }(function (f) { return le(function (x) { return f(f)(x); }); }); }
may be both ugly and unusable, scaled to higher degree of lambda usage. We can make it prettier by supporting expression closures as proposed (no { return and } noise), and more usable. But would it be perhaps most usable, even if uglier by some tastes, to use syntax such as => instead of the leading function?
The Y combinator is not even remotely a compelling use case for ECMAScript. ;-) More compelling are those that have already been posted in this thread, and as I wrote before, I didn't find those very compelling either.
From: lars.t.hansen at gmail.com [mailto:lars.t.hansen at gmail.com] On
Behalf
Of Lars T Hansen Sent: Tuesday, March 20, 2007 6:02 PM To: Brendan Eich Cc: Jeff Dyer; Dave Herman; es4-discuss at mozilla.org; Vassily
Gavrilyak;
Yuh-Ruey Chen Subject: Re: Expression closures - use-cases for shortcut lambda syntax(blocks)
On 3/20/07, Brendan Eich <brendan at mozilla.org> wrote:
On Mar 20, 2007, at 4:38 PM, Jeff Dyer wrote:
The => is a deal breaker for me. It looks foreign in the context
of
the function keyword.
Yeah, having let or function and the => is a bit much (but I still recall you wanting = at first in the same role for expression closures ;-).
As Lars points out it requires bottom up parsing or a trivial mod to formal top-down parsing to cope. But we have other such syntactic exceptions to our top-down C-like heritage. I'm not sure this should matter.
I've started wondering how trivial that mod actually is; the parser must be prepared to cope with type annotations in there:
(a,b:int):int => a+b
Yes and as I look at the grammar with the new syntax added, it has some ugly redundancy.
so any paren expression will effectively be parsed twice: once to accumulate comma-separated possibly-annotated expressions, and a second time to figure it out once we know how to interpret it. Still not a show-stopper, I know.
Note I'm not opposed to punctuation / special syntax in general;
the
object/array initializers are obviously better than their more "general" counterparts. I'm just not convinced that that's the
case
for function expressions, which are already quite succinct in
ES3.
Let expressions strengthen the case for statement-less function expressions. But let's be clear, both new forms exist for
aesthetics
only. They do nothing that isn't already being done by ES3
function
expressions.
It's not just aesthetics. Aesthetics is the science of beauty, but usability is what users grok -- not always what is beautiful.
Ah, thanks for clarifying.
I think the proposed syntax loses the usability case. It is redundant, and most users won't be able to hold enough of the grammar in their heads to easily see its meaning when they encounter it in the wild.
the pile of "function" keywords in the Y combinator:
function Y(le) { return function (f) { return f(f); }(function (f) { return le(function (x) { return f(f)(x); }); }); }
may be both ugly and unusable, scaled to higher degree of lambda usage. We can make it prettier by supporting expression closures as proposed (no { return and } noise), and more usable. But would it be perhaps most usable, even if uglier by some tastes, to use syntax such as => instead of the leading function?
The Y combinator is not even remotely a compelling use case for ECMAScript. ;-) More compelling are those that have already been posted in this thread, and as I wrote before, I didn't find those very compelling either.
Sorry Vassily et al, but I'm changing my mind. We should decide finally at the f2f tomorrow.
On 21/03/07, Lars T Hansen <lth at acm.org> wrote:
On 3/20/07, Brendan Eich <brendan at mozilla.org> wrote:
It's not just aesthetics. Aesthetics is the science of beauty, but usability is what users grok -- not always what is beautiful. IOW, the pile of "function" keywords in the Y combinator:
function Y(le) { return function (f) { return f(f); }(function (f) { return le(function (x) { return f(f)(x); }); }); } The Y combinator is not even remotely a compelling use case for ECMAScript. ;-) More compelling are those that have already been posted in this thread, and as I wrote before, I didn't find those very compelling either.
Yet Y combinator is a useful boundary case to test syntax proposals that in fact caused me to cool for the proposal. Here are the alternatives:
function(le) (function(f) f(f))(function(f) le(function(x) f(f)(x)))
le => (f => f(f))(f => le(x => f(f)(x)))
Both are better then the original version and it seems that "name =>
expr" is significantly less clattered than the "function (name) exp" case. But I remember when I saw for the first time Y code written in Haskell (which is much more suited to write that kind stuff) then to grasp the idea I had to rewrite it using:
function selfApply(f) f(f)
which gives for the combinator:
function(le) selfApply(function(f) le(function(x) f(f)(x)))
le => selfApply(f => le(x => f(f)(x)))
Now the "=>" case does not look that much better and in fact I suspect
that to comprehend the code it can be worse than using function keyword since an explicit and long keyword gives a useful reference indeed.
The bottom line: "=>" allows to create incomprehensible but nicely
looking code yet for simpler down-to earth cases its advantages are minor if any.
, Igor
On 3/20/07, Lars T Hansen <lth at acm.org> wrote:
On 3/19/07, Brendan Eich <brendan at mozilla.org> wrote:
Anyway, I'd like to hear Lars's preference, but I don't think consistency favors one outcome, and brevity can't always win (we're talking JS here, not C/C++/Java/C#).
gmail threading is pretty much broken so I don't quite know where all of this stands as of now, but for the record I pretty much loathe these proposals :-)
As far as I'm concerned if it doesn't start with "function" it's a non-starter, because the terse syntax isn't worth the cost. The brevity of anything else doesn't actually pay off (none of the examples posted convince me).
That's probably because you've just looked at tha Well, that's ok.
I'm not even sure that expression functions of the form "function (a) a*2" are really worth it, but at least I'll concede that we agreed to put those in. I doubt it makes any difference at all whether one writes "function (x) 37" or "function (x) => 37"; I think they're both slightly inferior to "function (x) { return 37 }".
Note I'm not opposed to punctuation / special syntax in general; the object/array initializers are obviously better than their more "general" counterparts. I'm just not convinced that that's the case for function expressions, which are already quite succinct in ES3.
Yes, two times longer then everything else. That's why we have no "first-class" functions in ES. First-class means same rights, while functions are discriminated in syntax (and only in syntax). That's probably almost everybody knows that functions are first-class in Ruby, and very few know this fact about ES :-)
(Also, unless I'm mistaken something like the proposed "(a,b,c,d,e,f,g,h,i,j) => 37" requires arbitrary lookahead in a top-down parser, and I thought we were trying to avoid that. >
I wasn't in favour of this proposal much and exactly because of lookahead. Initially proposed haskell-like (\ it){} was free from lookahead problem, it event wasn't in parser, but in lexer. But it is not so pretty, I agree. => also plays well with typing of functional parameters (where YOU had
a pain to write and read).
I understand it can be fixed in the same way we fix destructuring assignments, but it makes me uneasy, as the arbitrary lookahead is also required by the human reader. At least for destructuring the major use cases are in binding forms, not really in plain assignments.)
--lars
Please take a minute and write this simple Ruby code in ES. Please do not refactor it, suppose we do not need any of logic from this function anywhere else and we do not want to bloat our class with miriads of methods. So. just one function.
def doSomethingWithNewPeople selectSQL ="SELECT code, name from people where status='new'" insertSQL = "INSERT INTO young(name,age) values (?,?)" connection.transaction do |tx| connection.prepare(selectSQL) do |selectStatement| connection.prepare(insertSQL) do |insertStatement| selectStatement.executeQuery do |record| name, age = record if name ~ /.*Joe/ and age % 2 then insertStatement.execute(name, age) end end end end end end
On 21/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
Please take a minute and write this simple Ruby code in ES. Please do not refactor it, suppose we do not need any of logic from this function anywhere else and we do not want to bloat our class with miriads of methods. So. just one function.
def doSomethingWithNewPeople selectSQL ="SELECT code, name from people where status='new'" insertSQL = "INSERT INTO young(name,age) values (?,?)" connection.transaction do |tx| connection.prepare(selectSQL) do |selectStatement| connection.prepare(insertSQL) do |insertStatement| selectStatement.executeQuery do |record| name, age = record if name ~ /.*Joe/ and age % 2 then insertStatement.execute(name, age) end end end end end end
Some refactoring is inevitable as Ruby provides its own idioms, but here is its counterpart in JS:
function doSomethingWithNewPeople() { let selectSQL ="SELECT code, name from people where status='new'" let insertSQL = "INSERT INTO young(name,age) values (?,?)" connection.transaction(function(tx) { let selectStatement = connection.prepare(selectSQL); let insertStatement = connection.prepare(insertSQL); for (let [name, age] in selectStatement.executeQuery()) { if (name.match(/.*Joe/) && age % 2) insertStatement.execute(name, age); } }); }
, Igor
---------- Forwarded message ---------- From: Vassily Gavrilyak <gavrilyak at gmail.com>
Date: Mar 21, 2007 7:18 PM Subject: Re: Expression closures - use-cases for shortcut lambda syntax(blocks) To: Igor Bukanov <igor at mir2.org>
On 3/21/07, Igor Bukanov <igor at mir2.org> wrote:
On 21/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
Please take a minute and write this simple Ruby code in ES. Please do not refactor it, suppose we do not need any of logic from this function anywhere else and we do not want to bloat our class with miriads of methods. So. just one function.
def doSomethingWithNewPeople selectSQL ="SELECT code, name from people where status='new'" insertSQL = "INSERT INTO young(name,age) values (?,?)" connection.transaction do |tx| connection.prepare(selectSQL) do |selectStatement| connection.prepare(insertSQL) do |insertStatement| selectStatement.executeQuery do |record| name, age = record if name ~ /.*Joe/ and age % 2 then insertStatement.execute(name, age) end end end end end end
Some refactoring is inevitable as Ruby provides its own idioms, but here is its counterpart in JS:
function doSomethingWithNewPeople() { let selectSQL ="SELECT code, name from people where status='new'" let insertSQL = "INSERT INTO young(name,age) values (?,?)" connection.transaction(function(tx) { let selectStatement = connection.prepare(selectSQL); let insertStatement = connection.prepare(insertSQL); for (let [name, age] in selectStatement.executeQuery()) { if (name.match(/.*Joe/) && age % 2) insertStatement.execute(name, age); } }); }
, Igor
Es4-discuss mailing list Es4-discuss at mozilla.org, mail.mozilla.org/listinfo/es4-discuss
2 leaks. Statements must be closed too. They are prepared, and sometimes you will need to use them again, sometime not. So you can't rely on some "external statement manager" here. GC will not help too. So, simple code provokes 2 bugs in ES. I agree with for (you mean generator here) and let, that is very nice addition to ES. Direct translation with proposed syntax will be this. Using only
function doSomethingWithNewPeople(){ var selectSQL ="SELECT code, name from people where status='new'"; var insertSQL = "INSERT INTO young(name,age) values (?,?)" connection.transaction( tx => { connection.prepare(selectSQL).using(selectStatement => { connection.prepare(insertSQL).using(insertStatement => { selectStatement.query(selectSQL).fetch(record =>{ let [name, age] = record; if(isPersonYoung(name, age)){ insertStatement.execute(name, age); } }) }) }) }) }
Plain ES3 version
function doSomethingWithNewPeople(){ var selectSQL ="SELECT code, name from people where status='new'"; var insertSQL = "INSERT INTO young(name,age) values (?,?)" connection.transaction(function(tx){ connection.prepare(selectSQL).using(function(selectStatement){ connection.prepare(insertSQL).using(function(insertStatement){ selectStatement.query(selectSQL).fetch(function(record) { var name = record[0]; var age = record[1]; if(isPersonYoung(name, age)){ insertStatement.execute(name, age); } }) }) }) }) }
There are also versions with try-finally, even more painful. The same about every other use-cases. Now, I'm sure you are very experienced developer (I saw lot of your code). Now think how many bugs like those I see in novice code. And I can't supply them with clean pattern to avoid such kind of bugs. I can supply such example for every other use-case I provided, but I'm tired of this. Just belive, our team wrote much of such kind of code in server-side JS, client-side DHTML and AS and we have found the clear patterns for all of those use-cases. Ruby, Perl and C# developers found them too, so idioms are well-understood. Python has some workarounds with "with" keyword, but they don't cover all the use-cases. Java developers are discussing closures syntax right now. ES4 will be far behind.
, Vassily
First, let me apologize about spreading FUD about Ruby do-blocks. They are indeed full lambdas at least since 2002 even if the originally they were indeed a body of do-loop integrated with generators.
Now back to to that Rubby example.
On 21/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
2 leaks. Statements must be closed too. They are prepared, and sometimes you will need to use them again, sometime not. So you can't rely on some "external statement manager" here. GC will not help too. So, simple code provokes 2 bugs in ES.
Well, I did not know that the transaction destructor would leave the prepared statements. In any case, a good library should reflect the language so a direct translation of idioms from one language to another may lead to ugly code.
I agree with for (you mean generator here) and let, that is very nice addition to ES. Direct translation with proposed syntax will be this. Using only
If one really wants to go to the direct translation, then one have to use the generators all the way down to much Ruby semantics as the example does not require lambdas as the do-blocks-as-lambdas do not leak:
function doSomethingWithNewPeople() { let selectSQL ="SELECT code, name from people where status='new'"; let insertSQL = "INSERT INTO young(name,age) values (?,?)"; for (let tx in connection.transaction()) { for (let selectStatement in connection.prepare(selectSQL)) { for (let insertStatement in connection.prepare(insertSQL)) { for (let [name, age] in selectStatement.executeQuery()) { if (name.match(/.*Joe/) && age % 2) insertStatement.execute(name, age); } } } } }
Except for the loop stigma that is associated with for-in statement, I do not see how this is worse than that Ruby's case. I suspect that do blocks in Ruby do no have the loop stigma since from the date one Ruby advocated such usage.
, Igor
On Mar 21, 2007, at 3:26 AM, Igor Bukanov wrote:
Yet Y combinator is a useful boundary case to test syntax proposals that in fact caused me to cool for the proposal. Here are the alternatives:
[...]
function selfApply(f) f(f)
which gives for the combinator:
function(le) selfApply(function(f) le(function(x) f(f)(x)))
le => selfApply(f => le(x => f(f)(x)))
Now the "=>" case does not look that much better and in fact I suspect that to comprehend the code it can be worse than using function keyword since an explicit and long keyword gives a useful reference indeed.
The bottom line: "=>" allows to create incomprehensible but nicely looking code yet for simpler down-to earth cases its advantages are minor if any.
I agree (nice demonstration based on Y -- thanks!). I'm quite cool to
=> now, and I'd like to point out that with expression closures as
proposed, the only issue that nags at us, the one Vassily raised
obliquely, is the cost of the leading 'function'.
I agree the top-down parse-ability of (a,b,c) => expr is poor. Lars
makes a good point about have to check the AST. I pointed out that we
have a similar case with destructuring assignment (but not
destructuring in declarative binding contexts). But two wrongs...
If we had proper macros, we could support |define λ function| and be
done. As Peter Hall points out, λ is a perfectly valid ES3
identifier. The one could rewrite:
function(le) (function(f) f(f))(function(f) le(function(x) f(f)(x)))
as:
λ(le) (λ(f) f(f))(λ(f) le(λ(x) f(f)(x)))
which looks awfully familiar. But that can wait for ES6 ;-).
On 2007-03-21, at 13:44 EDT, Vassily Gavrilyak wrote:
---------- Forwarded message ---------- From: Vassily Gavrilyak <gavrilyak at gmail.com> [...]
There are also versions with try-finally, even more painful. The same about every other use-cases.
Another solution would be to add macros to the language, so that the
try/finally version was cleaner. That's the lispy way:
macro withPreparedStatement {
withPreparedStatement (??prepared:variable = ?statement:expression
on ?connection:expression) ?body:statement =>
{ let ?prepared = ?connnection.prepare(?statement);
try {
?body
} finally {
?connnection.deallocate(?prepared);
}
}
}
function doSomethingWithNewPeople()
{
withPreparedStatement(selectStatement =
"SELECT code, name from people where status='new'" on
connection) {
withPreparedStatement(insertStatement =
"INSERT INTO young(name,age) values (?,?)" on connection){
connection.transaction(function(tx) {
for (let [name, age] in selectStatement.executeQuery()) {
if (name.match(/.*Joe/) && age % 2)
insertStatement.execute(name, age);
}
});
}
}
}
:)
[Yes I know the macro idea has been deferred.]
I totally agree with both proposals, they will work. I will argue though :-)
Generators are the same as functions, that's true. And I understand you, Igor, as a primary generators author. Everything looks like 'for' :-). The same with me, everything looks like 'function' :-). We already talked about this, shortly. I understand that everything can be done with generators. Just why with this cool new concept, and not plain old function? I still have problems explaining generators even to most experienced members of our team. Functions were accepted almost in no time.
Macros are deferred, and rightly so, I do not see many major use-cases for macro in ES (as there are still no use for macros in Ruby, language just good enough). I would like having macros probably, but why use this heavy artillery when simple function will do? Especially when those functions are already here and they are doing everything and doing just right. Only a LITTLE clumsy. Why add new concepts into the language? And your example looks almost the same as mine, the only difference is in using = instead of =>
You removed parens somehow in withStatement, with just a little magic. What if I want to add another function argument after this block (2 function arguments)? Seems I can't, syntax problem, two blocks, and no possibility to do this in one macro (that was already discussed in this thread, Dave told about something similar). So we will need macro overloading by arguments counts or types, or something like this. I won't talk about well-known problems with macros from Lisp (why people avoid them?). Why complicate things? How I can explain this magic to newbie? I explained function shortcuts very easy, in one statement. No magic here. Just plain functions you already know, written with short syntax you have already seen and used for arrays, objects, strings and regexps (and dates in ES4). It's clear what they do, clear how to write, read and debug them (reusing knowledge).
You can defer functions to ES5 and you have all the rights to do so, you are the language designers. Just I do not understand the reasons. Every other language seems wanting to provide something in this area (C#, Java, even C++ discussed this) or already provided (Ruby etc). It was hard to do for designers of those languages, but user asks them and they just provide what users ask. ES had good functions almost from the start (thanks Brendan). Use-cases for them are clear from other languages and from ES itself. Why not just reuse their experience? We will also reuse developers knowledge this way.
Those were pros. Cons? I see only technical problems with lookahead parser, though lookahead is already here. Readability arguments - it seems nobody provided more readable code with current syntax. Project policy - one can always disable usage of shortcuts for a team. What else?
Best , Vassily
On 3/21/07, Igor Bukanov <igor at mir2.org> wrote:
First, let me apologize about spreading FUD about Ruby do-blocks. They are indeed full lambdas at least since 2002 even if the originally they were indeed a body of do-loop integrated with generators.
Now back to to that Rubby example.
On 21/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
2 leaks. Statements must be closed too. They are prepared, and sometimes you will need to use them again, sometime not. So you can't rely on some "external statement manager" here. GC will not help too. So, simple code provokes 2 bugs in ES.
Well, I did not know that the transaction destructor would leave the prepared statements. In any case, a good library should reflect the language so a direct translation of idioms from one language to another may lead to ugly code.
Yes, they can leave and that's good. Connections are pooled, statements can be reused later again.
I agree with for (you mean generator here) and let, that is very nice addition to ES. Direct translation with proposed syntax will be this. Using only
If one really wants to go to the direct translation, then one have to use the generators all the way down to much Ruby semantics as the example does not require lambdas as the do-blocks-as-lambdas do not leak:
function doSomethingWithNewPeople() { let selectSQL ="SELECT code, name from people where status='new'"; let insertSQL = "INSERT INTO young(name,age) values (?,?)"; for (let tx in connection.transaction()) { for (let selectStatement in connection.prepare(selectSQL)) { for (let insertStatement in connection.prepare(insertSQL)) { for (let [name, age] in selectStatement.executeQuery()) { if (name.match(/.*Joe/) && age % 2) insertStatement.execute(name, age); } } } } }
Except for the loop stigma that is associated with for-in statement, I do not see how this is worse than that Ruby's case. I suspect that do blocks in Ruby do no have the loop stigma since from the date one Ruby advocated such usage.
Hmm, maybe there is some idea here.... It looks nicer then closure example, for this particularly usage. At least variable are written before statement, not after as with functions and that's something I like. Maybe we can get rid of for stigma somehow... Need to think more about this.
Thanks, Vassily
On 3/22/07, P T Withington <ptw at pobox.com> wrote:
Another solution would be to add macros to the language, so that the try/finally version was cleaner.
Having a lightweight closure syntax reduces the need for many with-* style macros. This is how Smalltalk manages to have a lot of nice looking additions to the language by using their lightweight blocks. There was a discussion a while back on ll1-discuss about it:
people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg02060.html
I wrote a parser combinator library in Javascript and the noise of 'function' did get in the way a bit when dealing with on-the-fly constructed parsers.
Chris.
Hey Chris, you throw Bachrach, Weinreb, and Graham at me, I have to
cry uncle!
Vassily Gavrilyak wrote:
If one really wants to go to the direct translation, then one have to use the generators all the way down to much Ruby semantics as the example does not require lambdas as the do-blocks-as-lambdas do not leak:
function doSomethingWithNewPeople() { let selectSQL ="SELECT code, name from people where status='new'"; let insertSQL = "INSERT INTO young(name,age) values (?,?)"; for (let tx in connection.transaction()) { for (let selectStatement in connection.prepare(selectSQL)) { for (let insertStatement in connection.prepare(insertSQL)) { for (let [name, age] in selectStatement.executeQuery()) { if (name.match(/.*Joe/) && age % 2) insertStatement.execute(name, age); } } } } }
Except for the loop stigma that is associated with for-in statement, I do not see how this is worse than that Ruby's case. I suspect that do blocks in Ruby do no have the loop stigma since from the date one Ruby advocated such usage. Hmm, maybe there is some idea here.... It looks nicer then closure example, for this particularly usage. At least variable are written before statement, not after as with functions and that's something I like. Maybe we can get rid of for stigma somehow... Need to think more about this.
Thanks, Vassily
, Igor
Es4-discuss mailing list Es4-discuss at mozilla.org, mail.mozilla.org/listinfo/es4-discuss
Es4-discuss mailing list Es4-discuss at mozilla.org, mail.mozilla.org/listinfo/es4-discuss
As I've mentioned before, Python has a "with" statement that seems to cover this issue, but I'm not that familiar with Python 2.5 features so I'm not really sure.
In any case, we can come up with a construct that explicitly evaluates a generator only once. Something like (reusing do keyword):
do (let tx in connection.transaction()) { ... }
But this still has a slight connotation of a loop (do...while). Or reusing the let syntax:
let (tx in connection.transaction()) { ... }
One possible disadvantage of this let syntax is that tx can't be an existing variable, i.e. tx is always a new variable within the block's scope. I say possible, since I don't see any use case that requires relaxing this restriction.
Just some brainstorming...
On 21/03/07, Vassily Gavrilyak <gavrilyak at gmail.com> wrote:
And I understand you, Igor, as a primary generators author.
I has nothing to do with generator authorship in JS or any other language!
Everything looks like 'for' :-). The same with me, everything looks like 'function' :-). We already talked about this, shortly. I understand that everything can be done with generators. Just why with this cool new concept, and not plain old function?
Because the question was how to write the example in ES, not how to write it in ES using only functions.
The result was in fact very readable code which IMO looks better (if one forgets the loop stigma) than both function(name) expr and name =>
expr versions. That plus those Y-combinator examples tells me that in fact name => expr or (\name) expr as syntax sugar are not sufficiently
sweet to justify their existence compared with function(name) expr.
, Igor
I like the expression closures were accepted into the spec, they will nicely cover many use-cases for iterating, sorting, etc. using closures. And I was the one who suggested Brendan on his blog to bring the issue of shortcut function syntax. Actually I proposed a more simpler solution, just using lambda-like syntax from Haskell.
However I didn't provide more use-cases where it can be useful and allows to not add support for such use-cases with some special language construct.
In short - look how Ruby uses blocks.
So, just a few from the top
2.Transactions (mixed with resource usage) for frameworks like Phobos (Phobos on Rails? :-) ) connection.transaction( function( tx){ using(connection.prepare("INSERT INTO ..."), function( statement){ using(statement, function(recordset){ processRecordset(recordset); }) // recordset closed }); //statement closed })//commit-rollback depended on whether exception was thrown
CPS-style asynchronous processing 3.1 - AJAX server.getEMails(function(emails){ eMails.forEach(function(email){ if(somethingInEmail(email)){ server.forwardEMail(function(response){ processForwarding(responce); }) } }) }) 3.2 animations (Flash or DHTM) Something like - move ball, when it hits the wall, explode can be clearly expressed with cps-style too.
Some simplest idioms like 10.times do {} from Ruby. (10).times(function(){ print("Hi"); })
Now, all those "function" just doesn't look like functions, bat rather blocks. So word function is meaningless here and unnecessary complicates the code.
Maybe the best solution here is too introduce some special syntax for resource allocation and async programming. That what some languages do and ES is still not here. But I doubt that transaction support will ever be in language. And there probably many more usages for blocks.
In our team we currently using modified SpiderMonkey for JS and MTASC for AS with modified lexer (not parser) for support shortcut syntax. We use this syntax quite heavily and found it very useful and not a false economy. Modification is VERY simple (2 lines in both cases) - when lexer found "(" replace it with "function("
This way ES could almost for free have a feature that many languages (C# 3, Ruby, Smalltalk, Perl) already have and developers found this feature useful. Java seems going there too with closures proposal.
And yes, this is just syntax sugar and nothing new for ES that aren't already present. But developers will fail to recognize that ES actually have blocks just because of syntax. I am the example of such developer :-).
Hope it's probably not to late to address those issues.
, Vassily Gavrilyak