Allen's lambda syntax proposal
2008/11/29 Brendan Eich <brendan at mozilla.com>:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
In know this syntax primarily from Ruby's blocks.
This would allow writing
var foo = {|a, b, c| ...};
which could be written even shorter as
var foo(a, b, c) {...};
The later is more consistent with the following which would also be necessary at times.
lambda(a, b, c) {...}
I'd prefer the last two options.
Anyway, we need a fun weekend thread, and everyone loves syntax. Comments?
The following please!
(lambda (a b c) ...)
;-)
Peter
Hey, I like that. In the spirit of fun, here's something that does something weird and random.
var fsm = { r: {|| Math.random()<0.5}, f: {|a| print(a); ( fsm.r() ? fsm.s : fsm.r() ? fsm.m : {||} )("f");}, s: {|a| print(a); ( fsm.r() ? fsm.m : fsm.r() ? fsm.f : {||} )("s") }, m: {|a| print(a); ( fsm.r() ? fsm.f : fsm.r() ? fsm.s : {||} )("m");} } fsm.f();
On Nov 29, 2008, at 11:33 PM, Peter Michaux wrote:
This would allow writing
var foo = {|a, b, c| ...};
which could be written even shorter as
var foo(a, b, c) {...};
Why ever would you want to use 'var' there? Could foo be reassigned to
denote 42, or "hello"?
Syntax for quite different semantic effects should be composable,
possibly with compound forms (short-hands for clichéd patterns). But
it would be wrong to conflate two unrelated ideas (declare a variable;
define a function) if no use-case wants the full combination.
Yes, ES1 didn't separate function from var enough in this regard, but
that's a bug (addressed by strict mode in ES3.1).
Some languages use def to introduce constants including constant
function bindings. So I could see an argument for const foo(a,b,c)
{...}. But not for var.
The later is more consistent with the following which would also be necessary at times.
lambda(a, b, c) {...}
Necessary for what use-case?
On Sat, Nov 29, 2008 at 11:53 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 29, 2008, at 11:33 PM, Peter Michaux wrote:
This would allow writing
var foo = {|a, b, c| ...};
which could be written even shorter as
var foo(a, b, c) {...};
Why ever would you want to use 'var' there? Could foo be reassigned to denote 42, or "hello"?
Yes it could be reassigned.
It is analogous to
(define (foo a b c) ...)
Could also write
var foo(a, b, c) {...} let foo(a, b, c) {...} const foo(a, b, c) {...}
The later is more consistent with the following which would also be necessary at times.
lambda(a, b, c) {...}
Necessary for what use-case?
I believe the same ones as for {|a, b, c| ...}. For example, if one wants to pass an anonymous lambda to another lambda.
If this is a more serious thread about syntax, I think all the callable things should have semetrical declaration syntax
lambda(a, b, c) {...} function(a, b, c) {...} class(a, b, c) {...}
Especially the syntax for lambda and function declarations seem as though they should be symmetrical because they will be called the same way: "foo()". This also makes me thing that class's should be called the same way. That is, the "new" should be optional just like it is for Array constructor, for example.
There is beauty in symmetry and nature has shown over and over again that symmetry is a sign of good fitness. I think this is widely known to be related to Lisp and its strengths. One interesting thing about Lisp is that s-expressions were not the intended end syntax. M-expressions were going to be more like languages which have "syntax". Once the power enabled by the symmetry of s-expressions was subsequently discovered it was so persuasive that they stuck.
By adding more syntax ES could be digging a deeper hole which favors syntax over other semantically useful ideas.
Peter
On Nov 30, 2008, at 12:28 AM, Peter Michaux wrote:
On Sat, Nov 29, 2008 at 11:53 PM, Brendan Eich <brendan at mozilla.com>
wrote:On Nov 29, 2008, at 11:33 PM, Peter Michaux wrote:
This would allow writing
var foo = {|a, b, c| ...};
which could be written even shorter as
var foo(a, b, c) {...};
Why ever would you want to use 'var' there? Could foo be reassigned
to denote 42, or "hello"?Yes it could be reassigned.
You didn't say why, though -- what is the use-case?
The later is more consistent with the following which would also be necessary at times.
lambda(a, b, c) {...}
Necessary for what use-case?
I believe the same ones as for {|a, b, c| ...}. For example, if one wants to pass an anonymous lambda to another lambda.
But why do you need to start such a lambda expression with a Greek
letter-name keyword?
Function expressions and definitions in ES3 start with 'function',
true enough. Allen's lambda syntax is simply an alternative proposal
for the expression case. It doesn't make a compound lambda + binding
form.
lambda(a, b, c) {...} function(a, b, c) {...} class(a, b, c) {...}
Especially the syntax for lambda and function declarations seem as though they should be symmetrical because they will be called the same way: "foo()".
This seems good as far as it goes, but the 'lambda' keyword is
overlong and obscure to some (I already agreed with you that it's le
mot juste in a previous thread; but Allen's proposal avoids any mot at
all, in favor of punctuation).
But why have three defining forms for callables? Sure, compatibility
may require two. But three? The weakest link here is class, without
more motivation (which I believe exists, see below).
This also makes me thing that class's should be called the same way. That is, the "new" should be optional just like it is for Array constructor, for example.
Some constructors behave differently when called. This could be
considered a rare quirk but (1) people like doing it in their own
abstractions; (2) it's desirable for self-hosting the quirky built-ins.
But again, a class is not just another kind of function. It's a higher-
integrity factory for higher-integrity instances. Whether one calls it
via new or directly as a function is secondary. Its syntax can differ
from function if necessary (not gratuitously).
IOW I think your argument oversimplifies.
I believe that TC39 agrees that classes should be generative (nesting
a class in another class or function makes a new, distinct class
"type" on each evaluation), and (unrelated point) optionally anonymous
as your list shows. But this does not follow simply from borrowed/
desugared-to function syntax or semantics. Classes may grow hair that
functions won't, and lambdas must not (if they are to be worthy of the
name).
On Sun, Nov 30, 2008 at 12:46 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 30, 2008, at 12:28 AM, Peter Michaux wrote:
On Sat, Nov 29, 2008 at 11:53 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 29, 2008, at 11:33 PM, Peter Michaux wrote:
This would allow writing
var foo = {|a, b, c| ...};
which could be written even shorter as
var foo(a, b, c) {...};
Why ever would you want to use 'var' there? Could foo be reassigned to denote 42, or "hello"?
Yes it could be reassigned.
You didn't say why, though -- what is the use-case?
Currently it is relatively common to reassign to a variable holding a reference to a function. I don't know of cases where I want to assign an integer to a variable that references a function; however, reassigning a function to a variable referencing a function is common.
// file 1 : the program
var foo = function() {}
// file 2 : the debugging program
var foo = (function() { var original = foo; return function() { alert('debugging'); return original(); }; })();
I wrote a layered XMLHttpRequest library which used the above idea of wrapping and reassigning.
There is also the lazy function definition pattern.
peter.michaux.ca/articles/lazy-function-definition-pattern
The later is more consistent with the following which would also be necessary at times.
lambda(a, b, c) {...}
Necessary for what use-case?
I believe the same ones as for {|a, b, c| ...}. For example, if one wants to pass an anonymous lambda to another lambda.
But why do you need to start such a lambda expression with a Greek letter-name keyword?
It isn't necessary to start with a Greek letter-name keyword. It could be
gratitouslyDifferentNameWhichIgnoresLongStandingPrecedenceForUsingLambda(a, b, c) {...}
Hey, you said this thread was for fun! ;-)
All I meant was that if there was a short form
var foo(a, b, c) {...}
there would still need to be a lambda expression.
Function expressions and definitions in ES3 start with 'function', true enough. Allen's lambda syntax is simply an alternative proposal for the expression case. It doesn't make a compound lambda + binding form.
lambda(a, b, c) {...} function(a, b, c) {...} class(a, b, c) {...}
Especially the syntax for lambda and function declarations seem as though they should be symmetrical because they will be called the same way: "foo()".
This seems good as far as it goes, but the 'lambda' keyword is overlong
It is no different in the amount of typing. Both
lambda(){}
and
{||}
both take only two characters (e.g. ell tab) to type in any editor with snippets.
Even without snippets, I simply don't think "lambda" is very long. In fact, 99% of the time I type "function() {};" manually because I rarely remember eff tab will do the same thing for me and it doesn't bother me at all.
and obscure to some
Yes obscure at first but once they learn about lambda and its history, don't you think they will cherish the privilege to use it?
(I already agreed with you that it's le mot juste in a previous thread; but Allen's proposal avoids any mot at all, in favor of punctuation).
I like the "lambda" version and think the syntax fits in nicely and shows how lambdas and functions are related semantically.
But why have three defining forms for callables? Sure, compatibility may require two. But three? The weakest link here is class, without more motivation (which I believe exists, see below).
This also makes me thing that class's should be called the same way. That is, the "new" should be optional just like it is for Array constructor, for example.
Some constructors behave differently when called.
Yes.
This could be considered a rare quirk
Definitely.
but (1) people like doing it in their own abstractions;
They do?!
(2) it's desirable for self-hosting the quirky built-ins.
Yes.
But again, a class is not just another kind of function. It's a higher-integrity factory for higher-integrity instances. Whether one calls it via new or directly as a function is secondary. Its syntax can differ from function if necessary (not gratuitously).
Yes it could be different. Since Java has dispatch on argument type and hence can have multiple constructors, it wouldn't make much sense to have class(){} in Java.
IOW I think your argument oversimplifies. I believe that TC39 agrees that classes should be generative (nesting a class in another class or function makes a new, distinct class "type" on each evaluation),
I agree with this requirement. I'm not in favor of type-checking because I don't think it will work well in ES (not even as well as it doesn't work in other languages) but it will be interesting to see how type-checking is proposed to work with this requirement.
and (unrelated point) optionally anonymous as your list shows. But this does not follow simply from borrowed/desugared-to function syntax or semantics. Classes may grow hair that functions won't, and lambdas must not (if they are to be worthy of the name).
Indeed.
Peter
Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
Looks like Ruby to me, so you know I love it. :)
Nice lambda syntax really matters. If that tool is too heavy, I only use it half as often as I should.
Chris
On Mon, Dec 1, 2008 at 2:06 AM, Chris Pine <chrispi at opera.com> wrote:
Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
Looks like Ruby to me, so you know I love it. :)
Nice lambda syntax really matters. If that tool is too heavy, I only use it half as often as I should.
What is to be made of that last sentence? If you have a choice between the following, The "lambda" version is still shorter.
function() {} lambda() {}
Peter
Peter Michaux wrote:
On Mon, Dec 1, 2008 at 2:06 AM, Chris Pine <chrispi at opera.com> wrote:
Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ? Looks like Ruby to me, so you know I love it. :)
Nice lambda syntax really matters. If that tool is too heavy, I only use it half as often as I should.
What is to be made of that last sentence? If you have a choice between the following, The "lambda" version is still shorter.
function() {} lambda() {} {||}
Not from where I'm sitting... 'lambda' is shorter than 'function', but '{||}' is shortest of all... or perhaps I don't understand you?
Chris
On 2008-11-30, at 01:30EST, Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
I would rather have a more literate syntax, lest we degenerate to
where practically any comic book blasphemy is a valid program.
(BTW, I'm pretty sure I have that same Byte issue, in a similar box,
with a similar musty smell, and "the blue book". Back then,
worrying that 'line noise' or the death throes of your modem hanging
up would write code for you was a legitimate concern. Today, it is
just my old eyes that might gloss over {||
and wonder why the var
s
in that block are not visible in the enclosing function...)
On Mon, Dec 1, 2008 at 7:31 AM, P T Withington <ptw at pobox.com> wrote:
On 2008-11-30, at 01:30EST, Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not:
{ |a, b, c| ... } ?
I would rather have a more literate syntax, lest we degenerate to where practically any comic book blasphemy is a valid program.
(BTW, I'm pretty sure I have that same Byte issue, in a similar box, with a similar musty smell, and "the blue book". Back then, worrying that 'line noise' or the death throes of your modem hanging up would write code for you was a legitimate concern. Today, it is just my old eyes that might gloss over
{||
and wonder why thevar
s in that block are not visible in the enclosing function...)
Since it's a lambda, the 'var's will be visible in the enclosing function.
The point of having a very compact syntax for lambda is too make it pleasant to write control abstractions, as one does casually in Smalltalk. With the verbose "lambda" spelling, people will avoid those, or invent macro systems (as Scheme programmers do) mostly so they can avoid seeing all those extra "lambda" letters in the code.
Think of lambdas as blocks plus a bit more, rather than function minus a bit. Viewed this way, their block-like syntax is a virtue.
On 2008-12-01, at 11:30EST, Mark S. Miller wrote:
On Mon, Dec 1, 2008 at 7:31 AM, P T Withington <ptw at pobox.com> wrote:
On 2008-11-30, at 01:30EST, Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not:
{ |a, b, c| ... } ?
I would rather have a more literate syntax, lest we degenerate to
where practically any comic book blasphemy is a valid program.(BTW, I'm pretty sure I have that same Byte issue, in a similar
box, with a similar musty smell, and "the blue book". Back then, worrying
that 'line noise' or the death throes of your modem hanging up would write
code for you was a legitimate concern. Today, it is just my old eyes that might
gloss over{||
and wonder why thevar
s in that block are not visible
in the enclosing function...)Since it's a lambda, the 'var's will be visible in the enclosing
function.
Eh? So:
function () { var foo = 42; {|| var foo = 3; } return foo; }
and:
function () { var foo = 42; { var foo = 3; } return foo; }
Give the same answer?
The point of having a very compact syntax for lambda is too make it
pleasant to write control abstractions, as one does casually in Smalltalk.
With the verbose "lambda" spelling, people will avoid those, or invent macro
systems (as Scheme programmers do) mostly so they can avoid seeing all those
extra "lambda" letters in the code.Think of lambdas as blocks plus a bit more, rather than function
minus a bit. Viewed this way, their block-like syntax is a virtue.
I agree with the goal of compactness. I just don't like it to be too
compact. Call me a curmudgeon. I don't like that not
is spelt !
or that it is so easy to make a one-letter misspelling of eql
and
end up with setq
either.
On Mon, Dec 1, 2008 at 8:47 AM, P T Withington <ptw at pobox.com> wrote:
Eh? So:
function () { var foo = 42; {|| var foo = 3; } return foo; }
and:
function () { var foo = 42; { var foo = 3; } return foo; }
Give the same answer?
No, because you forgot to call it.
function () { var foo = 42; {|| var foo = 3; }(); return foo; }
and:
function () { var foo = 42; { var foo = 3; } return foo; }
do give the same answer.
On 2008-12-01, at 11:54EST, Mark S. Miller wrote:
On Mon, Dec 1, 2008 at 8:47 AM, P T Withington <ptw at pobox.com> wrote:
Eh? So:
function () { var foo = 42; {|| var foo = 3; } return foo; }
and:
function () { var foo = 42; { var foo = 3; } return foo; }
Give the same answer?
No, because you forgot to call it.
Cool. So I can use {||
and }
to comment out blocks of code... :P
function () { var foo = 42; {|| var foo = 3; }(); return foo; }
and:
function () { var foo = 42; { var foo = 3; } return foo; }
do give the same answer.
Ok, your suggestion of 'block plus' not 'function minus' is making
more sense to me. Still hard for me to understand the newspeak.
Trying to wrap my mind around var
meaning "free"; I think I get it.
But, I still think {||
looks like my cat stepped on the keyboard.
On Mon, Dec 1, 2008 at 7:31 AM, P T Withington <ptw at pobox.com> wrote:
On 2008-11-30, at 01:30EST, Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
I would rather have a more literate syntax, lest we degenerate to where practically any comic book blasphemy is a valid program.
I agree with this sentiment. The phrase "ASCII vomit" comes to mind and becomes a worry.
Take an ES program and replace all if-else with ?: and then most functions with {||} and it starts to look quite cryptic.
A reader would have a difficult time even knowing what to look up in a book index to find out what the {||} that they see in some code is supposed to be. It wouldn't necessarily be clear that the {} and the || are related.
Peter
On Dec 1, 2008, at 10:17 AM, Peter Michaux wrote:
On Mon, Dec 1, 2008 at 7:31 AM, P T Withington <ptw at pobox.com> wrote:
On 2008-11-30, at 01:30EST, Brendan Eich wrote:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
I would rather have a more literate syntax, lest we degenerate to
where practically any comic book blasphemy is a valid program.I agree with this sentiment. The phrase "ASCII vomit" comes to mind and becomes a worry.
I don't think it's a big worry. It seems to me ES3 (never mind new
syntax) is far from Perl, but not nearly Python or an even more
pedagogical language in which
There should be one-- and preferably only one --obvious way to do
it.
There are already several ways to say the same thing, for many values
of "thing", in JS.
Take an ES program and replace all if-else with ?: and then most functions with {||} and it starts to look quite cryptic.
But functions remain. I doubt lambdas in any synax will replace them.
A reader would have a difficult time even knowing what to look up in a book index to find out what the {||} that they see in some code is supposed to be. It wouldn't necessarily be clear that the {} and the || are related.
A novice reader, you mean. Readers who know the language may prefer
brevity. This is not a simple "read over write" optimization issue.
On Mon, Dec 1, 2008 at 10:24 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Dec 1, 2008, at 10:17 AM, Peter Michaux wrote:
Take an ES program and replace all if-else with ?: and then most functions with {||} and it starts to look quite cryptic.
But functions remain. I doubt lambdas in any synax will replace them.
Actually, I think the combination of Object.create, lambda, rest parameters and class will probably eliminate function and prototype from most code.
lambda will be slightly faster and lighter than function. Programmers like faster and lighter when it comes at no cost.
People seem to want to think in terms of classes and not the prototype property of a function object. Colin Moock's book on AS2 recommended against using prototype after the introduction of class to that language.
Peter
Just to clarify some speculation, the syntax I proposed ({||}) was solely inspired by Smalltalk and tempered by the parsing realities of a C-like syntax. Any similarities to Ruby constructs are probably examples of parallel evolution under similar environmental pressures. I suspect that designers of other languages with C-like syntax (C# comes to mind with its () => expr "lambda" syntax) did not have the experience or goal of using closures to create control abstractions (which often requires passing multi-statement closures) and so arrived at a more function-like concise closure syntax.
(more below)
-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Peter Michaux Sent: Monday, December 01, 2008 10:17 AM To: P T Withington ...
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
I would rather have a more literate syntax, lest we degenerate to where practically any comic book blasphemy is a valid program.
I agree with this sentiment. The phrase "ASCII vomit" comes to mind and becomes a worry.
The use of {} as grouping syntax is inherent in the early (and irrevocable) decision for JavaScript to use a C-like syntax rather than an Algol/Pascal or Lisp like syntax. We already overload {} to bracket both statement blocks and object constructors so it doesn't seem too burdensome to have another syntactically distinct form that from some perspectives is the semantic union of the other two forms.
JavaScript already has a concise "ASCII vomit" syntax for constructing arrays and objects. Given the direction the language seems to be heading, having a similar concise syntax for constructing closures seems quite appropriate.
On Mon, Dec 1, 2008 at 12:19 PM, Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com> wrote:
JavaScript already has a concise "ASCII vomit" syntax for constructing arrays and objects.
I didn't mean to imply that JavaScript currently deserves the "ASCII vomit" label. I also didn't mean to imply the {||} was so bad as to deserve name calling. It is more the potential for the cumulative effect to cause trouble.
Given the direction the language seems to be heading,
Can you expand on what you mean by that please?
having a similar concise syntax for constructing closures seems quite appropriate.
Peter
On Nov 29, 2008, at 10:30 PM, Brendan Eich wrote:
At the TC39 meeting two weeks ago in Kona, we had a brief
bikeshedding discussion about lambda syntax and why it matters.
Observation: blocks in Smalltalk being lightweight means users don't
mind writing them for control abstractions, compared to JS functions
in ES3. In Smalltalk, ignoring JS, it's hard to beat [ and ] as
overhead, although one must count the message selector and its
punctuation too.Allen Wirfs-Brock put his proposal, which will not shock you who
know Smalltalk or Allen, on the whiteboard:// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
I like the brevity, but having the formals inside the block and in ||
delimiters seems like it will look weird in an ECMAScript program. For
function declarations the parameters go in parentheses, and for calls
(presumably also for lambda calls), the arguments go in parens. If
brevity is important, why not lift the lambda syntax from modern pure
functional languages:
(a, b, c) { ... }
That's backslash as a lambda operator. This has one more character
than your version, but will make formals and parameters look the same
for functions, lambdas, and calls, and will avoid putting the formals
inside the body which I think is confusing and visually hard to scan.
, Maciej
Question: How would I write a recursive function with that syntax? Is there a way to name the lambda, other than var = {||}; ?
On 2008-12-01, at 15:59EST, Maciej Stachowiak wrote:
(a, b, c) { ... }
+1
{|a,b,c| ...} or (a,b,c) {...} or {(a,b,c) ...}
I could be happy with any of them and can find pros and cons with each. I think the high order bit should be that a concise closure syntax is possible and desirable. If we agree on that then we just need to pick one.
The use of \ slightly bothers me because it is takes a character that now is exclusively used in the lexical (token) grammar ( Unicode escapes, string escapes, line continuations) and gives it syntactic significance. This is probably not a fatal flaw but it would mean that the lexical uses of \ become less visually distinctive.
Whether someone prefers the parameters inside or outside the braces may be another symptom of whether they are focusing on control abstraction or functional abstraction. With control abstraction you use closures as "blocks" of code that form pieces of the abstraction so it may seems natural from that perspective for the braces to surround the entire "block". This is closer to the syntactic feel of the built-in control statements. If you are building functional abstractions then you are probably thinking about the closures as functions so placing the formals before the body seems natural.
It's fairly common with control abstractions to use 0 argument closures so the readability of {||...}, (){...}, and {()...} are probably also worth considering. Ideally a 0 argument closure would be written as {...} but resolution of the syntactic ambiguities between object literal and such closures (particularly in the presence of statement labels) seems too difficult to contend with.
My focus on supporting control abstraction may be mute. While Smalltalk and Ruby show that power of such abstraction mechanisms it takes more than just a concise block-like closure notation to achieve it. The complexity of the BGGA Java closure proposal (and the associated controversy) shows how hard it can be to fully support control abstraction using C syntax (and, of course, Java semantics).
Allen Wirfs-Brock wrote:
{|a,b,c| ...} or (a,b,c) {...} or {(a,b,c) ...}
The use of \ slightly bothers me because it is takes a character that now is exclusively used in the lexical (token) grammar ( Unicode escapes, string escapes, line continuations) and gives it syntactic significance. This is probably not a fatal flaw but it would mean that the lexical uses of \ become less visually distinctive.
The language overuses / recklessly, and still it didn't die. I suppose it could survive the overuse of \ as well.
Is recursion still desirable in this form. If so, then of the three I like
\(a,b,c) {}
because you can think of the \ as being an abbreviation of function.
\ name(a,b,c) {}
Just don't start your function name with u.
@ is unused @(a, b, c){}
On Dec 1, 2008, at 3:47 PM, Felix wrote:
@ is unused @(a, b, c){}
@ is already used by ECMA-357 (E4X). Also some tradition of denoting
dereference (Cyclone, managed C++ IIRC).
Hard to beat \ if we want the parenthesized formal parameter list in
front.
Is recursion still desirable in this form. If so, then of the three I like
(a,b,c) {}
because you can think of the \ as being an abbreviation of function.
\ name(a,b,c) {}
Just don't start your function name with u.
well if we're thinking about lambdas as blocks++, then why not
name: {|a,b,c| }
Since we already have labeled blocks. This also slides neatly into an object literal
{ name: {|a,b,c| } }
Or does this horribly break parsing again?
oh, right, forgot e4x.
ok, one objection to (){} is that it looks too much like a function. in particular, it "feels like" I should write var a = (x){ return x; }; but that isn't right, {||} is sufficiently different that it "feels like" I should write var a = {|x| x};
-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Douglas Crockford ... because you can think of the \ as being an abbreviation of function.
\ name(a,b,c) {}
Just don't start your function name with u.
Exactly, that's why I didn't mention this possibility. I don't think it would be acceptable to have that \u ambiguity.
Smalltalk does not have a way (other than variable assignment) to name closures for recursive reference and it rarely has proven to be a problem. If you really want to pass a recursive lambda to a function g you could define it using a const: const fact = {|n| (n<=1) ? 1 : fact(n-1)* n}; g(fact) or more perversely: g({||const fact = {|n| (n<=1) ? 1 : fact(n-1)* n}; fact}() );
(the second statement of the outer lambda wouldn't be needed if const evaluates to the value of its initialization expression)
On Dec 1, 2008, at 2:32 PM, Allen Wirfs-Brock wrote:
{|a,b,c| ...} or (a,b,c) {...} or {(a,b,c) ...}
I could be happy with any of them and can find pros and cons with
each. I think the high order bit should be that a concise closure
syntax is possible and desirable. If we agree on that then we just
need to pick one.The use of \ slightly bothers me because it is takes a character
that now is exclusively used in the lexical (token) grammar
( Unicode escapes, string escapes, line continuations) and gives it
syntactic significance. This is probably not a fatal flaw but it
would mean that the lexical uses of \ become less visually
distinctive.Whether someone prefers the parameters inside or outside the braces
may be another symptom of whether they are focusing on control
abstraction or functional abstraction. With control abstraction you
use closures as "blocks" of code that form pieces of the abstraction
so it may seems natural from that perspective for the braces to
surround the entire "block". This is closer to the syntactic feel of
the built-in control statements. If you are building functional
abstractions then you are probably thinking about the closures as
functions so placing the formals before the body seems natural.
Can you give an example? Since ECMAScript's built-in control
structures already have special syntax, it's hard to make anything
look exactly like them.
Here's my best shot at an example. Let's pretend you have a
withOpenFile function that opens a file, does some work using the
resulting file handle (assuming there were such things in some host
extension), and guarantees the file handle will be closed whether on
normal exit or exception, but passing exceptions through. How would a
site of use look?
withOpenFile(fileName, { |handle| doSomething(handle); doSomethingElse(handle); });
withOpenFile(fileName, (handle) { doSomething(handle); doSomethingElse(handle); });
Both look a little odd but I'd say the latter resembles built-in
constructs like for..in a tiny bit more.
It's fairly common with control abstractions to use 0 argument
closures so the readability of {||...}, (){...}, and {()...} are
probably also worth considering. Ideally a 0 argument closure would
be written as {...} but resolution of the syntactic ambiguities
between object literal and such closures (particularly in the
presence of statement labels) seems too difficult to contend with.
Perhaps with the backslash idea, a 0-argument lambda could have a
short form of just \ {...}. I'm trying to think of a useful control
construct that takes thunks. Maybe implementing memoized lazy lists, a
la scheme, so you can do stuff like:
lazyPair(val1, \ { computeRestOfList(); } );
var fibonacciNumbers = \ { let fib = (a, b) { lazyPair(b, \ { fib(b, a + b) }) }; lazyPair(0, fib(0, 1)); }();
(I'm skipping the definition of lazyPair and the lazyCar / lazyCdr
you'd need to walk the stream).
Seems adequately readable.
For comparison:
lazyPair(val1, { || computeRestOfList(); } );
var fibonacciNumbers = { || let fib = { |a, b| lazyPair(b, { || fib(b, a + b) }) }; lazyPair(0, fib(0, 1)); }();
Maybe it's just lack of familiarity but the second version seems less
readable and natural to me.
My focus on supporting control abstraction may be mute. While
Smalltalk and Ruby show that power of such abstraction mechanisms it
takes more than just a concise block-like closure notation to
achieve it. The complexity of the BGGA Java closure proposal (and
the associated controversy) shows how hard it can be to fully
support control abstraction using C syntax (and, of course, Java
semantics).
I think it's possible to build useful control abstractions with a
concise block-like syntax. But an important factor here is making
these look natural in the context of the language as well as concise.
Ruby and Smalltalk have overall designs that make them friendly to a
smalltalkish block syntax, so your custom control constructs look just
like the built-in ones (or conversely you could claim none are built
in). In ECMAScript you can't have them look exactly the same, but I
think the Haskellish backslash style fits in a little better.
, Maciej
On Dec 1, 2008, at 5:37 PM, Allen Wirfs-Brock wrote:
-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Douglas Crockford ... because you can think of the \ as being an abbreviation of function.
\ name(a,b,c) {}
Just don't start your function name with u.
Exactly, that's why I didn't mention this possibility. I don't
think it would be acceptable to have that \u ambiguity.
I think requiring a space after the \ if a name is provided would
remove the potential ambiguity.
Smalltalk does not have a way (other than variable assignment) to
name closures for recursive reference and it rarely has proven to be
a problem. If you really want to pass a recursive lambda to a
function g you could define it using a const: const fact = {|n| (n<=1) ? 1 : fact(n-1)* n}; g(fact) or more perversely: g({||const fact = {|n| (n<=1) ? 1 : fact(n-1)* n}; fact}() );(the second statement of the outer lambda wouldn't be needed if
const evaluates to the value of its initialization expression)
g(\ fact(n) { (n<=1) ? 1 : fact(n-1) * n });
That seems sweeter.
On Mon, Dec 1, 2008 at 5:37 PM, Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com> wrote:
-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Douglas Crockford ... because you can think of the \ as being an abbreviation of function.
\ name(a,b,c) {}
Just don't start your function name with u.
Exactly, that's why I didn't mention this possibility. I don't think it would be acceptable to have that \u ambiguity.
I agree that the ambiguity is not acceptable. It is too confusing.
Peter
Breton Slivka wrote:
Is recursion still desirable in this form. If so, then of the three I like
(a,b,c) {}
because you can think of the \ as being an abbreviation of function.
\ name(a,b,c) {}
Just don't start your function name with u.
Too ambiguous, even with the space.
well if we're thinking about lambdas as blocks++, then why not
name: {|a,b,c| }
Since we already have labeled blocks.
Because '{|a,b,c| }' is an expression, and labelling an ExpressionStatement already has a different meaning.
Below
-----Original Message----- From: Maciej Stachowiak [mailto:mjs at apple.com] Can you give an example? Since ECMAScript's built-in control structures already have special syntax, it's hard to make anything look exactly like them.
Here's my best shot at an example. Let's pretend you have a withOpenFile function that opens a file, does some work using the resulting file handle (assuming there were such things in some host extension), and guarantees the file handle will be closed whether on normal exit or exception, but passing exceptions through. How would a site of use look?
withOpenFile(fileName, { |handle| doSomething(handle); doSomethingElse(handle); });
withOpenFile(fileName, (handle) { doSomething(handle); doSomethingElse(handle); });
Both look a little odd but I'd say the latter resembles built-in constructs like for..in a tiny bit more.
Just so everybody knows where I'm coming from with this, in Smalltalk this might look something like:
FileHandle forName: filename do: [:handle| self dosomething: handle. Self doSomethingElse: handle]
Which is syntactically very similar to the "built-in" control constructs such as:
1 to: max do: [:n| self doSomething: n].
The BGGA Java closure proposal attempts to support control abstractions that look like built in control constructs by allowing trailing literal closure arguments to appear after the parenthesized argument list (shades of Ruby). If we did something similar in JavaScript you might then code your example as:
withOpenFile(fileName) { |handle| doSomething(handle); doSomethingElse(handle); };
which (to me) feels a little more like a built-in control structure than
withOpenFile(fileName) {handle) { doSomething(handle); doSomethingElse(handle); };
However, neither one is perfect. The Java closure proposal has some additional syntactic embellishments that attempt to make it even a bit closer but in my opinion they may have exceeded to point of diminishing returns.
...
I think it's possible to build useful control abstractions with a concise block-like syntax. But an important factor here is making these look natural in the context of the language as well as concise. Ruby and Smalltalk have overall designs that make them friendly to a smalltalkish block syntax, so your custom control constructs look just like the built-in ones (or conversely you could claim none are built in). In ECMAScript you can't have them look exactly the same, but I think the Haskellish backslash style fits in a little better.
I largely agree with this view. Preserving the existing feel of a language is very important and it is hard to retrofit control abstraction facilities into a language that didn't have them in its initial design. Neither the backslash or vertical bar syntax is a perfect fit with the existing language and I could live with either although I personally like the vertical bar's homage to Smalltalk.
On Mon, Dec 1, 2008 at 9:33 PM, Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com> wrote:
The BGGA Java closure proposal attempts to support control abstractions that look like built in control constructs by allowing trailing literal closure arguments to appear after the parenthesized argument list (shades of Ruby). If we did something similar in JavaScript you might then code your example as:
withOpenFile(fileName) { |handle| doSomething(handle); doSomethingElse(handle); };
I find the idea that a lambda comes after the argument list, but that it is somehow still an argument, an inconsistent and bad idea. I've used it in Ruby and don't like it there. In Ruby, I believe these trailing {||} "blocks" are the only place where the {||} syntax will create a lambda and so there is appeal to those who like the extremely compact notation. They cannot get that compact notation elsewhere. In ES we won't have that restriction.
One problem is if the argument list needs to take two lambdas, then where would the second lambda go? One inside the arguments list and one after? Why is one lambda more important or deserving of a special place compared with the other lambda?
Some API designers won't like the trailing lambda syntax and some will. Then we end up with inconsistent APIs. If all arguments go in the argument list then this problem will not arise.
The following is better
withOpenFile(fileName, {|handle| doSomething(handle); doSomethingElse(handle); });
and the following is better still
withOpenFile(fileName, lambda(handle) { doSomething(handle); doSomethingElse(handle); });
Peter
What about:
.(a,b,c) {}
or
..(a,b,c) {}
or
...(a,b,c) {}
,
Eric Suen
----- Original Message ---
i still prefer 'lambda (a,b,c) { ... }' as it is readable to the uninitiated and can then at least give a handle for someone to lookup.
On Dec 2, 2008, at 5:31 AM, Aaron Gray wrote:
i still prefer 'lambda (a,b,c) { ... }' as it is readable to the
uninitiated and can then at least give a handle for someone to lookup.
I think the truly uninitiated would not find "lambda" any more obvious
in meaning than "" or "||".
, Maciej
On Dec 2, 2008, at 5:03 AM, Eric Suen wrote:
What about:
.(a,b,c) {}
or
..(a,b,c) {}
or
...(a,b,c) {}
As long as we're giving the bikeshed a few more coats of paint,
Objective-C is adding block-like closures with ^ as the prefix, so we
could take inspiration from that:
^(a, b, c) { ... }
I'm not sure if this would introduce ambiguities in the grammar since
"^" is already an operator; I tend to think not, because "+" and "-"
manage to be both unary and binary operators without ambiguity. "^"
also has a slight resemblance to the greek lambda, which is the reason
Haskell uses "". This could support named lambdas without risk of
clashing with the \u escape.
, Maciej
On 2008-12-02, at 11:48EST, Maciej Stachowiak wrote:
As long as we're giving the bikeshed a few more coats of paint,
Objective-C is adding block-like closures with ^ as the prefix, so
we could take inspiration from that:^(a, b, c) { ... }
That's cute. Mnemonic for Λ (capital λ), also 'pointer-ish',
implying an object reference.
On Dec 2, 2008, at 5:31 AM, Aaron Gray wrote:
i still prefer 'lambda (a,b,c) { ... }' as it is readable to the uninitiated and can then at least give a handle for someone to lookup.
I think the truly uninitiated would not find "lambda" any more obvious in meaning than "" or "||".
People can google "lambda" they cannot google "", or "||". Also keywords seem better suited to Javascript syntax.
Aaron
'~' is ambiguities usless you using two NO_LINE_BREAK like:
'~' NO_LINE_BREAK (...) NO_LINE_BREAK {...}
'.' may not looks good, but it does not introduce new token like '' or 'lambda'
,
Eric Suen
----- Original Message ---
On 1 Dec, 2008, at 10:18 PM, Peter Michaux wrote:
One problem is if the argument list needs to take two lambdas, then where would the second lambda go? One inside the arguments list and one after? Why is one lambda more important or deserving of a special place compared with the other lambda?
I've been writing JS a long time and I don't frequently find myself
wanting to pass more than one function/lambda to another function. On
the (very) rare occasion when I do, I suspect I'm not calling a
control-structure-like function. Then I'd be comfortable passing two
lambdas as arguments.
Since the goal seems to be allowing control structures like Smalltalk
(yay!), how about specifying that one lambda that follows a function
invocation is passed as the final argument to the invocation. You can
write the invocation:
withNode(node, {|n| ... })
and I'll write it as:
withNode(node) { |n| ... }
Some API designers won't like the trailing lambda syntax and some will. Then we end up with inconsistent APIs.
I think if lambdas can be specified outside the parameter list, API
designers will feel pressure from those who use their APIs to adopt
the trailing lambda style. This would only be a significant issue when
the API is expecting a single lambda.
And regarding using the full term lambda in the syntax, I think I'm
not alone that I'd have a hard time adopting lambdas over functions
without a significant improvement in syntax -- and changing the name
from function to lambda isn't significant. Especially considering that
the initial implementations of lambdas might not hold a significant
performance improvement over functions.
Of course, it would also be super cool if I could write something like:
{ |n|
hide: function()
{
n.style.display='none';
},
show: function()
{
n.style.display='';
},
etc...
}
Of course if I didn't have to pay the cost of instantiating a new
function for every object instance, that would make something like
this worthwhile.
2008/12/2 Jeff Watkins <watkins at apple.com>:
Since the goal seems to be allowing control structures like Smalltalk (yay!), how about specifying that one lambda that follows a function invocation is passed as the final argument to the invocation.
Brendan seemed to reject the idea that "this" could be passed as the first argument to a constructor, so that "this" could be explicitly named, on the grounds that the parameter lists would not match. I agree that parameter lists should match. I was just asking.
esdiscuss/2008-November/008203
If a trailing block outside the parameter list is passed as the last argument, then what happens when there is a rest parameter as the last argument in the parameter list? It gets a bit messy to determine if the last argument passed in was the last argument of some rest parameters or an optional trailing lambda.
Peter
On 2 Dec, 2008, at 10:28 AM, Peter Michaux wrote:
If a trailing block outside the parameter list is passed as the last argument, then what happens when there is a rest parameter as the last argument in the parameter list? It gets a bit messy to determine if the last argument passed in was the last argument of some rest parameters or an optional trailing lambda.
Er, yeah. That's a really good point. I suppose you could bake that
into the specification, but I think that way lies madness.
On Tue, Dec 2, 2008 at 10:36 AM, Aaron Gray <aaronngray.lists at googlemail.com
wrote:
On Dec 2, 2008, at 5:31 AM, Aaron Gray wrote:
i still prefer 'lambda (a,b,c) { ... }' as it is readable to the
uninitiated and can then at least give a handle for someone to lookup.
I think the truly uninitiated would not find "lambda" any more obvious in meaning than "" or "||".
People can google "lambda" they cannot google "", or "||". Also keywords seem better suited to Javascript syntax.
Aaron
Is the argument that the uninitiated should be able understand complex code concepts via a google search of the thing that they don't understand?
There are already all kinds of things that one couldn't understand via a simple google search for something in the code, and which people of various levels of experience might experience: Object literals come to mind or "||" and "&&" as guard/default, "?" as ternary if, or even "++" or "%=" and there are plenty more things which might or might not have a readily identifiable thing to allow someone to google for like default values, coercion rules, or scoping concepts.
Google is an excellent tool, but I don't think that it's necessarily a great way to interpret code into something understandable to the uninitiated. Doesn't seem like a good deciding factor.
Peter Michaux wrote:
2008/12/2 Jeff Watkins <watkins at apple.com>:
Since the goal seems to be allowing control structures like Smalltalk (yay!), how about specifying that one lambda that follows a function invocation is passed as the final argument to the invocation.
Brendan seemed to reject the idea that "this" could be passed as the first argument to a constructor, so that "this" could be explicitly named, on the grounds that the parameter lists would not match. I agree that parameter lists should match. I was just asking.
esdiscuss/2008-November/008203
If a trailing block outside the parameter list is passed as the last argument, then what happens when there is a rest parameter as the last argument in the parameter list? It gets a bit messy to determine if the last argument passed in was the last argument of some rest parameters or an optional trailing lambda.
Will ES-Harmony have labelled arguments? If it does then both of these problems go away, since the block argument can have a standard label, and similarly for the this argument.
On Tue, Dec 2, 2008 at 11:09 AM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Peter Michaux wrote:
2008/12/2 Jeff Watkins <watkins at apple.com>:
Since the goal seems to be allowing control structures like Smalltalk (yay!), how about specifying that one lambda that follows a function invocation is passed as the final argument to the invocation.
Brendan seemed to reject the idea that "this" could be passed as the first argument to a constructor, so that "this" could be explicitly named, on the grounds that the parameter lists would not match. I agree that parameter lists should match. I was just asking.
esdiscuss/2008-November/008203
If a trailing block outside the parameter list is passed as the last argument, then what happens when there is a rest parameter as the last argument in the parameter list? It gets a bit messy to determine if the last argument passed in was the last argument of some rest parameters or an optional trailing lambda.
Will ES-Harmony have labelled arguments? If it does then both of these problems go away, since the block argument can have a standard label, and similarly for the this argument.
(David, These comments aren't particularly in response to yours but to the trailing lambda idea in general.)
This would lead to a "reserved label" which is adding yet another complication to enabled a trailing lambda after the parameter list.
The language already has a nice consistent way to pass parameters to a function: the parameters go inside the parameter list. Why add new syntax, a trailing lambda, basically for the sake of whim, with know consequences which require other changes to the language and likely unknown consequences, when the language can already do what is necessary? Using the currently available mechanism is something people already understand and works. The only difference is an LPAREN is in a different place. Is that worth all the problems and inconsistencies? I don't think so. I don't even think it is even aesthetically appealing which is supposedly the only real gain anyway.
Peter
Maciej Stachowiak wrote:
As long as we're giving the bikeshed a few more coats of paint, Objective-C is adding block-like closures with ^ as the prefix, so we could take inspiration from that:
^(a, b, c) { ... }
I'm not sure if this would introduce ambiguities in the grammar since "^" is already an operator; I tend to think not, because "+" and "-" manage to be both unary and binary operators without ambiguity.
Correct, it would be grammatically unambiguous. It might still be mildly confusing, though (unlike unary minus, which is related to subtraction by -x = 0-x, lambda abstraction is unrelated to bitwise exclusive-or). But any notation for lambdas is likely to be mildly confusing to the uninitiated.
"^" also has a slight resemblance to the greek lambda, which is the reason Haskell uses "".
"^" also has some historical relevance:
Felice Cardone, J. Roger Hindley, History of Lambda-calculus and Combinatory Logic 2006, Swansea University Mathematics Department Research Report No. MRRS-05-06. www-maths.swan.ac.uk/staff/jrh/papers/JRHHislamWeb.pdf
(By the way, why did Church choose the notation "λ"? In [Church, 1964, §2]
he stated clearly that it came from the notation "x̂" used for
class-abstraction by Whitehead and Russell, by first modifying "x̂" to
"∧x" to distinguish function-abstraction from class-abstraction, and
then changing "∧" to "λ" for ease of printing. This origin was also
reported in [Rosser, 1984, p.338]. On the other hand, in his later years
Church told two enquirers that the choice was more accidental: a symbol
was needed and "λ" just happened to be chosen.)
[Church, 1964] A. Church, 7 July 1964. Unpublished letter to Harald Dickson.
[Rosser, 1984] J. B. Rosser. Highlights of the history of the lambda calculus. Annals of the History of Computing, 6:337–349, 1984.
[If your email client doesn't display the above correctly: "x̂" is an x with a hat operator (similar to a circumflex) above it. "∧" is an upward-pointing wedge. "λ" is a lowercase Greek lambda.]
I've been casually following this discussion, and I can't help but rail against the inherent limitations of ES's C heritage.
On Tue, Dec 2, 2008 at 4:08 PM, Yuh-Ruey Chen <maian330 at gmail.com> wrote:
(As a side note, sometimes I wonder if all this talk about sugar is moot, because I suspect more and more languages will start compiling down to ES.)
I keep a list of all the X->ES3 compilers I encounter in the middle of
the following page
peter.michaux.ca/articles/compiling-down-to-javascript-1-5
This list has grown quite long with the majority of popular and revered languages included. I wonder how many companies are using these technologies and if that group is growing very quickly. ES is "good enough" for most developers that I think the attraction is low given the extra difficulties in development process and compiled code speed (i.e. trampolines for tail calls are slow.)
If ES is the byte code of the browser then the better ES is, the better the compiled code can be. Tail calls and continuations would be valuable additions to ES. Threads are probably a trickier issue. If these features cannot be added to ES directly then it would be great if ES ran on a VM which exposed these features. Then all languages could compile to that VM's byte code. I vaguely recall a web page where Brendan Eich listed some of these features as part of JavaScript 3 (perhaps ES5). I can't find the page at the moment. The really unfortunate part is the time lines to reach such goals are likely very long...available in 95% of browsers in the year 2020? Who knows?
Peter
I really don't think prefix is neccessary, what about:
(a;b;c) {...}
or
(a:b:c) {...}
2nd one may have problem with '::' or TypedIdentifier in ES4.
,
Eric Suen
----- Original Message ---
On Dec 2, 2008, at 6:26 PM, Eric Suen wrote:
I really don't think prefix is neccessary, what about:
(a;b;c) {...}
or
(a:b:c) {...}
2nd one may have problem with '::' or TypedIdentifier in ES4.
This loses almost all connection with the rest of the language.
Arguments are passed in a comma separated list. Wrapped in
parentheses. The Smalltalk hommage loses the parens but keeps the
commas. Any other separator is just wrong, sorry.
C# uses (a, b, c) => ... but in JS the comma operator makes that nasty
to parse top-down. I think the only candidates have to be of the form
^(a, b, c) {...}
(^ could be another character, but it seems to beat \ as others have
noted), or else the Smalltalky
{ |a, b, c| ... }
At this point we need a bake-off, or a convincing argument against the
unusual vertical bar usage.
On Dec 2, 2008, at 8:57 PM, Brendan Eich wrote:
C# uses (a, b, c) => ... but in JS the comma operator makes that
nasty to parse top-down. I think the only candidates have to be of
the form^(a, b, c) {...}
(^ could be another character, but it seems to beat \ as others have
noted), or else the Smalltalky{ |a, b, c| ... }
At this point we need a bake-off, or a convincing argument against
the unusual vertical bar usage.
How's this for a strawman: the choice is to follow either Objective-C
or Smalltalk. Given that Objective-C and JS share syntactical roots
in C, it makes more sense to follow the Objective-C precedent.
On Tue, Dec 2, 2008 at 7:11 PM, Neil Mix <nmix at pandora.com> wrote:
How's this for a strawman: the choice is to follow either Objective-C or Smalltalk.
Or Scheme which, after all, was the first Lisp with lexical scoping and first-class lambdas.
Peter
2008/12/2 Brendan Eich <brendan at mozilla.com>
This loses almost all connection with the rest of the language. Arguments are passed in a comma separated list. Wrapped in parentheses. The Smalltalk hommage loses the parens but keeps the commas. Any other separator is just wrong, sorry.
C# uses (a, b, c) => ... but in JS the comma operator makes that nasty to parse top-down. I think the only candidates have to be of the form
^(a, b, c) {...}
(^ could be another character, but it seems to beat \ as others have noted), or else the Smalltalky
{ |a, b, c| ... }
At this point we need a bake-off, or a convincing argument against the unusual vertical bar usage.
/be
The double bar syntax looks ok except when there are no arguments since it then looks like logical or which is confusing to my eyes. And when there is just a space between them, it looks awkward as well.
I like the idea of a short prefix (shorter than lambda), but there aren't any single character symbols that don't already have meaning somewhere else it seems.
With these two points in mind, I'd like to propose using the SML syntax for lambdas. This introduces a new keyword but it is only two characters and I think it better describes what is going on than ^ or some other character. There are probably other short 2 letter keywords that would work as well.
In that case, some binary operator are ok to use and no ambiguities,
like:
^ (a,b,c) { ... } & (a,b,c) { ... } % (a,b,c) { ... } | (a,b,c) { ... }
Eric Suen wrote:
I really don't think prefix is neccessary, what about:
(a;b;c) {...}
or
(a:b:c) {...}
Not compatible:
(a) {...}
A semicolon would be inserted after "(a)" in ES3, and adding the above construct would change that.
Besides, prefixes are good: they allow a human reader to see precisely what construct is coming next when reading strictly left-to-right, without having to look ahead.
Brendan Eich wrote:
C# uses (a, b, c) => ... but in JS the comma operator makes that nasty to parse top-down. I think the only candidates have to be of the form
^(a, b, c) {...}
(^ could be another character, but it seems to beat \ as others have noted), or else the Smalltalky
{ |a, b, c| ... }
At this point we need a bake-off, or a convincing argument against the unusual vertical bar usage.
Here's a possible technical issue that might not apply to ES: Ruby blocks params can't have default arguments according to eigenclass.org/hiki.rb?Changes+in+Ruby+1.9#l9 :
The new syntax allows to specify default values for block arguments,
since
{|a,b=1| ... }
is said to be impossible with Ruby's current LALR(1) parser, built
with bison.
In feature(ES4), ES may have OptionalParameter, that will cause trouble
{ |a ,b = 1 | c | d | e | f }
is
{ (|a ,b = 1 |) c | d | e | f }
or
{ (|a ,b = 1 | c) | d | e | f }
,
Eric Suen
----- Original Message ---
On 2008-12-02, at 20:32EST, Peter Michaux wrote:
I keep a list of all the X->ES3 compilers I encounter in the middle of the following page
Clarification on:
OpenLaszlo is the opposite direction: JavaScript compiled into Flash.
OL compiles an XML language to a subset of ECMAScript4 (or ECMAScript3
with the more mainstream parts of ES4: classes, type declarations,
parameter defaults, rest args, etc.) and has multiple back-ends that
compile that ECMAScript4 subset to (for example) JavaScript,
ActionScript 2, or ActionScript 3. So, in a sense, we use Javascript
as both an intermediate language and as an assembly language.
If I understand the proposals correctly, then of the following e4x:
var doc = {{|a,b,c|...}(d,e,f)}
var doc = {^(a,b,c){...}(d,e,f)}
var doc = {{||...}()}
var doc = {^(){...}()}
I honestly don't see the clarity of one over the other especially if someone writes larger, asinine e4x.
At least with the vertical bars it has similarities/inspiration from other languages as mentioned. This may be important for those migrating
from one of those languages. When I first saw ^(){...}, the vision that came to my head was the pointer operator in Visual C++
2008/12/2 Brendan Eich <brendan at mozilla.com>:
I think the only candidates have to be of the form ^(a, b, c) {...} (^ could be another character, but it seems to beat \ as others have noted),
Was there a problem with \ other than the fact that it can't easily be turned into a binding form, à la
\userFn() {...}
... due to the \u ambiguity? Because the Smalltalk-like syntax doesn't have an obvious binding form either.
{ userFn |a, b, c| ... }
is potentially already valid ES3, depending on the contents of '...' .
Eric Suen wrote:
In feature(ES4), ES may have OptionalParameter, that will cause trouble
{ |a ,b = 1 | c | d | e | f }
is
{ (|a ,b = 1 |) c | d | e | f }
or
{ (|a ,b = 1 | c) | d | e | f }
,
Eric Suen
That should be a syntax error. Parenthesis should be required in that case to avoid ambiguity: {|a, b = (1 | c)| ... }
In that case, you have to rewrite the grammar from:
OptionalParameter ::= Parameter '=' NonAssignmentExpression[allowIn]
to
OptionalParameter ::= Parameter '=' NonAssignmentExpression[allowIn,noOR]
Make it necessary complicated.
Good point -- you scored a direct hit on the Smalltalk-homage
battleship here, IMHO.
Despite my nostalgic longing for Smalltalk, I agree ;). But since the purpose is to be friendlier to control abstraction patterns, it remains important to have a very lightweight syntax for the no-parameter (thunk) case. The parameter list between the ^ and the { should be optional.
Btw, I see no reason to allow a name after the ^, so I agree the name issue by itself doesn't argue against . But my eyes just like ^ better.
2008/12/3 Brendan Eich <brendan at mozilla.com>
Brendan Eich wrote:
Allen Wirfs-Brock put his proposal, which will not shock you who know Smalltalk or Allen, on the whiteboard:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
Can't resist a historical note (which I haven't seen mentioned, but perhaps I missed something): the Clipper language[1] in its version 5.0, circa 1990, used this exact syntax for its "code block" data type, which were in fact lexically scoped lambdas.[2]
Not surprisingly, Clipper adapted its code block syntax from Smalltalk. Curly brackets were used instead of Smalltalk's square brackets, presumably to fit better with existing Clipper syntax, which used curly brackets for array definitions like {1,2,3} and square brackets for array subscripts like x[2].
Googling for "clipper code block" turns up various pages with example uses. Unfortunately, the examples that come up are all quite simple ones, but code blocks, being lambdas, were capable of much more.
Anton
[1] Clipper was originally a compiler for Ashton-Tate's dBASE database system/language, and was developed by the now-defunct Nantucket Corporation. It was ultimately sold to Computer Associates.
[2] The terminology surrounding lambdas was unfamiliar to most Clipper programmers at the time - the Clipper community referred to captured lexical variables as "detached locals". Googling for that exact term will turn up various examples of its use in the context of Clipper and its descendants such as Harbour and xHarbour.
If we are voting on the color of paint to use, I sure like:
^(a, b, ...) { ... }
- ^ is mnemonic for Λ (Greek capital lambda)
- ^ is mnemonic for 'pointer' so I am reminded that I am creating and
object here - () keeps parameters in a parameter list so I don't have to learn a
new syntax (as I might with any other delimiter)
- prefix ^ might be confused with the infix operator of the same name
On 12/2/08 7:11 PM, "Neil Mix" <nmix at pandora.com> wrote:
How's this for a strawman: the choice is to follow either Objective-C or Smalltalk. Given that Objective-C and JS share syntactical roots in C, it makes more sense to follow the Objective-C precedent.
I find Objective-C's syntax to be its weakest point, so I'm not sure I think either one of these is a good idea.
My question is whether a new keyword (or token) is needed for lambda at all. Can't the existing "function" keyword be repurposed here?
Since one of the issues brought up was the issue of readability, I figured it was fitting to mention that in the context of E4X code the new syntax could potentially be even more difficult to discern due to the double nested braces depending on the details, whether it be the examples I gave or one where it could be generated as such:
var doc = <{{|a,b,c|...}(d,e,f)}><foo>{{|a,b,c|...}(d,e,f)}</foo></{{|a,b,c|...}(d,e,f )}>
One would obviously hope never to see something like that, but if its legal syntax I wouldn't put it past someone to try (in a larger document perhaps) vice a named function call
On Dec 3, 2008, at 10:45 AM, Steven Johnson wrote:
On 12/2/08 7:11 PM, "Neil Mix" <nmix at pandora.com> wrote:
How's this for a strawman: the choice is to follow either Objective-C or Smalltalk. Given that Objective-C and JS share syntactical roots in C, it makes more sense to follow the Objective-C precedent.
I find Objective-C's syntax to be its weakest point, so I'm not sure
I think either one of these is a good idea.My question is whether a new keyword (or token) is needed for lambda
at all. Can't the existing "function" keyword be repurposed here?
How? ES3 already defines function expressions as well as definitions.
They are not the lambdas we seek.
2008/12/3 P T Withington <ptw at pobox.com>
If we are voting on the color of paint to use, I sure like: ^(a, b, ...) { ... }
- ^ is mnemonic for Λ (Greek capital lambda)
- ^ is mnemonic for 'pointer' so I am reminded that I am creating and object here
- () keeps parameters in a parameter list so I don't have to learn a new syntax (as I might with any other delimiter)
- prefix ^ might be confused with the infix operator of the same name
Though use of the infix ^ is so rare that I bet many JS users don't even know about it. The first article found when googling "javascript operators" (w3schools.com) doesn't even mention it.
2008/12/3 P T Withington <ptw at pobox.com>:
- prefix ^ might be confused with the infix operator of the same name
With semicolon insertion, isn't this a bigger problem?
The opening brace will need to be on the same line as the formals, otherwise the syntax is ambiguous:
^(x) { x = x * x ^(a,b,c,d,e,f,g) { x } }
And, if it is on the same line, it's still bad for a top-down parser:
^(x) { x = x * x ^(a,b,c,d,e,f,g) {x} }
Will semicolon insertion be illegal inside a lambda body?
On Dec 2, 2008, at 6:57 PM, Brendan Eich wrote:
This loses almost all connection with the rest of the language.
Arguments are passed in a comma separated list. Wrapped in
parentheses. The Smalltalk hommage loses the parens but keeps the
commas. Any other separator is just wrong, sorry.C# uses (a, b, c) => ... but in JS the comma operator makes that
nasty to parse top-down. I think the only candidates have to be of
the form^(a, b, c) {...}
(^ could be another character, but it seems to beat \ as others have
noted), or else the Smalltalky{ |a, b, c| ... }
At this point we need a bake-off, or a convincing argument against
the unusual vertical bar usage.
As a potential intuition pump, here's an example of some existing
built-in JS control structures, and how one would write the equivalent
with either form of lambda syntax. I am assuming the ^ version allows
omitting the parameter list when empty. They are formatted to look as
much as practical like the standard syntax.
if (x < 3) { handleSmallNum(x); } else { handleLargeNum(x); }
if_ (^{x < 3}, ^{ handleSmallNum(x); }, ^{ handleLargeNum(x); });
if_ ({ || x < 3}, { || handleSmallNum(x); }, { || handleLargeNum(x); });
while (x != null) { x.process(); x = x.next(); }
while_ (^{x != null}, ^{ x.process(); x = x.next(); });
while_ ({ || x != null}, { || x.process(); x = x.next(); });
for (var i = 1; i < 10; i++) { total += vec[i]; }
for_ (^{var i = 1}, ^{i < 10}, ^{i++}, ^{ total += vec[i]; });
for_ ({ || var i = 1}, { || i < 10}, { || i++}, { || total += vec[i]; });
for (var prop in obj) { props.push(prop); }
forIn_ (obj, ^(prop) { props.push(prop); });
forIn_ (obj, { |prop| props.push(prop); });
Here's examples with some built in JS functions that take function
arguments:
arr.sort(function (a, b) { return aa < bb; }); arr.sort(^(a, b) { aa < bb }); arr.sort({ |a, b| aa < bb });
arr.map(function (x) { return x * (x - 1); }); arr.map(^(x) { x * (x-1)}); arr.map({ |x| x * (x-1) });
function doubleBs(str) {
return str.replace(/b*/, function (substr) { return substr +
substr; });
}
function doubleBs(str) { str.replace(/b*/, ^(substr) { substr + substr }); }
function doubleBs(str) { str.replace(/b*/, { |substr| substr + substr }); }
I like the ^ solution better because:
- It makes parameter lists look like function parameter lists and
call argument lists. - It separates the parameter from the body more clearly, which seems
more readable, especially when there lambda is passed to something
that also takes other arguments. - ^{ ... } seems like a less visually noisy way to express a 0- argument thunk than { || ... }
, Maciej
Summary of what I've read:
The syntax (){} has a named lambda problem if the lambda name starts with a 'u'. Depending on whitespace between the backslash and the identifier seems like it will cause bugs
\u03c0(){} \ u03c0(){}
The syntax ^(){} has a semicolon insertion ambiguity. What does the following mean?
x = x ^(){}
The {||} syntax has an optional parameters problem. What does the following mean?
{|a, b = 1 | c | d}
The other suggestion which seems to be on the table has been a new keyword "lambda" or something shorter.
lambda() {} lmbd() {} ld() {}
Any parsing problems are already ones programmers know how to work around. The only whinge has been that "lambda" is too long. Changing from the 6 character "lambda" keyword to something shorter would work technically. I think just a one character keyword would be too short as they are commonly used for loop variables. I think the sometimes used "fn" can't be used because "function" is already taken. Both "lmbd" or "ld" are abbreviations which are not in the character of ES language keywords. "var" is the only keyword abbreviation in ES3. All other keywords are complete words and the word for the concept desired here is "lambda".
Peter
Yuh-Ruey Chen wrote:
Brendan Eich wrote:
C# uses (a, b, c) => ... but in JS the comma operator makes that nasty to parse top-down. I think the only candidates have to be of the form
^(a, b, c) {...}
(^ could be another character, but it seems to beat \ as others have noted), or else the Smalltalky
{ |a, b, c| ... }
At this point we need a bake-off, or a convincing argument against the unusual vertical bar usage.
Here's a possible technical issue that might not apply to ES: Ruby blocks params can't have default arguments according to eigenclass.org/hiki.rb?Changes+in+Ruby+1.9#l9 :
The new syntax allows to specify default values for block arguments, since {|a,b=1| ... } is said to be impossible with Ruby's current LALR(1) parser, built with bison.
That Ruby 1.9 page also lists yet another possible syntax:
->(a, b, ...) {...}
Using Maciej's examples:
if_ (->{x < 3}, ->{ handleSmallNum(x); }, ->{ handleLargeNum(x); });
while_ (->{x != null}, ->{ x.process(); x = x.next(); });
for_ (->{var i = 1}, ->{i < 10}, ->{i++}, ->{ total += vec[i]; });
forIn_ (obj, ->(prop) { props.push(prop); });
arr.sort(->(a, b) { aa < bb });
arr.map(->(x) { x * (x-1)});
function doubleBs(str) { str.replace(/b*/, ->(substr) { substr + substr }); }
The control abstractions just don't look right, regardless of which lambda syntax we choose. I'd rather wait for a more powerful macro system, instead of choosing the syntax based off how it would look in control abstractions.
2008/12/3 Yuh-Ruey Chen <maian330 at gmail.com>:
The control abstractions just don't look right, regardless of which lambda syntax we choose. I'd rather wait for a more powerful macro system, instead of choosing the syntax based off how it would look in control abstractions.
I agree completely.
Peter
This may be a stupid question, but is the current let expression syntax defined in JavaScript 1.7 too fundamentally different from the sought out lambda expression to be repurposed? Or would this wreak havoc on current uses?
On Thu, Dec 4, 2008 at 9:28 AM, Michael <Michael at lanex.com> wrote:
This may be a stupid question, but is the current let expression syntax defined in JavaScript 1.7 too fundamentally different from the sought out lambda expression to be repurposed? Or would this wreak havoc on current uses?
I had kind of a similar thought about let statements. Would it be possible to simply turn a let statement into an assignable/callable value? Would that just break too many things? would it get us at least close to the lambda functionality we seek?
Just so there is no confusion on meaning, I'm not referring to let statements nor let definitions but to this portion in particular: developer.mozilla.org/en/New_in_JavaScript_1.7#let_expressions
First, let expressions as implemented in JS1.7 are ambiguous in a
bottom up grammar. See
Second, let statements and expressions in JS1.7 are evaluated when
reached. They are not implicitly quoted for later application.
Third, let is the wrong keyword if there's no need to bind a name --
as there indeed is no need with many (most?) lambda use-cases.
2008/11/30 Brendan Eich <brendan at mozilla.com>:
At the TC39 meeting two weeks ago in Kona, we had a brief bikeshedding discussion about lambda syntax and why it matters.
Has anybody given any thought to the C# (ECMA TC49) syntax?
(input parameters) => {statement;}
where the parens are optional if there is only one parameter, and the braces are optional if the statement is a simple expression. Example:
(a,b) => a+b
More specifics here:
msdn.microsoft.com/en-us/library/bb397687.aspx
- Sam Ruby
On Dec 3, 2008, at 4:06 PM, Sam Ruby wrote:
2008/11/30 Brendan Eich <brendan at mozilla.com>:
At the TC39 meeting two weeks ago in Kona, we had a brief
bikeshedding discussion about lambda syntax and why it matters.Has anybody given any thought to the C# (ECMA TC49) syntax?
Yes, it has come up on this list. Head of thread message is:
In JS, the C# syntax creates an ambiguity with the comma expression in
the n-ary n > 1 case. Bottom-up parsers can cope; top-down have a
harder time changing their minds about what they are parsing when the
see the => after the parameter list. It can be handled either way,
though.
Having had several threads over a couple of years on this, my
impression is almost no one has championed an "infix" syntax such as
C#'s. The prefix crowd is split between those wanting lambda vs. a one-
char punctuator. The "postfix" (not accurate but you know what I mean)
position favors the Smalltalk homage.
On Dec 3, 2008, at 1:23 PM, Peter Michaux wrote:
Summary of what I've read:
The syntax (){} has a named lambda problem if the lambda name starts with a 'u'. Depending on whitespace between the backslash and the identifier seems like it will cause bugs
\u03c0(){} \ u03c0(){}
The syntax ^(){} has a semicolon insertion ambiguity. What does the following mean?
x = x ^(){}
I think this has only one possible meaning: an expression statement
consisting of an assignment of x to itself, followed by an expression
statement that creates an empty no-argument closure. Do you see
another possible meaning?
More generally, I can imagine cases that might actually be ambiguous
but I think they can be fixed in the same way that this is:
x = x +x
, Maciej
On Dec 3, 2008, at 6:18 PM, Maciej Stachowiak wrote:
x = x +x
That is equivalent to
x = x + x;
so the case with ^ should not differ. (Were you testing in an
interactive REPL?)
That the case Peter showed:
x = x ^(){}
cannot be parsed as a bitwise-xor expression doesn't help in general,
if we do not want to mandate bottom-up parsing (we don't).
On 12/03/2008 06:18 PM, Maciej Stachowiak wrote:
On Dec 3, 2008, at 1:23 PM, Peter Michaux wrote:
x = x ^(){}
I think this has only one possible meaning: an expression statement consisting of an assignment of x to itself, followed by an expression statement that creates an empty no-argument closure. Do you see another possible meaning?
According to the wording in E-262, that's a syntax error, and your second example:
x = x +x
Is equivalent to |x = x + x| because, when at the 2nd |x|, the next token is valid.
No,
^(x) is not a legal expression, so you don't have to make block in same line, no semicolon insertion will happens here.
see this post:
esdiscuss/2008-December/008296
----- Original Message ---
I suggest following grammar:
Lambda ::= '&' '(' Parameters ')' Block | '&' Block //if no parameters
I can comfire that this rule is no problem for a LALR(k) parser, ES4 is not LALR(1) anyway. and I think ^ is not good for eyes, it is too small and may confused with ~. & look more like C/C++ style
a = & { ... }
a = & (a,b) { ... }
On Wed, Dec 3, 2008 at 9:36 PM, Eric Suen <eric.suen.tech at gmail.com> wrote:
No,
^(x) is not a legal expression, so you don't have to make block in same line, no semicolon insertion will happens here.
You're looking at the wrong part of the example. Sorry -- the "wrapper" lambda is superfluous, and I should have left it out for clarity.
Using the GNU bracing style:
x = x * x ^(a,b,c,d,e,f,g) { x }
The above parses as an xor expression followed by a block, like so:
x = x * x ^ (a,b,c,d,e,f,g) { x } ... which is odd, to be sure, but perfectly legal.
The second example:
x = x * x ^(a,b,c,d,e,f,g) {x}
is not ambiguous, but it's unsuitable for top-down parsing. (I tried to underscore the point by using a long list of formals.) The parser has to get to the opening brace before it can determine that it isn't dealing with an xor expression.
Because it is a xor expression, for lambda, it will be
x = x * x ^ ^ (a,b,c,d,e,f,g) { x }
it is same for regexp,
x = x / x /i
is not regexp, but
x = x / /x/i
is regexp
seems that ^ will confuse lots of people
----- Original Message ---
It even can be writen as:
Lambda ::= '&' IdentifierOpt ParametersOpt Block
IdentifierOpt ::= %Empty | PropertyIdentifier
ParametersOpt ::= %Empty | '(' Parameters ')'
On Wed, Dec 3, 2008 at 9:52 PM, Eric Suen <eric.suen.tech at gmail.com> wrote:
Because it is a xor expression, for lambda, it will be
x = x * x ^ ^ (a,b,c,d,e,f,g) { x }
I don't understand what you're getting at. That's a syntax error. My example:
x = x * x ^(a,b,c,d,e,f,g) { x }
is not a syntax error, but it also (unfortunately) doesn't contain a lambda expression. Or am I missing something?
On Wed, Dec 3, 2008 at 10:00 PM, Jon Zeppieri <jaz at bu.edu> wrote:
On Wed, Dec 3, 2008 at 9:52 PM, Eric Suen <eric.suen.tech at gmail.com> wrote:
Because it is a xor expression, for lambda, it will be
x = x * x ^ ^ (a,b,c,d,e,f,g) { x }
I don't understand what you're getting at. That's a syntax error.
Sorry -- I take that back. It's not (or likely would not be) a syntax error, but I still don't understand its relevance.
Yes, it doesn't contain a lambda expression, just like:
a = x /x/i
is not same as:
a = x; /x/i
they both right but has different meaning...
On Wed, Dec 3, 2008 at 7:16 PM, Eric Suen <eric.suen.tech at gmail.com> wrote:
Yes, it doesn't contain a lambda expression, just like:
a = x /x/i
is not same as:
a = x; /x/i
they both right but has different meaning...
Is adding more confusion like this in the language desirable?
Peter
On Wed, Dec 3, 2008 at 10:16 PM, Eric Suen <eric.suen.tech at gmail.com> wrote:
Yes, it doesn't contain a lambda expression, just like:
a = x /x/i
is not same as:
a = x; /x/i
they both right but has different meaning...
Okay -- so we agree. In that case, it's clear that your proposed syntax:
&(a,b,c) {...}
has the same problem, right? Any valid ES3 infix operator will have the same problem, if we use it as a prefix lambda operator.
Why not using two version, one is for definition like:
Lambda name (a,b,c) { }
and for expression, you can use both, like:
a = lambda (a,b,c) { }
and
a = &(a,b,c) { }
My example:
x = x * x ^(a,b,c,d,e,f,g) { x }
is not a syntax error, but it also (unfortunately) doesn't contain a lambda expression. Or am I missing something?
Or a bit more obvious than the use of the comma-operator: As soon as named lambdas are introduced (the weak spot on the \ proposal), you'll get big problems with the ^ proposal, too. Consider the following snippet which is valid Javascript code, but certainly not a lambda expression.
var f = function (){return 8;} var x = 5 ^f(x) { x=x*x }
Would it work to move the parameter list inside the block (as in the
Smalltalk way, but as a regular parameter list, not using ||'s)?
{(a, b) a + b}
AFAICT, {(
is a syntax error for an expression in es3.
On 12/4/2008 3:15 PM, P T Withington wrote:
Would it work to move the parameter list inside the block (as in the Smalltalk way, but as a regular parameter list, not using ||'s)?
{(a, b) a + b}
AFAICT,
{(
is a syntax error for an expression in es3.
And if you just execute the lambda expression? For example the following code is legal Javascript:
var a = b = 0; {(a,b) a+b}(10, 5)
Would this form also be ambiguous and/or too difficult to parse?
{=> 9*9}() {a => a+b}(12) {(a,b) => a+b}(12,6)
On Dec 3, 2008, at 6:30 PM, Brendan Eich wrote:
On Dec 3, 2008, at 6:18 PM, Maciej Stachowiak wrote:
x = x +x
That is equivalent to
x = x + x;
so the case with ^ should not differ. (Were you testing in an
interactive REPL?)
I didn't test, I just knew this case must be disambiguated somehow and
didn't test which way. I don't think it matters much which way, since
you can avoid any such problems in your own code by using semicolons
for line endings.
That the case Peter showed:
x = x ^(){}
cannot be parsed as a bitwise-xor expression doesn't help in
general, if we do not want to mandate bottom-up parsing (we don't).
I think it would be fine for this case to be a syntax error.
, Maciej
On Dec 4, 2008, at 7:18 AM, Michael wrote:
Would this form also be ambiguous and/or too difficult to parse?
{=> 9*9}() {a => a+b}(12) {(a,b) => a+b}(12,6)
I imagine it would be problematic for a top-down parser because you
may have to parse an unbounded number of characters to determine if
the initial parameter list is in fact a parameter list or a comma
expression.
On Wed, Dec 3, 2008 at 7:25 PM, Jon Zeppieri <jaz at bu.edu> wrote:
Okay -- so we agree. In that case, it's clear that your proposed syntax:
&(a,b,c) {...}
has the same problem, right? Any valid ES3 infix operator will have the same problem, if we use it as a prefix lambda operator.
Welcome to the syntax races. "lambda" takes an early lead, but drops back because of too much weight. For a while, it's neck and neck between "||" and "^", with "" following closely and "fn", "&", and other trailing. Many old timers (including your commentator) are rooting for "||" because of its previous historic performances. But "||" trips up over ambiguities not present on its original track. "^" is now in the lead. Oh no! It trips on a different ambiguity. This track seems riddled with more ambiguities than any of these contenders have ever trained on. Seeing "^" stumble, "&" and other contenders saddled with "binary operator"ness, drop back and concede. "" has taken the lead....
2008/12/4 Mark S. Miller <erights at google.com>:
Welcome to the syntax races. "lambda" takes an early lead, but drops back because of too much weight. For a while, it's neck and neck between "||" and "^", with "" following closely and "fn", "&", and other trailing. Many old timers (including your commentator) are rooting for "||" because of its previous historic performances. But "||" trips up over ambiguities not present on its original track. "^" is now in the lead. Oh no! It trips on a different ambiguity. This track seems riddled with more ambiguities than any of these contenders have ever trained on. Seeing "^" stumble, "&" and other contenders saddled with "binary operator"ness, drop back and concede. "" has taken the lead....
I have my money on "lambda". I'm thinking it has the endurance necessary. It has already lasted longer in history than all the others.
Peter
On Dec 4, 2008, at 10:28 AM, Maciej Stachowiak wrote:
On Dec 4, 2008, at 7:18 AM, Michael wrote:
Would this form also be ambiguous and/or too difficult to parse?
{=> 9*9}() {a => a+b}(12) {(a,b) => a+b}(12,6)
I imagine it would be problematic for a top-down parser because you
may have to parse an unbounded number of characters to determine if
the initial parameter list is in fact a parameter list or a comma
expression.
Right -- especially if one includes destructuring parameters.
Typically a top-down cover grammar is parsed, and then disamiguated
based on right context after the AST is built, with any adjustments to
the AST encoding made retrospectively. This can get ugly.
Worse, as Waldemar pointed out, you can end up with a failure to
backtrack and find the valid sentential form that a bottom up parser
would find via shifting and reducing.
Combined, this says to me that the C# syntax is no-go for JS.
Yuh-Ruey Chen wrote:
Brendan Eich wrote:
C# uses (a, b, c) => ... but in JS the comma operator makes that nasty to parse top-down. I think the only candidates have to be of the form
^(a, b, c) {...}
(^ could be another character, but it seems to beat \ as others have noted), or else the Smalltalky
{ |a, b, c| ... }
At this point we need a bake-off, or a convincing argument against the unusual vertical bar usage.
Here's a possible technical issue that might not apply to ES: Ruby blocks params can't have default arguments according to eigenclass.org/hiki.rb?Changes+in+Ruby+1.9#l9 :
The new syntax allows to specify default values for block arguments, since {|a,b=1| ... } is said to be impossible with Ruby's current LALR(1) parser, built with bison.
There's an ambiguity here that is independent of what kind of parser is used (I don't know whether it is the same issue as in Ruby). Consider:
{|a=1|2, b|c}
Is the argument list (a = 1|2, b) with body {c}, or is it (a = 1) with body {2, b|c}?
This ambiguity could be resolved by restricting expressions used as default argument initialisers, but it is not clear why they should be restricted, given that other possible concrete syntaxes for lambda do not have this problem.
IMHO this does count as a "convincing argument against the unusual vertical bar usage", if we want default arguments (and I think we do).
However, I'd like to suggest that it may be premature to be deciding on the concrete syntax of lambda, without knowing its abstract syntax. (The issue that MarkM raised about unintentionally leaking a value that happens to be evaluated in tail position, for example, needs to be dealt with at the abstract syntax level.) The same point applies to the syntax of object literals.
That will require some notation for discussing abstract syntax, on which I'll start another thread.
Yuh-Ruey Chen wrote:
Eric Suen wrote:
In feature(ES4), ES may have OptionalParameter, that will cause trouble
{ |a ,b = 1 | c | d | e | f }
is
{ (|a ,b = 1 |) c | d | e | f }
or
{ (|a ,b = 1 | c) | d | e | f }
,
Eric Suen
That should be a syntax error. Parenthesis should be required in that case to avoid ambiguity: {|a, b = (1 | c)| ... }
Yuck. The restrictions on 'in' are bad enough; this is essentially the same kind of ambiguity in another context. Requiring expressions containing '|' to be parenthesized would impose the same degree of grammar complexity overhead again as that imposed by 'NoIn':
ArgumentInitialiser : = ExpressionNoBar = ( Expression )
... unless all default argument initialisers have to be parenthesized, but that is just ugly.
Jon Zeppieri wrote:
2008/12/3 P T Withington <ptw at pobox.com>:
- prefix ^ might be confused with the infix operator of the same name
With semicolon insertion, isn't this a bigger problem?
The opening brace will need to be on the same line as the formals, otherwise the syntax is ambiguous:
^(x) { x = x * x ^(a,b,c,d,e,f,g) { x } }
Strictly speaking, the syntax is not ambiguous; it just is not parsed how you might expect. The semicolons would be inserted in this example as follows:
^(x) { x = (x * x)^(a, b, c, d, e, f, g); { x; } };
Arguably, the problem here is that semicolon insertion is and always was a bad idea.
And, if it is on the same line, it's still bad for a top-down parser:
^(x) { x = x * x ^(a,b,c,d,e,f,g) {x} }
Same result as above.
Will semicolon insertion be illegal inside a lambda body?
That's worth considering. It does not prevent lambdas from being used to desugar other constructs, because semicolon insertion would be performed on the original program before desugaring.
On 2008-12-04, at 15:23EST, David-Sarah Hopwood wrote:
Arguably, the problem here is that semicolon insertion is and always was a bad idea.
<whinge>
That and not requiring whitespace around operators, thus taking away a
huge domain of possible multi-symbol names (such as := for
initialization/assignment to preclude the =/== trap, or say, )\ for λ,
and forcing camelCasing or carpal_tunnel_syndrome upon everyone who
prefers descriptive-symbol-names...)
</whinge>
On Thu, Dec 4, 2008 at 3:23 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Jon Zeppieri wrote:
And, if it is on the same line, it's still bad for a top-down parser:
^(x) { x = x * x ^(a,b,c,d,e,f,g) {x} }
Same result as above.
Actually, I think we're both wrong. If I'm reading the spec correctly, no semicolon would be inserted, and the whole thing would be a syntax error. The "offending token" here is '{', but it's not separated from the previous token -- namely, ')' -- by at least one LineTerminator.
At any rate, it's a problem.
Mark S. Miller wrote:
[...] "" has taken the lead....
There's still #, @, and ` (and of course keywords like lambda and fn). None of these are as mnemonic as , but they leave \ as a purely lexical escape character.
It's quite ironic that we are still limited, as Church was, in which characters we can use for the modern equivalent of "typographical reasons".
David-Sarah Hopwood wrote:
Jon Zeppieri wrote: [...]
The opening brace will need to be on the same line as the formals, otherwise the syntax is ambiguous:
^(x) { x = x * x ^(a,b,c,d,e,f,g) { x } }
Strictly speaking, the syntax is not ambiguous; it just is not parsed how you might expect. The semicolons would be inserted in this example as follows:
^(x) { x = (x * x)^(a, b, c, d, e, f, g); { x; } };
Arguably, the problem here is that semicolon insertion is and always was a bad idea.
And, if it is on the same line, it's still bad for a top-down parser:
^(x) { x = x * x ^(a,b,c,d,e,f,g) {x} }
Same result as above.
Sorry, not the same result. This would be formally a syntax error, although note that some implementations do perform semicolon insertion even at non-line-boundaries.
On Dec 4, 2008, at 12:52 PM, David-Sarah Hopwood wrote:
Sorry, not the same result. This would be formally a syntax error, although note that some implementations do perform semicolon insertion even at non-line-boundaries.
Yes, that bothers me (I'm feeling guilty here: I could use a
bugzilla.mozilla.org bug on file). But is it required for web interop?
If IE JScript does it and has since the old days, then the default
answer has to be "yes", and we should think about specifying the de-
facto standard.
On Fri, Dec 5, 2008 at 7:49 AM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Mark S. Miller wrote:
[...] "" has taken the lead....
There's still #, @, and ` (and of course keywords like lambda and fn). None of these are as mnemonic as , but they leave \ as a purely lexical escape character.
It's quite ironic that we are still limited, as Church was, in which characters we can use for the modern equivalent of "typographical reasons".
-- David-Sarah Hopwood
this may be a stupid question, but why? Is it really so impossible to have λ(a,b,c){} ? You guys seem to have no trouble typing it. It's not that much trouble to remap a key, and you can always keep lambda(a,b,c){} as a more verbose but more accessable alternative. IDEs could make a macro out of it so you wouldn't even have to bother with going to the trouble of remapping. Nearly all computers on the planet have a greek alphabet installed on them. And keep in mind, we're not designing a language for tomorrow. We're designing a language for 10 years from now. λ could be way more convenient to enter by then, particularly if it's in an upcoming spec for a programming language.
I admit this seems ludicrous at its face, but admittedly I have not really seen the arguments against λ as an abbreviated lambda syntax yet.
On Dec 4, 2008, at 2:31 PM, Breton Slivka wrote:
I admit this seems ludicrous at its face, but admittedly I have not really seen the arguments against λ as an abbreviated lambda syntax yet.
Not compatibly: ES3 already allows Unicode identifiers, including
Greek Lambda. Other Mathematical Lambda characters are not in the BMP:
www.mail-archive.com/[email protected]/msg15581.html
It's still too hard to type.
[oops, sent from the wrong address...]
2008/12/4 Breton Slivka <zen at zenpsycho.com>:
this may be a stupid question, but why? Is it really so impossible to have λ(a,b,c){} ?
Last time I brought this up, Brendan made fun of me on a podcast. :(
You guys seem to have no trouble typing it. It's not that much trouble to remap a key, and you can always keep lambda(a,b,c){} as a more verbose but more accessable alternative. IDEs could make a macro out of it so you wouldn't even have to bother with going to the trouble of remapping.
Exactly what I wrote then.
I admit this seems ludicrous at its face, but admittedly I have not really seen the arguments against λ as an abbreviated lambda syntax yet.
Well aside from the "random guy doesn't know how to map a key" problem (which is perfectly true), I could see some character set issues in the field.
On Thu, Dec 4, 2008 at 5:35 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Dec 4, 2008, at 2:31 PM, Breton Slivka wrote:
I admit this seems ludicrous at its face, but admittedly I have not really seen the arguments against λ as an abbreviated lambda syntax yet.
Not compatibly: ES3 already allows Unicode identifiers, including Greek Lambda.
Also including the word 'lambda' -- but that hasn't stopped it from being seriously considered.
On Fri, Dec 5, 2008 at 9:35 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Dec 4, 2008, at 2:31 PM, Breton Slivka wrote:
I admit this seems ludicrous at its face, but admittedly I have not really seen the arguments against λ as an abbreviated lambda syntax yet.
Not compatibly: ES3 already allows Unicode identifiers, including Greek Lambda. Other Mathematical Lambda characters are not in the BMP:
www.mail-archive.com/[email protected]/msg15581.html
It's still too hard to type.
/be
picasaweb.google.com/eileen.world.traveler/EileenBestOfGreece#5139474493916668850
For some reason I'm reminded of this quote:
"APL, in which you can write a program to simulate shuffling a deck of cards and then dealing them out to several players in four characters, none of which appear on a standard keyboard." David Given
On Dec 4, 2008, at 2:45 PM, Jon Zeppieri wrote:
2008/12/4 Breton Slivka <zen at zenpsycho.com>:
this may be a stupid question, but why? Is it really so impossible to have λ(a,b,c){} ?
Last time I brought this up, Brendan made fun of me on a podcast. :(
Not you personally! I hope that was at least a :-/ emoticon...
On Thu, Dec 4, 2008 at 5:35 PM, Brendan Eich <brendan at mozilla.com>
wrote:On Dec 4, 2008, at 2:31 PM, Breton Slivka wrote:
I admit this seems ludicrous at its face, but admittedly I have not really seen the arguments against λ as an abbreviated lambda syntax yet.
Not compatibly: ES3 already allows Unicode identifiers, including
Greek Lambda.Also including the word 'lambda' -- but that hasn't stopped it from being seriously considered.
True enough. And 'lambda' is likelier to be in use in web JS as an
identifier than λ, at a guess.
If we have to go to one character, though, I'd rather we use an ASCII
punctuation character, for the reasons given (hard to type, slight
incompatibility). But you λ fans need to help me here: how does one
type λ on a Mac laptop? How about on a standard Windows machine? Pick
a Linux and lay the clues on there, too.
2008/11/29 Brendan Eich <brendan at mozilla.com>:
At the TC39 meeting two weeks ago in Kona, we had a brief bikeshedding discussion about lambda syntax and why it matters.
Who would have thought a discussion about lambda syntax in JavaScript would go over 120 posts while a simultaneous thread about class syntax has had little attention outside a handful of posters?
Would this have been reverse 10 years ago?
Sign of the paradigm shift? Maybe folks want an immutable cons cells too?
Modern attention span?
Peter
On Thu, Dec 4, 2008 at 6:10 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Dec 4, 2008, at 2:45 PM, Jon Zeppieri wrote:
2008/12/4 Breton Slivka <zen at zenpsycho.com>:
this may be a stupid question, but why? Is it really so impossible to have λ(a,b,c){} ?
Last time I brought this up, Brendan made fun of me on a podcast. :(
Not you personally! I hope that was at least a :-/ emoticon...
Oops. You see the typographical limitations we're still saddled with? The mock-wounded :( and the actually-wounded :( aren't slated to have distinct code points until Unicode 17.
If we have to go to one character, though, I'd rather we use an ASCII punctuation character, for the reasons given (hard to type, slight incompatibility). But you λ fans need to help me here: how does one type λ on a Mac laptop? How about on a standard Windows machine? Pick a Linux and lay the clues on there, too.
I'm a lot more likely to do this within emacs (or an editor, in general) than at the system / window system level. Anyhow, ASCII punctuation is great, if we can settle on a candidate.
Brendan Eich wrote:
If we have to go to one character, though, I'd rather we use an ASCII punctuation character, for the reasons given (hard to type, slight incompatibility). But you λ fans need to help me here: how does one type λ on a Mac laptop? How about on a standard Windows machine? Pick a Linux and lay the clues on there, too.
you can add a greek keyboard to your input methods, and set up a kb shortcut to switch easily.
like, for mac osx: system preferences, international, input menu. enable greek keyboard. enable "show input menu in menu bar".
click on "keyboard shortcuts". in "input menu", enable "select the next input source", assign it a shortcut that doesn't conflict with anything you use, like maybe option-space.
then, to type lambda, type option-space until you're at the greek flag, then type lowercase-L (on u.s. qwerty),
windows is pretty similar to osx, it's in "regional and language options"
I think modern linux is also similar, but I'm not near one at the moment.
If you started to recap the history of this discussion, could you (or anybody else in the know) verbalize following things:
-
What is the difference between the function and the lambda? I am not talking about their syntax, I want to understand the semantic difference, if there is any.
-
Why is it important for a lambda to have an optional name? What's wrong with using a function, if we want a name? IMHO lambda should have the minimalistic syntax.
-
Why is it important to be able to specify parameter defaults in lambda? Again, it looks like an extra sugar to me that can be covered by a function with parameter defaults.
The reason I ask is a lot of discussion is going around "but if it has a name" and "but if it has a default". If it doesn't have a name I would be satisfied personally with (a, b) {...} --- it doesn't clash with anything. Or even with (a, b) expr.
Thanks,
Eugene
On Dec 4, 2008, at 5:44 PM, Eugene Lazutkin wrote:
If you started to recap the history of this discussion, could you (or anybody else in the know) verbalize following things:
- What is the difference between the function and the lambda? I am
not talking about their syntax, I want to understand the semantic difference, if there is any.
Please read
- Why is it important for a lambda to have an optional name?
It may not be.
What's wrong with using a function, if we want a name? IMHO lambda should
have the minimalistic syntax.
"Minimalistic" does not define itself. The question is what is the
minimal syntax given various constraints.
Church's Lambdas take one argument only. One can curry by hand. Why
isn't that the minimum minimum?
- Why is it important to be able to specify parameter defaults in lambda? Again, it looks like an extra sugar to me that can be
covered by a function with parameter defaults.
See
Also consider that default parameters are a convenience we want
lambdas to have if we believe functions should be avoided for much
lambda-coding by hand. The countervailing argument is that lambdas
have unintended completion value hazards, but Schemers and others
don't worry about these and would prefer not to have to run back to
functions and lose Tennent's Correspondence Principle every time
default parameters beckon.
The reason I ask is a lot of discussion is going around "but if it
has a name" and "but if it has a default". If it doesn't have a name I would be satisfied personally with (a, b) {...} --- it doesn't clash with anything. Or even with (a, b) expr.
You're right to question name to rescue , but trying to minimize
lambdas won't save all the proposed syntaxes. We're making progress in
finding some to be in trouble, if not fatally flawed.
I thought this might be the answer. It's clearly too much to ask of
all lambda-coders and would-be lambda-coders in the world.
My two cents, perhaps I'm wrong and the Schemers and others will
switch their kbd configs. Or the code generators will rise and
exterminate lambda-coding humans. But I doubt it.
On Fri, Dec 5, 2008 at 1:10 PM, Brendan Eich <brendan at mozilla.com> wrote:
I thought this might be the answer. It's clearly too much to ask of all lambda-coders and would-be lambda-coders in the world.
My two cents, perhaps I'm wrong and the Schemers and others will switch their kbd configs. Or the code generators will rise and exterminate lambda-coding humans. But I doubt it.
/be
That's why you'd map it to l <<tab>>
in your ide.
Also, you wouldn't be inconveniencing all lambda coders in the world. Only the ones without greek keyboards. Are there just not enough greek javascripters to matter?
On Fri, Dec 5, 2008 at 1:10 PM, Brendan Eich <brendan at mozilla.com> wrote:
I thought this might be the answer. It's clearly too much to ask of all lambda-coders and would-be lambda-coders in the world.
My two cents, perhaps I'm wrong and the Schemers and others will switch their kbd configs. Or the code generators will rise and exterminate lambda-coding humans. But I doubt it.
/be
approaching it from the other side of the question, it seems that people with german keyboards would have a similarly difficult time with the pipe character.
example: forums.macosxhints.com/archive/index.php/t-29410.html
It's the same issue with possibly any of the other symbols that have been discussed for the syntax. It doesn't really matter what you pick. If it's not "lambda", you're inconveniencing someone.
On Dec 4, 2008, at 6:39 PM, Breton Slivka wrote:
On Fri, Dec 5, 2008 at 1:10 PM, Brendan Eich <brendan at mozilla.com>
wrote:I thought this might be the answer. It's clearly too much to ask of
all lambda-coders and would-be lambda-coders in the world.My two cents, perhaps I'm wrong and the Schemers and others will
switch their kbd configs. Or the code generators will rise and exterminate lambda-coding humans. But I doubt it./be
That's why you'd map it to l <<tab>> in your ide.
I don't have an ide -- March has some, but they bode ill :-P.
Seriously, of course most users could figure out how to inject a Greek
Lambda, but add up all that effort imposed on probably thousands to
millions. It's an imposition. It is not a cost free good. Why is it so
important to use a non-ASCII character?
Also, you wouldn't be inconveniencing all lambda coders in the world. Only the ones without greek keyboards. Are there just not enough greek javascripters to matter?
Heh. While I would like to think so, I doubt it. But you never know...
Please read
There is a lot of discussion over whether it is necessary to introduce syntax sugar instead of a "lambda" keyword, but is there any remaining controversy over the semantics of lambdas in JavaScript, or is that considered settled at this point?
(To throw some more kerosene on the syntax fire, I would point out that "fun" for function nicely resembles "var" for variable:
var x = fun y z => y + z;
but it's not big deal :)
Best ,
Michael
No, "" worse than '^' or '&', why not use
function ^ Identifier ( parameters ) block for declaration
and use
^ IdentifierOpt ( parameters ) block for expression
ExpressionStatement ::= [lookahead !{ {, function, ^ }] CommaExpression
On Dec 4, 2008, at 7:09 PM, Michael Day wrote:
Hi Brendan,
Please read strawman:lambdas
There is a lot of discussion over whether it is necessary to
introduce syntax sugar instead of a "lambda" keyword, but is there
any remaining controversy over the semantics of lambdas in
JavaScript, or is that considered settled at this point?
I've created a bikeshedding monster, I admit. It was not entirely
misdirection on my part :-P.
The main contention about lambdas ignoring syntax is whether the
completion-value creates a hazard that needs to be treated somehow, or
even judged as fatal to the proposal.
(To throw some more kerosene on the syntax fire, I would point out
that "fun" for function nicely resembles "var" for variable:var x = fun y z => y + z;
but it's not big deal :)
Not bad but you lost the necessary (destructuring, default parameters)
parenthesized formal list.
I toyed with 'fun' instead of 'function' in 1995 but it would have
been a misfit in the Java-esque/C-like keyword set, even with 'var'
included.
It's not bad, even now, but it may be that something shorter, or at
least spelled different from any word derived from function (fun, fn)
-- precisely because lambdas as proposed are not like functions in
many ways: arguments, this, return, completion-value.
The main contention about lambdas ignoring syntax is whether the completion-value creates a hazard that needs to be treated somehow, or even judged as fatal to the proposal.
Completion value, like the last thing to be evaluated in the lambda? What exactly is the nature of the hazard?
(To throw some more kerosene on the syntax fire, I would point out that "fun" for function nicely resembles "var" for variable:
var x = fun y z => y + z;
but it's not big deal :)
Not bad but you lost the necessary (destructuring, default parameters) parenthesized formal list.
Right, an arguments list should still look like an arguments list:
var x = fun (y, z) => y + z
or with an identifier:
var x = fun fact(n) => (x < 2 ? 1 : n * fact(n-1))
I toyed with 'fun' instead of 'function' in 1995 but it would have been a misfit in the Java-esque/C-like keyword set, even with 'var' included.
In an alternate universe, you might have used 'method' for functions with a 'this' value, saving two characters and the name function for real functions :)
On Dec 4, 2008, at 7:45 PM, Michael Day wrote:
Hi Brendan,
The main contention about lambdas ignoring syntax is whether the
completion-value creates a hazard that needs to be treated somehow,
or even judged as fatal to the proposal.Completion value, like the last thing to be evaluated in the lambda?
What exactly is the nature of the hazard?
Functional programming favors using completion values -- function call
results propagate back up naturally this way. Chaining, filters, etc.
all work the way you want. Here's the Y combinator:
const fact = lambda(proc) { return lambda (n) { (n <= 1) ? 1 : n * proc(n-1); } }
const Y = lambda(outer) { const inner = lambda (proc) { outer(lambda (arg) { proc(proc)(arg); }); } inner(inner); }
print("5! is " + Y(fact)(5));
Adding return keywords just adds overhead, noise. One might even want
to get rid of the curly braces around lambda bodies, but the only way
to do it in the ES grammar and avoid ambiguity is to replace braces
with mandatory parentheses.
On the other hand, much JS on the web is imperative, and a lot uses a
mixed functional/imperative style. Often the last value in a function
is not the return value you want callers to be able to get, and with
functions all is well: falling off the end returns undefined.
But with lambdas, falling off the end returns the last statement's
completion value. This means people will have to write
lambda (secret) { compute(secret); void 0; }
and similar. Of course too few will remember to do this, so implicit
return values will tend to leak.
How severe a problem this might be is arguable, but we don't want to
gamble. We want user feedback based on trial implementations, and
other convincing evidence for or against.
On Dec 4, 2008, at 10:12 PM, Brendan Eich wrote:
On Dec 4, 2008, at 7:45 PM, Michael Day wrote:
Hi Brendan,
The main contention about lambdas ignoring syntax is whether the
completion-value creates a hazard that needs to be treated
somehow, or even judged as fatal to the proposal.Completion value, like the last thing to be evaluated in the
lambda? What exactly is the nature of the hazard?Functional programming favors using completion values -- function
call results propagate back up naturally this way. Chaining,
filters, etc. all work the way you want. Here's the Y combinator:const fact = lambda(proc) { return lambda (n) { (n <= 1) ? 1 : n * proc(n-1); }
D'oh -- I wrote return incorrectly there. That means, by Tennent's
Correspondence Principle, that if the above were embedded in a
function, the return would force control flow to return from the
function as well as the outer lambda (the one assigned to const fact),
and the return value would be the inner lambda.
This is the other hazard with lambdas. The program equivalences
Tennent's Correspondence Principle enables are good for refactoring,
but bad for thinkos like the above.
(Honest, I didn't do it on purpose!)
This is the other hazard with lambdas. The program equivalences Tennent's Correspondence Principle enables are good for refactoring, but bad for thinkos like the above.
It seems like most of the problems come from lambdas being able to contain statements as well as expressions. If there were only lambda expressions, I think they would be much easier to reason about, but currently there is no way to embed loops in expressions, right?
On Dec 4, 2008, at 10:27 PM, Brendan Eich wrote:
On Dec 4, 2008, at 10:12 PM, Brendan Eich wrote:
On Dec 4, 2008, at 7:45 PM, Michael Day wrote:
Hi Brendan,
The main contention about lambdas ignoring syntax is whether the
completion-value creates a hazard that needs to be treated
somehow, or even judged as fatal to the proposal.Completion value, like the last thing to be evaluated in the
lambda? What exactly is the nature of the hazard?Functional programming favors using completion values -- function
call results propagate back up naturally this way. Chaining,
filters, etc. all work the way you want. Here's the Y combinator:const fact = lambda(proc) { return lambda (n) { (n <= 1) ? 1 : n * proc(n-1); }
D'oh -- I wrote return incorrectly there. That means, by Tennent's
Correspondence Principle, that if the above were embedded in a
function, the return would force control flow to return from the
function as well as the outer lambda (the one assigned to const
fact), and the return value would be the inner lambda.This is the other hazard with lambdas. The program equivalences
Tennent's Correspondence Principle enables are good for
refactoring, but bad for thinkos like the above.(Honest, I didn't do it on purpose!)
What exactly does return from a lambda mean? Let's say I do this:
function F(x) { return lambda(n) { return x + n; } } function G(h) { return h(1) +1; } var H = F(1); G(H);
What is the value of the last expression and why?
, Maciej
On Fri, Dec 5, 2008 at 12:39 PM, Maciej Stachowiak <mjs at apple.com> wrote:
What exactly does return from a lambda mean? Let's say I do this:
function F(x) { return lambda(n) { return x + n; } } function G(h) { return h(1) +1; } var H = F(1); G(H);
What is the value of the last expression and why?
Based on the lambda and return-to-label strawmen, It's an error. Dave presents a desugaring of function to lambda, where each function body is given a label at the bottom:
lambda(x0,...,xn,...$rest) { let $THIS = thisRegister; let arguments = makeAlias([[lambda() x1, lambda($x1) x1 = $x1], ..., [lambda() xn, lambda($xn) xn = $xn]], $rest); $RETURN: { Body; void 0 } }
'return e' without a label desugars to:
return : $RETURN e
But, from the return-to-label strawman:
"The dynamic return point associated with the label must still be live when the return statement is invoked; otherwise it is a dynamic error, i.e., an exception is raised."
A label is an escape continuation. Once control has returned past the point where the continuation was captured, it's dead, and it can't be resumed.
On Dec 5, 2008, at 10:00 AM, Jon Zeppieri wrote:
On Fri, Dec 5, 2008 at 12:39 PM, Maciej Stachowiak <mjs at apple.com>
wrote:What exactly does return from a lambda mean? Let's say I do this:
function F(x) { return lambda(n) { return x + n; } } function G(h) { return h(1) +1; } var H = F(1); G(H);
What is the value of the last expression and why?
Based on the lambda and return-to-label strawmen, It's an error. Dave presents a desugaring of function to lambda, where each function body is given a label at the bottom:
lambda(x0,...,xn,...$rest) { let $THIS = thisRegister; let arguments = makeAlias([[lambda() x1, lambda($x1) x1 = $x1], ..., [lambda() xn, lambda($xn) xn = $xn]], $rest); $RETURN: { Body; void 0 } }
'return e' without a label desugars to:
return : $RETURN e
But, from the return-to-label strawman:
"The dynamic return point associated with the label must still be live when the return statement is invoked; otherwise it is a dynamic error, i.e., an exception is raised."
A label is an escape continuation. Once control has returned past the point where the continuation was captured, it's dead, and it can't be resumed.
So return from a lambda is sometimes but not always a runtime error?
Other times it can return through multiple levels of function calls
without raising an exception? That seems pretty bad for ease of
understanding and for performance of implementations. If you do this:
[1 2 3].map(lambda (x) { return x + 1; })
I think it would be better for that to be a syntax error than to make
the containing function return 2.
It seems to me the current design prioritizes lambda as a desugaring
construct or building block for imperative-style control flow, over
use as an actual first-class function. I assume break and continue
inside a lambda have similar issues.
, Maciej
On Fri, Dec 5, 2008 at 10:22 AM, Maciej Stachowiak <mjs at apple.com> wrote:
It seems to me the current design prioritizes lambda as a desugaring construct or building block for imperative-style control flow, over use as an actual first-class function. I assume break and continue inside a lambda have similar issues.
Yes. I was initially hopeful that lambdas would be usable in general as a replacement for function, in much the same way that we are all hopeful than const and let are usable as a replacement for var. However, after talking about the hazards Waldemar raised of leaking completion value, I think your statement above is accurate. It results in the sugared language having the same two levels as in Smalltalk and E: The "function" level[1], where a "return" is required in order to provide a value to one's caller, and a "lambda" level, recommended for use only for desugaring and control abstraction, where completion values leak, and where "return" remains bound to the enclosing "function".
In other words, "let" is the new "var", but "lambda" is less than the new "function" and more than the new block. That's why I argued against David-Sarah's otherwise very pleasant pattern for writing an enhanced object literal to express high integrity class-like abstractions.
[1] ES-Harmony's "function" like Smalltalk's method like E's "to" ES-Harmony's "lambda" like Smalltalk's block "[" like E's "method". ES-Harmony's "return" like Smalltalk's "^" like E's "return"
-----Original Message----- From: es-discuss-bounces at mozilla.org [mailto:es-discuss- bounces at mozilla.org] On Behalf Of Mark Miller ... In other words, "let" is the new "var", but "lambda" is less than the new "function" and more than the new block. That's why I argued against David-Sarah's otherwise very pleasant pattern for writing an enhanced object literal to express high integrity class-like abstractions. ...
In which case perhaps we should abandon this style of lambda and work harder at developing a concise construct that is a reformed function.
I'll have to hunt down David-Sarah's proposal which I haven't seen as this is a direction for object literals is something I have also thought about and found attractive.
Given, the nature of the language we have (as opposed to the language we wish we had) I might choose class-like abstractions based upon object literals over conflicting features for support concise expression of control abstraction.
On Dec 5, 2008, at 11:47 AM, Allen Wirfs-Brock wrote:
Given, the nature of the language we have (as opposed to the
language we wish we had) I might choose class-like abstractions
based upon object literals over conflicting features for support
concise expression of control abstraction.
The return hazard exists in Smalltalk too. What's different here that
makes you choose differently? Of course, there are more choices than
Tennent-to-the-max lambdas or-else classes-as-sugar.
From: Brendan Eich [mailto:brendan at mozilla.com]
The return hazard exists in Smalltalk too. What's different here that makes you choose differently? Of course, there are more choices than Tennent-to-the-max lambdas or-else classes-as-sugar.
The difference is in the foundation language we are starting with. Because of the central role of c-style syntactic control constructs in JavaScript it is unlikely that lambda-based control abstractions will ever be as important in JavaScript as they are in Smalltalk. On the other hand, object literals are core to JavaScript as they are the only mechanism in the current language for declaratively defining a new "kind" of object. Using JavaScript, every significant application probably needs to define new object abstractions but far fewer apps need to define new control abstractions. If it is necessary to make a choice I'm inclined to prioritize enhancing object literals to make them be a better object/class abstraction mechanisms over enhancing lambdas to make them a better control abstraction mechanism.
The return hazard is not a significant problem in Smalltalk. This is probably because of the pervasive use of blocks (closures) for all control structures including the simplest if statements. Every Smalltalk programmer learns at the outset that the lexical occurrence of a ^ (ie, "return") anywhere in a method (even with within a nested block) means to return from that method. They generally learn this even before they learn that full semantics of [ ] (ie, lambda). So, there is really never any confusion about whether a ^ was intended to mean return from the block as opposed to return from a method. Occasionally, (actually pretty rarely) situations arise where it would be convenient to explicitly express returning from a block evaluation rather than the method. However, I've never seen a situations where that result couldn't be achieved by restructuring the method so the return case falls off the bottom. Various people have toyed with creating some sort of explicit local block return syntax for Smalltalk (for example ^^) but there are significant complications (since Smalltalk only has block based conditionals the local return would really be a situation of an inner block forcing a return from an outer block) and the need is quite limited. Finally, if restructuring the block doesn't solve the problem, Smalltalk's very flexible exception handling abstractions can probably be used to accomplish a similar result.
Don't get me wrong, I like the semantics of break/continue/return that have been proposed for JavaScript lambdas but given our legacy I can see the return hazard being a real problem. And if it is a choice between enhanced object literals and control abstracting lambdas I'd probably go with the object literals.
Thank you for useful links and explanations. Correct me if I am wrong but in the current form lambda is a facility that duplicates a function. More than that it reminds my old languages that had separate keywords for functions (our "lambda"?) and procedures (our "function"?).
Writing code I frequently need small functions (or lambdas). The smaller the code the better --- it allows to be concise and does not obscure the intent. That's why I prefer to use lambda's proposed by Oliver Steele (osteele.com/sources/javascript/functional), which have a lot of problems being a pure JavaScript implementation. Writing a factorial using a linear recursion combinator with lambdas:
var fact1 = linrec("<= 1", "1", "[n - 1]", "m * n[0]");
is simple and more readable than the equivalent:
var fact2 = linrec( function(n){ return n <= 1; }, function(){ return 1; }, function(n){ return [n - 1]; }, function(m, n){ return m * n[0]; });
I typed 200% more text, and the readability went down by the same 200% --- because I added 200% of the technical noise not relevant to the algorithm itself. Anything that improves on that is good in my book. Lambdas are good:
var fact3 = linrec( lambda(n) n <= 1, lambda() 1, lambda(n) [n - 1], lambda(m, n) m * n[0]);
Shortcuts for lambdas are better:
var fact4 = linrec((n) n <= 1, () 1, (n) [n - 1], (m, n) m * n[0]);
I perceive them as less noisy.
The link you gave features long lambdas and I don't see what they buy vs. the regular functions. This is the example from that page:
lambda(i) { if (!isNumeric(i)) // etc. else if (i < 0) // etc. else if (i < params.length) params[i]0 else { i -= params.length; if (i < rest.length) rest[i] else // etc. } }
Written as function it is not that long or less clear:
function(i) { if (!isNumeric(i)) // etc. else if (i < 0) // etc. else if (i < params.length) return params[i]0 else { i -= params.length; if (i < rest.length) return rest[i] else // etc. } }
My point is we gain more by concentrating on small light-weight snippets than on one more way to code a big function.
So concentrating on small snippets:
- Reducing "technicalities" and the boilerplate improves the clarity of the code.
1a) I don't mind if lambdas don't have their own "this", "arguments", or a scope --- from my experience they are rarely used in small snippets.
1b) I am all for skipping "return" --- it reduces the boilerplate for small snippets.
-
Named lambdas, and parameter defaults are of little value. Use functions if you truly need a named functionality. Otherwise assign it to a variable and pass around (rarely needed).
-
Losing the keyword "lambda" in favor of a small shortcut (e.g., ) will be of great value --- less noise, less boilerplate, less typing, less opportunities to mistype.
I suggest paring down "lambda" by shedding names, default parameters, and possibly the keyword "lambda" itself --- it reduces complexity, no chance for ambiguity, easier to implement, easier to use.
Thanks,
Eugene
On Dec 5, 2008, at 12:49 PM, Allen Wirfs-Brock wrote:
From: Brendan Eich [mailto:brendan at mozilla.com]
The return hazard exists in Smalltalk too. What's different here that makes you choose differently? Of course, there are more choices than Tennent-to-the-max lambdas or-else classes-as-sugar.
The difference is in the foundation language we are starting with.
Because of the central role of c-style syntactic control constructs
in JavaScript it is unlikely that lambda-based control abstractions
will ever be as important in JavaScript as they are in Smalltalk.
Agreed so far. The long-term plan here would be macros. Post-Harmony
at this point.
[snip] If it is necessary to make a choice I'm inclined to
prioritize enhancing object literals to make them be a better
object/class abstraction mechanisms over enhancing lambdas to make
them a better control abstraction mechanism.
I question the need to make a choice (yet).
I'm actually concerned about usability of lambdas as anything similar
to functions. Say they're added, and they prove popular for control
abstractions and other purposes, including "better functions". Then
not only will completion-value leaks bite people -- misplaced return
probably will too, if "better functions" involves porting existing
code from function to lambda.
This is all speculation, but here's my non-speculative claim: lambda
syntax should not look so much like function syntax if return within
the body behaves completely differently. We would want syntax very
different from function (i.e., not lambda (args) {body} -- sorry,
Peter Michaux). Or else we should revisit the wisdom of applying TCP
to lambdas.
[Smalltalk observations snipped -- thanks for those, they make sense
but I want to keep pushing on what doesn't work in the current
strawman: wiki space.]
Don't get me wrong, I like the semantics of break/continue/return
that have been proposed for JavaScript lambdas but given our legacy
I can see the return hazard being a real problem.
I agree if lambda looks like function or is sold as a "better
function". If it looks more like a block, or something else, that
might mitigate the return hazard. Michael Day wondered if we confined
the body to an expression language -- that would eliminate the return
hazard.
And if it is a choice between enhanced object literals and control
abstracting lambdas I'd probably go with the object literals.
No false dilemmas yet, please!
This is all speculation, but here's my non-speculative claim: lambda syntax should not look so much like function syntax if return within the body behaves completely differently. We would want syntax very different from function (i.e., not lambda (args) {body} -- sorry, Peter Michaux). Or else we should revisit the wisdom of applying TCP to lambdas.
I'm unconvinced that TCP needs to apply to statements; it seems like a more valuable property when applied to expressions, even though JavaScript is not referentially transparent to begin with.
Anyway, these three options look good to me:
(1) Expression lambdas: lambdas whose body is an expression.
var x = lambda(y, z) y + z
Solves the problem with completion leakage, solves the nested return/break/continue issue. However, quite limited in usage, and makes it difficult to use lambdas to replace functions as they can't contain loop statements. (Hello, recursion! :)
(2) Function lambdas: objects just like functions, except no "this" or "arguments", and perhaps some guarantees about tail calls?
var x = lambda(y, z) { return y + z }
This seems the easiest for programmers to understand, and avoids the return/break issues. It violates TCP for statements, but I don't think that really matters in practice; after all, so do functions.
(3) Parametric blocks: where a block, possibly taking arguments, can be passed around as an object. The key use-case for this seems to be creating new control abstractions. I would argue that blocks should not be usable as expressions, and the completion value cannot be captured (unless using eval) for consistency with existing statement behaviour.
var a = block { ... statements ... } var b = block(x, y) { ... statements using x and y ... }
call b(1, 2); // this is a statement, not an expression
Unfortunately, object literals also look like blocks, and there is no perfect syntax for this that fits neatly into the existing language. without using bulky keywords. While this option preserves TCP, I don't think JavaScript really needs this feature, and the power/complexity ratio doesn't measure up.
I agree if lambda looks like function or is sold as a "better function". If it looks more like a block, or something else, that might mitigate the return hazard. Michael Day wondered if we confined the body to an expression language -- that would eliminate the return hazard.
I like options (1) and (2) above. The current proposal on the wiki feels like all three options mashed together, and I find it difficult make sense of it as a basic construct.
On Dec 5, 2008, at 2:57 PM, Michael Day wrote:
This is all speculation, but here's my non-speculative claim:
lambda syntax should not look so much like function syntax if
return within the body behaves completely differently. We would
want syntax very different from function (i.e., not lambda (args)
{body} -- sorry, Peter Michaux). Or else we should revisit the
wisdom of applying TCP to lambdas.I'm unconvinced that TCP needs to apply to statements; it seems like
a more valuable property when applied to expressions, even though
JavaScript is not referentially transparent to begin with.
Good points.
Anyway, these three options look good to me:
(1) Expression lambdas: lambdas whose body is an expression.
var x = lambda(y, z) y + z
Would need parens around the body, if this is a primary expression,
else reduce-reduce conflict.
Solves the problem with completion leakage, solves the nested return/ break/continue issue. However, quite limited in usage, and makes it
difficult to use lambdas to replace functions as they can't contain
loop statements. (Hello, recursion! :)
We could mandate tail recursive call sites in the spec so people could
count on it cross-browser.
bugs.ecmascript.org/ticket/215, bugs.ecmascript.org/ticket/215 (not be necessary for expression- enclosed tail calls)
(2) Function lambdas: objects just like functions, except no "this"
or "arguments", and perhaps some guarantees about tail calls?var x = lambda(y, z) { return y + z }
This seems the easiest for programmers to understand, and avoids the
return/break issues. It violates TCP for statements, but I don't
think that really matters in practice; after all, so do functions.
I must agree, since this looks like function syntax, with a new
introductory keyword. TCP must yield.
(3) Parametric blocks: where a block, possibly taking arguments, can
be passed around as an object. The key use-case for this seems to be
creating new control abstractions. I would argue that blocks should
not be usable as expressions, and the completion value cannot be
captured (unless using eval) for consistency with existing statement
behaviour.var a = block { ... statements ... } var b = block(x, y) { ... statements using x and y ... }
call b(1, 2); // this is a statement, not an expression
Unfortunately, object literals also look like blocks, and there is
no perfect syntax for this that fits neatly into the existing
language. without using bulky keywords. While this option preserves
TCP, I don't think JavaScript really needs this feature, and the
power/complexity ratio doesn't measure up.
Agreed. Past efforts to add just this kind of block have failed to get
anywhere.
I agree if lambda looks like function or is sold as a "better
function". If it looks more like a block, or something else, that
might mitigate the return hazard. Michael Day wondered if we
confined the body to an expression language -- that would eliminate
the return hazard.I like options (1) and (2) above. The current proposal on the wiki
feels like all three options mashed together, and I find it
difficult make sense of it as a basic construct.
Including Dave to get his thoughts, in case he is reading es-discuss
in a digest or deferred fashion.
Breton Slivka wrote:
On Fri, Dec 5, 2008 at 7:49 AM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Mark S. Miller wrote:
[...] "" has taken the lead.... There's still #, @, and ` (and of course keywords like lambda and fn). None of these are as mnemonic as , but they leave \ as a purely lexical escape character.
It's quite ironic that we are still limited, as Church was, in which characters we can use for the modern equivalent of "typographical reasons".
this may be a stupid question, but why? Is it really so impossible to have λ(a,b,c){} ? You guys seem to have no trouble typing it.
To type λ, I usually have to cut-and-paste it from somewhere else, which is quite inconvenient. But more importantly, having a non-US-ASCII character in the basic syntax means that parsing is dependent on recognising character encoding accurately. In practice, that is very hit-and-miss, with files' encoding often being labelled or guessed incorrectly. Currently, the effects of this are restricted to programs that use non-US-ASCII characters in strings without escaping, and therefore only the files containing such programs have to be labelled accurately.
(I wish it weren't so, and often the reason why it is so is inexcusably poor attention to standards by application writers, but we have to be realistic.)
Breton Slivka wrote:
On Fri, Dec 5, 2008 at 9:35 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Dec 4, 2008, at 2:31 PM, Breton Slivka wrote:
I admit this seems ludicrous at its face, but admittedly I have not really seen the arguments against λ as an abbreviated lambda syntax yet. [...]
It's still too hard to type.
picasaweb.google.com/eileen.world.traveler/EileenBestOfGreece#5139474493916668850
Using a Greek keyboard might, for the sake of argument, also be feasible for users whose main language is English, but not for those who require other keyboard layouts in order to type their main language.
Maciej Stachowiak wrote:
What exactly does return from a lambda mean? Let's say I do this:
function F(x) { return lambda(n) { return x + n; } } function G(h) { return h(1) +1; } var H = F(1); G(H);
What is the value of the last expression and why?
The lambda would return from F (specifically, from the activation of F in which the lambda was created), but since that activation of F has already returned when the lambda is called, this is a dynamic error -- presumably, resulting in an exception.
What that example doesn't show it is whether the exception is thrown only when the offending 'return' statement is executed, or when a a lambda that could execute an offending return is entered.
Also note that this example could be affected by the resolution of the "leakage" issue, if we decide for example that the syntax 'lambda(n) { return x + n; }' returns from the lambda rather than the enclosing function (which it could do without necessarily violating the Tennent Correspondence rationale for lambdas, as long as there is another syntax that doesn't bind 'return').
Maciej Stachowiak wrote:
On Dec 5, 2008, at 10:00 AM, Jon Zeppieri wrote:
A label is an escape continuation. Once control has returned past the point where the continuation was captured, it's dead, and it can't be resumed.
So return from a lambda is sometimes but not always a runtime error?
That in itself does not seem much of a problem (it doesn't seem to be in Smalltalk or E).
Other times it can return through multiple levels of function calls without raising an exception? That seems pretty bad for ease of understanding and for performance of implementations.
I agree, although I would express this as a safety issue: the safety hazard is that you may call a lambda without expecting it to perform a jump within the calling scope. The performance issue is that all calls that are within the scope of a labelled statement or loop must check for an escape.
There is a possibility for solving both of these issues while still allowing all of the suggested uses of lambdas. It involves permitting a given call to perform an escape (that is, break, continue or return) only if it is explicitly marked as being able to do so. If an unmarked call would escape, then it instead throws an exception.
For example (syntax intended only as a strawman; I know it is incompatible to add an 'escape' keyword):
CallExpression : ... 'escape' CallExpression Arguments
Then a user-defined 'while' could be written as:
const _while = (cond, body) { // escapes from 'cond' are intentionally disallowed; // only 'body' can escape. if (cond()) { escape body(); escape while(cond, body); } };
and a use of '_while' would be:
foo: escape _while({bar}, { ... if (baz) break foo; });
This doesn't impede the use of lambdas in control abstractions or in desugaring, because those uses can easily call the lambda using an 'escape' call. Nor does it impede "lambda as the new function", because uses of lambda in place of function won't attempt to 'break', 'continue' or 'return' (and that can be made a static error in the syntactic contexts where lambda is intended to replace functions, such as in my suggestion for object literals).
It does introduce the syntactic inconvience of having to call control abstractions using 'escape' (or whatever syntax is used for these calls). For use of lambda to desugar built-in constructs, this is not an issue.
If at some point macros were added (even a very limited form of macros), then the syntactic inconvience could be alleviated, and nothing would have to be changed in the semantics of lambda at that point.
If you do this:
[1 2 3].map(lambda (x) { return x + 1; })
I think it would be better for that to be a syntax error than to make the containing function return 2.
In my proposal above, that would be a dynamic error (the call to the lambda from 'map' would throw an exception).
This case could in principle be detected statically; that is, passing a lambda expression directly as an argument without using 'escape' would be a static error.
Unfortunately it's not possible to detect all of the errors introduced by this proposal statically, because the lambda could have been obtained from anywhere, so without static typing (or making lambdas not-first-class, which I don't recommend), it's not possible to know in all cases when a lambda is being passed as an argument.
It seems to me the current design prioritizes lambda as a desugaring construct or building block for imperative-style control flow, over use as an actual first-class function. I assume break and continue inside a lambda have similar issues.
They do, but the proposal above addresses 'break', 'continue' and 'return' together.
It may be considered too complicated, too restrictive, or not sufficiently easy to understand. I'm undecided about that, although it does solve both the safety and performance objections to escape continuations, and the semantics are easy to specify.
Peter Michaux wrote:
Summary of what I've read:
The syntax (){} has a named lambda problem if the lambda name starts with a 'u'.
Why do we need named lambdas (or more precisely, why do we need them to be named by the lambda syntax)? A lambda can always be named by assigning it to a 'const' variable.
Peter Michaux wrote:
The syntax ^(){} has a semicolon insertion ambiguity. What does the following mean?
x = x ^(){}
It's not ambiguous; it would mean "x = x^(){}", which is (in this case) a syntax error.
'-' and '/' already have a similar issue of possibly unexpected parsing when they occur at the beginning of a line. It's just that it is rare to intend to start an ExpressionStatement with a unary minus or a regexp literal, so this doesn't occur very often in practice. It's not clear how often programmers will intend to start an ExpressionStatement with a lambda, although I can see that this might be more common.
Note that programmers who never deliberately rely on semicolon insertion will not be surprised by this example; they will consider writing "x = x" without an terminating semicolon to have been their mistake.
David-Sarah Hopwood wrote:
Peter Michaux wrote:
The syntax ^(){} has a semicolon insertion ambiguity. What does the following mean?
x = x ^(){}
It's not ambiguous; it would mean "x = x^(){}", which is (in this case) a syntax error.
'-' and '/' already have a similar issue of possibly unexpected parsing when they occur at the beginning of a line.
and '+', '++', '--', '(', and (for a different reason) 'function'.
Actually, the case of '(' is arguably worse than '^', because '(' at the start of an ExpressionStatement is very common:
x = x (foo)
(parsed as 'x = x(foo);', but intended to be 'x = x; (foo);').
David-Sarah Hopwood wrote:
David-Sarah Hopwood wrote:
'-' and '/' already have a similar issue of possibly unexpected parsing when they occur at the beginning of a line.
and '+', '++', '--', '(', and (for a different reason) 'function'.
Correction: not '++' or '--', because they are "restricted productions".
P T Withington wrote:
Would it work to move the parameter list inside the block (as in the Smalltalk way, but as a regular parameter list, not using ||'s)?
{(a, b) a + b}
AFAICT,
{(
is a syntax error for an expression in es3.
I think this is unambiguous, but I don't like it because it has no symbol or combination of symbols that is specific to a lambda. ( "{(" can occur as the start of a block.)
On Sat, Dec 6, 2008 at 9:57 AM, Michael Day <mikeday at yeslogic.com> wrote:
(1) Expression lambdas: lambdas whose body is an expression.
var x = lambda(y, z) y + z
Solves the problem with completion leakage, solves the nested return/break/continue issue. However, quite limited in usage, and makes it difficult to use lambdas to replace functions as they can't contain loop statements. (Hello, recursion! :)
This idea appeals to me for a couple reasons:
-
I have occassionally found myself using and repeating fairly complex expressions in the conditions of if statements. Functions would work, but I tend to avoid them out of a (possibly misplaced?) Perception that refactoring the expressions isn't quite worth the performance cost of an extra function call. This is bad code practice I will admit, but if there were something that didn't have quite the performance cost, and could promote DRYness in expressions, that would be great.
-
It would be really nice to have a callable value that was garaunteed not to have side effects. a lambda with an expression body might not be that. Nevertheless, this would enable a parallelized array "map" function that's safe to use. In the absence of "real" multithreading, this kind of parallelism would be a boon for applications like 3d games, or image processing.
2008/12/6 David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk>:
To type λ, I usually have to cut-and-paste it from somewhere else, which is quite inconvenient. But more importantly, having a non-US-ASCII character in the basic syntax means that parsing is dependent on recognising character encoding accurately. In practice, that is very hit-and-miss, with files' encoding often being labelled or guessed incorrectly. Currently, the effects of this are restricted to programs that use non-US-ASCII characters in strings without escaping, and therefore only the files containing such programs have to be labelled accurately.
(I wish it weren't so, and often the reason why it is so is inexcusably poor attention to standards by application writers, but we have to be realistic.)
-- David-Sarah Hopwood
I thought as such, fair enough. It was just naive and boundless optimism that in 5-10 years time, this would cease to be an issue, but this is magical thinking.
On Fri, Dec 5, 2008 at 11:07 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Maciej Stachowiak wrote:
On Dec 5, 2008, at 10:00 AM, Jon Zeppieri wrote:
A label is an escape continuation. Once control has returned past the point where the continuation was captured, it's dead, and it can't be resumed.
So return from a lambda is sometimes but not always a runtime error?
That in itself does not seem much of a problem (it doesn't seem to be in Smalltalk or E).
Other times it can return through multiple levels of function calls without raising an exception? That seems pretty bad for ease of understanding and for performance of implementations.
I agree, although I would express this as a safety issue: the safety hazard is that you may call a lambda without expecting it to perform a jump within the calling scope. The performance issue is that all calls that are within the scope of a labelled statement or loop must check for an escape.
I don't get it. What issue is raised by return-to-label that isn't already raised by exceptions? They're practically the same thing, only return-to-label is easier to analyze statically, because 'return' can only jump to a label that is lexically (not just dynamically) in scope.
There is a possibility for solving both of these issues while still allowing all of the suggested uses of lambdas. It involves permitting a given call to perform an escape (that is, break, continue or return) only if it is explicitly marked as being able to do so. If an unmarked call would escape, then it instead throws an exception.
For example (syntax intended only as a strawman; I know it is incompatible to add an 'escape' keyword):
CallExpression : ... 'escape' CallExpression Arguments
Then a user-defined 'while' could be written as:
const _while = (cond, body) { // escapes from 'cond' are intentionally disallowed; // only 'body' can escape. if (cond()) { escape body(); escape while(cond, body); } };
Can the call to cond throw? If so, what did you gain from this?
This is ambiguous
{(a, b) a + b}
is
{(a,b); a+b }
unless use no line break restrict and it is difficult to parse
On Dec 5, 2008, at 11:12 PM, Jon Zeppieri wrote:
I don't get it. What issue is raised by return-to-label that isn't already raised by exceptions? They're practically the same thing, only return-to-label is easier to analyze statically, because 'return' can only jump to a label that is lexically (not just dynamically) in scope.
If you want to call a function and make sure control flow does not
escape, then in the face of exceptions alone you can wrap it in try/
catch. However, with multi-level returning lambdas, if you are passed
a function then you have no way to prevent it from returning early,
since it could be a lambda in the lexical scope of your caller.
, Maciej
On Sat, Dec 6, 2008 at 11:49 AM, Maciej Stachowiak <mjs at apple.com> wrote:
On Dec 5, 2008, at 11:12 PM, Jon Zeppieri wrote:
I don't get it. What issue is raised by return-to-label that isn't already raised by exceptions? They're practically the same thing, only return-to-label is easier to analyze statically, because 'return' can only jump to a label that is lexically (not just dynamically) in scope.
If you want to call a function and make sure control flow does not escape, then in the face of exceptions alone you can wrap it in try/catch. However, with multi-level returning lambdas, if you are passed a function then you have no way to prevent it from returning early, since it could be a lambda in the lexical scope of your caller.
The strawman contains the following text:
"Unwinding the execution context may pass through finally blocks, which execute and may perform their own control effects, effectively canceling the unwinding."
So, you have a dynamic-wind-like mechanism, if you need it.
Also, what was the performance issue?
On Dec 6, 2008, at 9:57 AM, Jon Zeppieri wrote:
On Sat, Dec 6, 2008 at 11:49 AM, Maciej Stachowiak <mjs at apple.com>
wrote:On Dec 5, 2008, at 11:12 PM, Jon Zeppieri wrote:
I don't get it. What issue is raised by return-to-label that isn't already raised by exceptions? They're practically the same thing, only return-to-label is easier to analyze statically, because 'return' can only jump to a label that is lexically (not just dynamically) in scope.
If you want to call a function and make sure control flow does not
escape, then in the face of exceptions alone you can wrap it in try/catch.
However, with multi-level returning lambdas, if you are passed a function
then you have no way to prevent it from returning early, since it could be a
lambda in the lexical scope of your caller.The strawman contains the following text:
"Unwinding the execution context may pass through finally blocks, which execute and may perform their own control effects, effectively canceling the unwinding."
So, you have a dynamic-wind-like mechanism, if you need it.
I guess then the damage can be contained, but it's unusual to use a
mechanism like this for normal control flow rather than just
exceptional conditions.
Also, what was the performance issue?
It turns return inside a lambda into a construct that has to unwind
the stack (and apparently run finally handlers), which makes its cost
more like the cost of throwing an exception than the cost of a normal
return. In most implementations, throwing an exception is much more
expensive. Actually, it could be worse than throwing an exception,
since if you can't actually unwind the call stack until you find
whether the lambda's containing function is currently on the stack.
, Maciej
On Sat, Dec 6, 2008 at 2:03 PM, Maciej Stachowiak <mjs at apple.com> wrote:
On Dec 6, 2008, at 9:57 AM, Jon Zeppieri wrote:
On Sat, Dec 6, 2008 at 11:49 AM, Maciej Stachowiak <mjs at apple.com> wrote:
On Dec 5, 2008, at 11:12 PM, Jon Zeppieri wrote:
I don't get it. What issue is raised by return-to-label that isn't already raised by exceptions? They're practically the same thing, only return-to-label is easier to analyze statically, because 'return' can only jump to a label that is lexically (not just dynamically) in scope.
If you want to call a function and make sure control flow does not escape, then in the face of exceptions alone you can wrap it in try/catch. However, with multi-level returning lambdas, if you are passed a function then you have no way to prevent it from returning early, since it could be a lambda in the lexical scope of your caller.
The strawman contains the following text:
"Unwinding the execution context may pass through finally blocks, which execute and may perform their own control effects, effectively canceling the unwinding."
So, you have a dynamic-wind-like mechanism, if you need it.
I guess then the damage can be contained, but it's unusual to use a mechanism like this for normal control flow rather than just exceptional conditions.
I'd say that under the proposed semantics, return from lambda isn't normal control flow; it's a (potentially) non-local jump. Normal "return" inside a lambda is just falling off the end.
Also, what was the performance issue?
It turns return inside a lambda into a construct that has to unwind the stack (and apparently run finally handlers), which makes its cost more like the cost of throwing an exception than the cost of a normal return.
Yes, because it is very similar to throwing an exception. Would you prefer that return inside lambda instead return from the lambda's own activation? That could be done, with some violence to TCP.
In the most common case, however -- namely, return from function, which, under Dave's proposal, desugars to a return from lambda -- couldn't the additional cost be optimized away easily? You can determine statically that the jump doesn't unwind the stack, so the cost of returning should remain the same.
In most implementations, throwing an exception is much more expensive. Actually, it could be worse than throwing an exception, since if you can't actually unwind the call stack until you find whether the lambda's containing function is currently on the stack.
Okay, but you only incur this expense when you actually take the non-local exit. There is no reason why normal returns should be more expensive.
Eric Suen wrote:
This is ambiguous
{(a, b) a + b}
is
{(a,b); a+b }
This example isn't ambiguous, because an ExpressionStatement cannot start with '{', therefore this is a block. However the fact that a lambda starting an ExpressionStatement would have to be parenthesized is a valid argument against this syntax, and also against {|a, b| a+b}.
unless use no line break restrict and it is difficult to parse
It's not actually difficult to parse (since an object literal cannot have '(' after the '{'), but I don't think it has any advantages over syntaxes starting with '^' or ''.
('^' is back on the table given that the semicolon insertion hazard that caused us to be suspicious of it, already exists when a line starts with '(', for example.)
Jon Zeppieri wrote:
On Sat, Dec 6, 2008 at 11:49 AM, Maciej Stachowiak <mjs at apple.com> wrote:
On Dec 5, 2008, at 11:12 PM, Jon Zeppieri wrote:
I don't get it. What issue is raised by return-to-label that isn't already raised by exceptions? [...] Also, what was the performance issue?
The (minor) performance issue is that if there is a lambda that returns from a given function, all calls within that function body must check for an escape, even if the lambda is never passed to them or otherwise accessible to them. Similarly for calls within the scope of a labelled statement or iteration that contains a lambda with a corresponding 'break' or 'continue'.
Exceptions, OTOH, are implementable without an explicit check on each function call.
Just this performance issue on its own wouldn't be significant -- it might matter in a language that is otherwise highly optimizable, but not in ECMAScript. I'll address the safety issue separately.
But in page:
lambda is not just a expression, it could be a Declaration.
If lambda is only a expression, that is why I suggest in post:
Eric Suen wrote:
But in page:
lambda is not just a expression, it could be a Declaration.
If lambda is only a expression, that is why I suggest in post:
No, "" worse than '^' or '&', why not use
function ^ Identifier ( parameters ) block for declaration
and use
^ IdentifierOpt ( parameters ) block for expression
ExpressionStatement ::= [lookahead !{ {, function, ^ }] CommaExpression
The keyword 'function' shouldn't be used for this because a lambda is not a function. However,
const name(formals) ... let name(formals) ...
could be sugar for
const name = lambda(formals) ...; let name = lambda(formals) ...;
(replacing 'lambda' with whatever symbol or keyword is chosen). Then there is no need to change the negative lookahead in ExpressionStatement.
On Sat, Dec 6, 2008 at 7:51 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
The keyword 'function' shouldn't be used for this because a lambda is not a function. However,
const name(formals) ... let name(formals) ...
could be sugar for
const name = lambda(formals) ...;
Does "const" have "var" or "let" scoping...or is it even a declaration at all? Although it is bulky it might be better to write "const var" and "const let". A variable being constant or not is orthogonal to its scoping and should be controlled independently.
let name = lambda(formals) ...;
I mentioned this a while back. I think it might be a good idea. Scheme has
(define foo (lambda (a b) 1)) (define (foo a b) 1)
which could be translated to ES
var foo = lambda(a, b) 1; var foo(a, b) 1;
Peter
On Dec 6, 2008, at 9:19 PM, Peter Michaux wrote:
On Sat, Dec 6, 2008 at 7:51 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
The keyword 'function' shouldn't be used for this because a lambda is not a function. However,
const name(formals) ... let name(formals) ...
could be sugar for
const name = lambda(formals) ...;
Does "const" have "var" or "let" scoping...or is it even a declaration at all? Although it is bulky it might be better to write "const var"
No, const var is an oxymoron.
and "const let". A variable being constant or not is orthogonal to its scoping and should be controlled independently.
The preferred approach is to make let and const have the same binding
scope, namely block, and leave var alone.
let name = lambda(formals) ...;
I mentioned this a while back. I think it might be a good idea.
Scheme has(define foo (lambda (a b) 1)) (define (foo a b) 1)
which could be translated to ES
var foo = lambda(a, b) 1; var foo(a, b) 1;
I still think this is bad form. A compound that does not create a
variable that must denote the defined function some time later (via
eval, arguments aliasing, hidden assignment if this is global code,
etc.) misstates what is usefully meant by the proposed syntax.
JS is not Scheme, and while you could argue assignment is like set!
the binding forms (including var extensions, but especially function,
const, and let) should have more definite and (under a strict mode or
future version) immutable meaning.
Peter Michaux wrote:
On Sat, Dec 6, 2008 at 7:51 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
The keyword 'function' shouldn't be used for this because a lambda is not a function. However,
const name(formals) ... let name(formals) ...
could be sugar for
const name = lambda(formals) ...;
Does "const" have "var" or "let" scoping...or is it even a declaration at all?
When 'let' and 'const' were proposed to be added to ES3.1, 'const' would have been a declaration with the same scoping as 'let'. This part of the ES3.1 proposal wasn't controversial, I think.
Although it is bulky it might be better to write "const var" and "const let". A variable being constant or not is orthogonal to its scoping and should be controlled independently.
That would be the case if the scoping of 'let' wasn't a strict improvement on, and intended replacement for, that of 'var'.
David-Sarah Hopwood wrote:
Jon Zeppieri wrote:
On Sat, Dec 6, 2008 at 11:49 AM, Maciej Stachowiak <mjs at apple.com> wrote:
On Dec 5, 2008, at 11:12 PM, Jon Zeppieri wrote:
I don't get it. What issue is raised by return-to-label that isn't already raised by exceptions? [...] Also, what was the performance issue?
The (minor) performance issue is that if there is a lambda that returns from a given function, all calls within that function body must check for an escape, even if the lambda is never passed to them or otherwise accessible to them. Similarly for calls within the scope of a labelled statement or iteration that contains a lambda with a corresponding 'break' or 'continue'.
Please disregard this -- I had overlooked a way to make the performance of escape continuations identical to that of exceptions. No explicit per-call checks are needed; the jump to the break/continue/return target can be handled using the same mechanism as try/catch handlers, with the same possible optimizations. In the case where the control abstraction is built-in or its implementation is inlined, the performance can be the same as conventional break/continue/return.
On 2008-12-06, at 00:23EST, David-Sarah Hopwood wrote:
P T Withington wrote:
Would it work to move the parameter list inside the block (as in the Smalltalk way, but as a regular parameter list, not using ||'s)?
{(a, b) a + b}
AFAICT,
{(
is a syntax error for an expression in es3.I think this is unambiguous, but I don't like it because it has no symbol or combination of symbols that is specific to a lambda. ( "{(" can occur as the start of a block.)
^{(a, b) a +b}
Perhaps? An expression cannot start with {(
, a statement cannot
start with ^
.
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Indeed, it can look like an expression to begin a name assignment:
var thinger = {("foo"+"bar"): ... };
Has a syntax like this been shot down yet?:
var thinger = {{ foo, bar }: ... };
Since objects (much less literals) aren't used as keys in hashes
often, this strikes me as being somewhat less ambiguous. The short (no
args) version might then be:
var thinger = {: ... };
Arguments against?
Alex Russell wrote:
Indeed, it can look like an expression to begin a name assignment:
var thinger = {("foo"+"bar"): ... };
Has a syntax like this been shot down yet?:
var thinger = {{ foo, bar }: ... };
Since objects (much less literals) aren't used as keys in hashes
often, this strikes me as being somewhat less ambiguous. The short (no
args) version might then be:var thinger = {: ... };
Arguments against?
I think that would require LALR(k) for bottom-up parsers, which we're trying to avoid mandating.
Alex Russell wrote:
On Dec 5, 2008, at 9:23 PM, David-Sarah Hopwood wrote:
P T Withington wrote:
Would it work to move the parameter list inside the block (as in the Smalltalk way, but as a regular parameter list, not using ||'s)?
{(a, b) a + b}
AFAICT,
{(
is a syntax error for an expression in es3.I think this is unambiguous, but I don't like it because it has no symbol or combination of symbols that is specific to a lambda. ( "{(" can occur as the start of a block.)
Indeed, it can look like an expression to begin a name assignment:
var thinger = {("foo"+"bar"): ... };
No, property names in object literals are required to be a single IdentifierName, StringLiteral or NumericLiteral.
Has a syntax like this been shot down yet?:
var thinger = {{ foo, bar }: ... };
Since objects (much less literals) aren't used as keys in hashes often, this strikes me as being somewhat less ambiguous. The short (no args) version might then be:
var thinger = {: ... };
Arguments against?
What is the advantage of this syntax over ^(a, b) {a+b}, for example?
Ditto for P T Withington's proposal of ^{(a, b) a+b}.
Breton Slivka wrote:
On Sat, Dec 6, 2008 at 9:57 AM, Michael Day <mikeday at yeslogic.com> wrote:
(1) Expression lambdas: lambdas whose body is an expression.
var x = lambda(y, z) y + z
Solves the problem with completion leakage, solves the nested return/break/continue issue. However, quite limited in usage, and makes it difficult to use lambdas to replace functions as they can't contain loop statements. (Hello, recursion! :)
[snip]
- It would be really nice to have a callable value that was garaunteed not to have side effects. a lambda with an expression body might not be that. Nevertheless, this would enable a parallelized array "map" function that's safe to use. In the absence of "real" multithreading, this kind of parallelism would be a boon for applications like 3d games, or image processing.
This little comment got lost in the recent deluge of emails, but I too would really like some mechanism to avoid or see if a function causes side effects (and not just mutability).
On Mon, Dec 8, 2008 at 4:01 AM, Yuh-Ruey Chen <maian330 at gmail.com> wrote:
Breton Slivka wrote:
On Sat, Dec 6, 2008 at 9:57 AM, Michael Day <mikeday at yeslogic.com> wrote:
(1) Expression lambdas: lambdas whose body is an expression.
var x = lambda(y, z) y + z
Solves the problem with completion leakage, solves the nested return/break/continue issue. However, quite limited in usage, and makes it difficult to use lambdas to replace functions as they can't contain loop statements. (Hello, recursion! :)
[snip]
- It would be really nice to have a callable value that was garaunteed not to have side effects. a lambda with an expression body might not be that. Nevertheless, this would enable a parallelized array "map" function that's safe to use. In the absence of "real" multithreading, this kind of parallelism would be a boon for applications like 3d games, or image processing.
This little comment got lost in the recent deluge of emails, but I too would really like some mechanism to avoid or see if a function causes side effects (and not just mutability).
Without a static type system, there's very little you can do. For example, let's say you wanted a guarantee that a call to some function F doesn't mutate any variables. A simple analysis can determine (conservatively) that F's own code doesn't perform any assignments, but the problem is that you also have to prove the same property for the entire possible call tree rooted at F. So, if F is passed a function and (potentially) calls it, that function has to be proven pure, as well.
Sure, you could abandon the proof requirement and turn it into an assertion, so that if F is annotated as "mutation-free" and if, at runtime, it tries to perform an assignment, an exception is thrown. But this doesn't work so well for other computational effects. Throwing an exception itself is an effect, and it would be pointless to throw an exception when your code illegally attempts... to throw an exception.
Non-termination is an effect, and it's notoriously immune to runtime checks.
I/O effects are possibly the most interesting from a practical perspective, but they're outside the scope of the ES standard.
On Mon, Dec 8, 2008 at 7:20 AM, Jon Zeppieri <jaz at bu.edu> wrote:
On Mon, Dec 8, 2008 at 4:01 AM, Yuh-Ruey Chen <maian330 at gmail.com> wrote:
Breton Slivka wrote:
- It would be really nice to have a callable value that was garaunteed not to have side effects.
This little comment got lost in the recent deluge of emails, but I too would really like some mechanism to avoid or see if a function causes side effects (and not just mutability).
Without a static type system, there's very little you can do. Sure, you could abandon the proof requirement and turn it into an assertion, so that if F is annotated as "mutation-free" and if, at runtime, it tries to perform an assignment, an exception is thrown. But this doesn't work so well for other computational effects. Throwing an exception itself is an effect, and it would be pointless to throw an exception when your code illegally attempts... to throw an exception.
Non-termination is an effect, and it's notoriously immune to runtime checks.
I/O effects are possibly the most interesting from a practical perspective, but they're outside the scope of the ES standard.
E's auditors www.erights.org/elang/kernel/auditors <
wiki.erights.org/wiki/Guard-based_auditing> are a mostly non-static
approach that can verify many properties, including side effects per se including IO effects, but not including throws and non-termination. Joe-E's similar, but non-extensible and static auditor system < www.cs.berkeley.edu/~daw/papers/pure-ccs08.pdf>, has been used to
verify some nice purity properties.
I am not ready to propose any such thing for Harmony at this time.
The auditors idea is good; we've talked about it in the context of
Harmony.
Of course, ES4 had optional types, but over time ES4 lost the idea of
a normative optional static checker. It's not clear on the web when
you check. Cormac Flanagan proposed checking "when it seemed like a
good time" -- when script and page loads stabilized into some web app
cohort of sources that seemed not to be loading more code at the moment.
Optional offline static analyses can be powerful tools. They don't
even need soundness to be useful -- Mozilla's experience in C++ bears
this out (blog.mozilla.com/tglek). The same trade-offs may
apply to a Harmonious optional annotation system, if it's used well in
real code.
On 2008-12-08, at 00:59EST, David-Sarah Hopwood wrote:
What is the advantage of this syntax over ^(a, b) {a+b}, for example?
I prefer the above, if it were unambiguous.
Ditto for P T Withington's proposal of ^{(a, b) a+b}.
The above, I think, is. Because it is currently invalid.
Yuh-Ruey Chen wrote:
Breton Slivka wrote:
On Sat, Dec 6, 2008 at 9:57 AM, Michael Day <mikeday at yeslogic.com> wrote:
(1) Expression lambdas: lambdas whose body is an expression.
var x = lambda(y, z) y + z
Solves the problem with completion leakage, solves the nested return/break/continue issue. However, quite limited in usage, and makes it difficult to use lambdas to replace functions as they can't contain loop statements. (Hello, recursion! :) [snip]
- It would be really nice to have a callable value that was garaunteed not to have side effects. a lambda with an expression body might not be that. Nevertheless, this would enable a parallelized array "map" function that's safe to use. In the absence of "real" multithreading, this kind of parallelism would be a boon for applications like 3d games, or image processing.
This little comment got lost in the recent deluge of emails, but I too would really like some mechanism to avoid or see if a function causes side effects (and not just mutability).
That's unfeasibly difficult in full ECMAScript. Perhaps you could do it starting with one of the secure subsets (which have immutable globals and immutable prototypes of built-in types, for instance).
On Mon, Dec 1, 2008 at 3:19 PM, Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com> wrote:
Just to clarify some speculation, the syntax I proposed ({||}) was solely inspired by Smalltalk and tempered by the parsing realities of a C-like syntax. Any similarities to Ruby constructs are probably examples of parallel evolution under similar environmental pressures. I suspect that designers of other languages with C-like syntax (C# comes to mind with its () => expr "lambda" syntax) did not have the experience or goal of using closures to create control abstractions (which often requires passing multi-statement closures) and so arrived at a more function-like concise closure syntax.
I can share some history for the => form. It's disconcerting that
everyone associates it with C#, because they are open about copying the syntax from Scala. Scala's designer, Martin Odersky, most definitely had in mind that people could use functions for control flow, and in fact he treats it as the primary way to do control flow in Scala. I believe Martin got this syntax most directly from ML's "fn" expressions. He noticed that you don't really need the keyword.
The development for ML->Scala->C# actually looks a lot like is
happening in ES discussions. Once a function literal syntax is available, people really want to use it, and the syntax is pressured to get shorter and even to get its keyword dropped in favor of symbols.
On this list, the => form has so far been dismissed due to parsing
concerns. If that's the only reason, let me try and allay that worry and put that horse back in the race. Scala also has a comma operator, but it still manages to parse the => syntax. They way it does it is
to initially parse an expression and then, if it sees a =>,
reinterpret what it has seen so far as a parameter list. It's an unusual parsing strategy, but it works well and the issue is localized.
IMHO, x => x+1 really looks like a function literal, so that's the
color I'd paint the bike shed. I agree with Allen and others, though, that any version that drops the keyword will make the form more useful in practice.
Lex Spoon wrote:
On this list, the => form has so far been dismissed due to parsing concerns. If that's the only reason, let me try and allay that worry and put that horse back in the race. Scala also has a comma operator, but it still manages to parse the => syntax. They way it does it is to initially parse an expression and then, if it sees a =>, reinterpret what it has seen so far as a parameter list. It's an unusual parsing strategy, but it works well and the issue is localized.
I don't think anyone is suggesting that it would be too difficult to parse for bottom-up parsers. It's just that it makes it difficult for a certain common class of bottom-up parsers, namely the LALR(k) for fixed k parser generators, to parse.
Personally, since I'm not responsible for any ES/JS implementation, I don't care about this difficulty, but a quick search for "ecmascript lalr" reveals that there are such existing parsers.
P.S. Take everything I say with a grain of salt, since I'm not really an expert on parsing.
On Wed, Dec 17, 2008 at 9:53 PM, Yuh-Ruey Chen <maian330 at gmail.com> wrote:
Lex Spoon wrote:
On this list, the => form has so far been dismissed due to parsing concerns. If that's the only reason, let me try and allay that worry and put that horse back in the race. Scala also has a comma operator, but it still manages to parse the => syntax. They way it does it is to initially parse an expression and then, if it sees a =>, reinterpret what it has seen so far as a parameter list. It's an unusual parsing strategy, but it works well and the issue is localized.
I don't think anyone is suggesting that it would be too difficult to parse for bottom-up parsers. It's just that it makes it difficult for a certain common class of bottom-up parsers, namely the LALR(k) for fixed k parser generators, to parse.
Good point. Actually, though, the same sort of approach should still work. The grammar for a => entry would be something like:
atomic_expression "=>" atomic_expression
Then, the rule that assembles this parse tree into a real AST would analyze the expression on the left and either convert it to a parse tree, or emit a retroactive parse error.
I know this initially violates some design sense--it does mine!--because normally a parser rule reuses subexpressions without change. However, in this case it works out well, and so I think the rule of thumb is misleading. This implementation technique should be simple, localized, easy to understand, and as a result robust.
Lex
On Dec 17, 2008, at 1:42 PM, Lex Spoon wrote:
I can share some history for the => form. It's disconcerting that everyone associates it with C#, because they are open about copying the syntax from Scala.
It's for Ecma solidarity -- we are indirectly boosting another Ecma
standard (C# is ECMA-334, IIRC), albeit at the expense of Scala :-P.
Ok, enough of that (we really do not try to align Ecma language
standards, which include Eiffel!). Thanks for the precedent correction.
On this list, the => form has so far been dismissed due to parsing concerns. If that's the only reason, let me try and allay that worry and put that horse back in the race. Scala also has a comma operator, but it still manages to parse the => syntax. They way it does it is to initially parse an expression and then, if it sees a =>, reinterpret what it has seen so far as a parameter list. It's an unusual parsing strategy, but it works well and the issue is localized.
I called the => syntax "no-go" for JS in
esdiscuss/2008-December/008352
cites two general arguments.
First, parsing top-down and then revising the AST based on right
context is do-able -- but the "This can get ugly" remark is meant to
suggest that it's costly compared to choosing a grammar that avoids
the ambiguity.
On the plus side, destructuring in JS1.7, proposed for Harmony to
broad agreement, requires similar revision:
[p, q] = [q, p];
swaps p and q, but it starts like
[p, q];
(and of course could be nested anywhere an assignment expression could
occur).
Let's say we can overcome this objection, by selling the benefit to
the users over the cost to implementors (and users, in minor code
footprint; slippery slope hazard here, otherwise it's not a big cost).
I'm not confident this assumption will hold in committee -- need to
get Waldemar's reaction, at least -- but for now let's just say :-).
The second argument is that the issue may not be localized, especially
in light of automatic semicolon insertion. A counter-example adapted
from the "The trouble with ambiguous grammars" thread:
a => a ? f : x++ (0);
A function expression in JS today is a primary expression, e.g.
var f = function (a) { return a ? f : x++; } (0);
so lambda users might expect the same precedence.
If the grammar is something like this:
AssignmentExpression: ... | identifier '=>' AssignmentExpression | '(' parameters ')' '=>' AssignmentExpression
(which AFAICT from web C# 3.0 grammars is what C# does) then we may be
ok. We'd need to check carefully.
Mono C# seems to have to bend over backwards to parse C# lambdas:
tirania.org/blog/archive/2007/Feb-15.html
but I don't see why a bottom-up parser can't decide quite late,
compared to a top-down parser, that it has a lambda parameter list and
not a parenthesized expression.
Perhaps we dismissed the => syntax too quickly, but we need a checked
bottom-up grammar. It's not enough to assert locality, unfortunately,
given the existing grammar and the complexity of automatic semicolon
insertion.
"^" also has a slight resemblance to the greek lambda, which is the
reason Haskell uses "".
As an aside, the circumflex is actually the precursor to lambda:
"We end this introduction by telling what seems to be the story how
the letter 'λ' was chosen to denote function abstraction. In Principia
Mathematica the notation for the function f with f(x) = 2x + 1 is
^
2x + 1.
Church originally intended to use the notation
^
x.2x+1.
The typesetter could not position the hat on top of the x and placed
it in front of it, resulting in
^x.2x + 1. Then another typesetter changed it into λx.2x + 1."
-- H. Barendregt, The Impact of the Lambda Calculus In Logic and
Computer Science [1]
Dave
[1] citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.26.7908
At the TC39 meeting two weeks ago in Kona, we had a brief bikeshedding
discussion about lambda syntax and why it matters. Observation: blocks
in Smalltalk being lightweight means users don't mind writing them for
control abstractions, compared to JS functions in ES3. In Smalltalk,
ignoring JS, it's hard to beat [ and ] as overhead, although one must
count the message selector and its punctuation too.
Allen Wirfs-Brock put his proposal, which will not shock you who know
Smalltalk or Allen, on the whiteboard:
// Instead of lambda (a, b, c) { ... }, why not: { |a, b, c| ... } ?
I then started to write an example of return to label, and in need of
a nested lambda, got stuck for a split second trying to write
function^H^H^H^H^H^H^H^lambda. After thinking 0.3 more seconds I then
said "I will use Allen's proposed syntax". Pure win, readers and the
writer (me) agreed.
I think someone proposed pretty much the same syntax here on es*- discuss within the last two years, but I can't find that message at
the moment.
Bikeshed color is secondary to semantics, but lambda conciseness does
matter. I think Allen's homage to Smalltalk in JS wins. Every time I
reach for more verbose syntax my hand steers back to those ||
delimiters.
Am I an old Smalltalk fan? Sure, I have Byte magazine with the
balloons on the cover still (in a box somewhere; mildewed, sadly). I'm
the C hacker who took the "make it look like Java" orders and made it
look like C with some awk, Self, Scheme, and even HyperCard
(HyperTalk, actually) influences.
Eclecticism is not an end, but it could be a means to a better end
than a cramped non-eclectic grammar, if the deeper reasons for concise
lambda syntax are sound and valid. Syntax is for users, it must be
usably sweet. It's not all about theoretical completeness and
minimality.
Anyway, we need a fun weekend thread, and everyone loves syntax.
Comments? Huzzahs? The latter go to Allen. Go nuts.