Short Functions

# Isaac Schlueter (14 years ago)

I very much want short functions. However, the semantics of JavaScript lambdas are so wonderfully simple.

Adding a semantically different callable thing is a huge mistake, in my opinion. I'd love to be able to write

{|a, b, c|  a + b * c }

or even

(a, b, c) -> { a + b * c }

or

#(a, b, c) { a + b * c }

instead of

function (a, b, c) { return a + b * c }

but I really don't want to replace our existing simple semantics.

I see a lot of the discussions of short function syntax seeming to imply ruby-block semantics, with the "return returns from the parent" idea. It would be great to separate those two concerns. They are very different. One is sugar, the other is a much more radical change to the way the language works, which I'm not altogether convinced is a good or useful thing.

# Brendan Eich (14 years ago)

On May 21, 2011, at 5:28 PM, Isaac Schlueter wrote:

function (a, b, c) { return a + b * c }

but I really don't want to replace our existing simple semantics.

Nothing is replaced.

You mean adding new semantics for new syntax is like replacing current semantics with current + new, which is too much?

I see a lot of the discussions of short function syntax seeming to imply ruby-block semantics, with the "return returns from the parent" idea. It would be great to separate those two concerns. They are very different. One is sugar, the other is a much more radical change to the way the language works, which I'm not altogether convinced is a good or useful thing.

It's essential to the control abstraction programmability. Take a look at the examples. You want those block-looking things to support Tennent's Correspondence Principle. Return from a block in JS today returns from the enclosing function. Same for return from a block-lambda in a function. It's an early error to return from global code, and will be in a block-lambda in global code.

Why is this so bad? JS programmers return, break, and continue from blocks all the time.

# Alex Russell (14 years ago)

On May 21, 2011, at 5:56 PM, Brendan Eich wrote:

On May 21, 2011, at 5:28 PM, Isaac Schlueter wrote:

function (a, b, c) { return a + b * c }

but I really don't want to replace our existing simple semantics.

Nothing is replaced.

You mean adding new semantics for new syntax is like replacing current semantics with current + new, which is too much?

I see a lot of the discussions of short function syntax seeming to imply ruby-block semantics, with the "return returns from the parent" idea. It would be great to separate those two concerns. They are very different. One is sugar, the other is a much more radical change to the way the language works, which I'm not altogether convinced is a good or useful thing.

It's essential to the control abstraction programmability. Take a look at the examples. You want those block-looking things to support Tennent's Correspondence Principle. Return from a block in JS today returns from the enclosing function. Same for return from a block-lambda in a function. It's an early error to return from global code, and will be in a block-lambda in global code.

Why is this so bad? JS programmers return, break, and continue from blocks all the time.

I think you're over-playing the use of blocks in the syntactic sense. My observation has been that when people use them, it's primarily accidental. Citing that feels like weak ground to argue from.

-- Alex Russell slightlyoff at google.com slightlyoff at chromium.org alex at dojotoolkit.org BE03 E88D EABB 2116 CC49 8259 CF78 E242 59C3 9723

# Brendan Eich (14 years ago)

On May 21, 2011, at 6:13 PM, Alex Russell wrote:

On May 21, 2011, at 5:56 PM, Brendan Eich wrote:

On May 21, 2011, at 5:28 PM, Isaac Schlueter wrote:

function (a, b, c) { return a + b * c }

but I really don't want to replace our existing simple semantics.

Nothing is replaced.

You mean adding new semantics for new syntax is like replacing current semantics with current + new, which is too much?

You overcited, I'll use it as an opportunity to say that function + return are still there. If short function syntax without block semantics is wanted, then we're back to -> or # or something like that, and it only shortens 'function' -- it does not avoid 'return' because several TC39ers object to the completion value leak hazard.

Some of the objectors drop this objection with block lambdas. The reasons are that block lambdas have new syntax that favors brevity, expression body in the braces; or that using blocks to build control abstractions in a statement language does not create any completion value leak, because the block's caller ignores the completion value of the block (the block may explicitly 'return' but that returns from the enclosing function.

Anyway, there's a difference between block-lambdas and shorter function syntax that goes beyond the added semantics. Shorter function syntax will never save 6+1-or-2 (space or left paren after return. requiring right paren at end) characters, not so long as the completion value leak hazard is a worry -- and it is for functions.

I see a lot of the discussions of short function syntax seeming to imply ruby-block semantics, with the "return returns from the parent" idea. It would be great to separate those two concerns. They are very different. One is sugar, the other is a much more radical change to the way the language works, which I'm not altogether convinced is a good or useful thing.

It's essential to the control abstraction programmability. Take a look at the examples. You want those block-looking things to support Tennent's Correspondence Principle. Return from a block in JS today returns from the enclosing function. Same for return from a block-lambda in a function. It's an early error to return from global code, and will be in a block-lambda in global code.

Why is this so bad? JS programmers return, break, and continue from blocks all the time.

I think you're over-playing the use of blocks in the syntactic sense.

I'm not doing anything here but asking why return from a block is ok but return from a block-lambda having the same semantics (modulo throwing in the case the block-lambda escaped and was called after the enclosing function's activation, in which the block-lambda was evaluated, has deactivated) is not ok.

My observation has been that when people use them, it's primarily accidental. Citing that feels like weak ground to argue from.

When people use blocks meaning statement-lists in braces, it's accidental?

No, people use them to join two statements that run in order. That's how you compose for sequencing. This may be incidental to the question of block-lambdas, but it's not accidental.

Some style guides and tools even want braces around every sub-statement, even the shortest if (unlikely) { break; } consequent. That's not required by C-like languages but it actually plays well with block-lambdas, at a style or coding convention level.

Again, my point was not to argue "for" blocks. I was (pretty clearly :-/) probing why return from a block, where the return is the last statement in a necessarily-sequenced group of statements in the block, is ok; but return from a block-lambda should be different.

The possibility of block-lambdas escaping downward-funarg flows and being called later, and getting runtime exceptions due to return from a returned function activation, can't be it. Exceptions happen in dynamic languages. Changing return in a block-lambda to return from the lambda just makes a different kind of problem: that return from a block differs (loss of Tennent correspondence).

Really, we're between these choices:

  1. Do nothing, function + return 4ever.

  2. Shorter function syntax by some abbreviation for 'function' that still requires 'return ' followed by an expression to return a value.

  3. Block-lambdas.

Some on the committee are enthusiastic about 3. 2 is warm beer and not going anywhere fast, least of all with # (a misuse of that punctuator, which is wanted for other purposes). 1 is lame.

# Bob Nystrom (14 years ago)

On Sat, May 21, 2011 at 6:39 PM, Brendan Eich <brendan at mozilla.com> wrote:

Really, we're between these choices:

  1. Do nothing, function + return 4ever.

  2. Shorter function syntax by some abbreviation for 'function' that still requires 'return ' followed by an expression to return a value.

  3. Block-lambdas.

Some on the committee are enthusiastic about 3. 2 is warm beer and not going anywhere fast, least of all with # (a misuse of that punctuator, which is wanted for other purposes). 1 is lame.

Isn't there also:

  1. Shorter function syntax that allows a single expression body which doesn't require return, but if it has a return, it retains the same semantics as current JS functions.

That was my understanding of how -> functions would work:

log(-> 'hi')()) // 'hi', no return required.

function foo() { let bar = (-> { return 'bar'; })(); return 'not bar'; }

log(foo()); // 'not bar'

Isn't that what the current proposal is? I'm not arguing for or against this, just trying to understand our options.

Personally, I'm having trouble figuring out what my opinion is on how return (et. al.) should act inside a block lambda. I can see it making sense either way:

function sortByName(people) { people.sort {|a, b| if (a.name == b.name) return 0; if (a.name < b.name) return -1; return 1; } }

function findProperty(path, property) { File.open(path) {|file| for (let line in file) { let [name, value] = line.split('='); if (name == property) return value; } } }

# Brendan Eich (14 years ago)

On May 21, 2011, at 11:45 PM, Bob Nystrom wrote:

Isn't there also:

  1. Shorter function syntax that allows a single expression body which doesn't require return, but if it has a return, it retains the same semantics as current JS functions.

That was my understanding of how -> functions would work:

log(-> 'hi')()) // 'hi', no return required.

Missing ( before the -> but yeah.

function foo() { let bar = (-> { return 'bar'; })(); return 'not bar'; }

log(foo()); // 'not bar'

Isn't that what the current proposal is? I'm not arguing for or against this, just trying to understand our options.

That's accurate. But I discounted arrow functions because to be usable, to have the syntax you show above, requires GLR parsing (if bottom up; top-down may be easier, haven't proven it yet).

I have big doubts about TC39 switching from LR(1). Even though left-hand-side expressions and destructuring specs would benefit from the precision.

Personally, I'm having trouble figuring out what my opinion is on how return (et. al.) should act inside a block lambda. I can see it making sense either way:

function sortByName(people) { people.sort {|a, b| if (a.name == b.name) return 0; if (a.name < b.name) return -1; return 1; } }

Nope, looks like a block (with some |...| at the front), return should act as it does in a block.

Use completion values if you like:

function sortByName(people) { people.sort {|a, b| (a.name == b.name) ? 0 : (a.name < b.name) ? -1 : 1; } }

or

function sortByName(people) { people.sort {|a, b| if (a.name == b.name) 0; else if (a.name < b.name) -1; else 1; } }

if you like.

function findProperty(path, property) { File.open(path) {|file| for (let line in file) { let [name, value] = line.split('='); if (name == property) return value; } } }

Yup. Looks like a block, return works as it does in a block.

# Claus Reinke (14 years ago)

the other is a much more radical change to the way the language works, which I'm not altogether convinced is a good or useful thing.

It's essential to the control abstraction programmability. Take a look at the examples. .. Return from a block in JS today returns from the enclosing function. Same for return from a block-lambda in a function. It's an early error to return from global code, and will be in a block-lambda in global code.

Why is this so bad? JS programmers return, break, and continue from blocks all the time.

The examples focus on inline uses of block_lambdas, but block_lambdas can do a lot more than that (or so I think - until we see more complete semantics, I have to extrapolate from the examples; perhaps the variations below are not intended). Again, that doesn't have to be bad, but the consequences need to be investigated and taken into consideration.

For instance, block_lambdas seem to include a form of generalized jump, a callable thing that does not return to the caller (Tennent mentioned such things as 'sequels', so they were probably known in Algol times, but I have not seen them implemented, short of (delimited) continuations). We just need to ensure that the callable's definition context is still active:

function A() { let ret = {|x| return x; }; // sequel let inc = {|x| x+1 }; // function let j = 0; while(true) { if (j > 3) ret(j); // leave loop, and A else j = inc(j); // continue loop } }

Calling a sequel like 'ret' is always a tail call, because it never returns to the caller, it returns elsewhere. In that sense, it is a generalized jump.

The break/continue variant of sequels also makes it possible to replace labeled breaks and continues with sequels:

A: while(true) { let continue_A = {|| continue; }; B: while (true) { let break_B = {|| break; }; C: while(true) { if (cond) continue_A(); else break_B(); } } }

The best approximation I have of what return/break/continue mean in a block_lambda is based on lexical scoping, to the nearest enclosing function or loop (but nullable: when the definition context is no longer active, return/break/continue throw a runtime error).

That means that functions and loops have implicit bindings (for return and for break/continue), and the 'break_B' example above is really analogous to the 'let self = this;' pattern: a way ot keeping the next outer implicit binding accessible. Of course, block_lambdas do not introduce this problem, they introduce a new workaround. Still, it is interesting to look at the new programming patterns that become possible.

Again, this is just guessing based on reading the examples in the strawman - it could be completely off the mark. Informal but complete semantics would help to understand what is supposed to be possible and what not. Further decisions cannot be based on syntax or selected examples alone.

Claus

# Brendan Eich (14 years ago)

On May 22, 2011, at 7:12 AM, Claus Reinke wrote:

Again, this is just guessing based on reading the examples in the strawman - it could be completely off the mark. Informal but complete semantics would help to understand what is supposed to be possible and what not. Further decisions cannot be based on syntax or selected examples alone.

Your examples all look correct at a glance. As you show, the return, break, or continue must nest within the "container" required for that statement in a regular block, as for a block-lambda. But your examples nicely show abstraction away from actual block arguments using let-bound local variables. (This will be less optimized in advanced implementations, but no worries.)

# Allen Wirfs-Brock (14 years ago)

On May 22, 2011, at 7:12 AM, Claus Reinke wrote:

For instance, block_lambdas seem to include a form of generalized jump, a callable thing that does not return to the caller (Tennent mentioned such things as 'sequels', so they were probably known in Algol times, but I have not seen them implemented, short of (delimited) continuations).

Yes, Pascal had them and so presumably did Algol 60. It was simply a goto statement whose target label was outside of the current procedure in an enclosing scope. The Pascal compiler writers I ran with called these "bad goto's" (bad in the sense that it took extra mechanism to make them work). I learned the term from people who had been involved in the compilers for the Burroughs 5500 Algol machines so I presume they were present there and in Algol in general. Too lazy right now to dig out the Algol 60 Report to check.

In Smalltalk, this is the semantics for a ^ in a block, eg [^nil] which returns from the enclosing method invocation unlike [nil] which returns from the block invocation. Ruby generalized it to deal with explicit control statements and break/continue style escapes.

We just need to ensure that the callable's definition context is still active:

Yes, for example the design sketch at the very end of lists.squeakfoundation.org/pipermail/squeak-dev/2001-April/008237.html describes an efficient way to implement this for Smalltalk blocks.

function A() { let ret = {|x| return x; }; // sequel let inc = {|x| x+1 }; // function let j = 0; while(true) { if (j > 3) ret(j); // leave loop, and A else j = inc(j); // continue loop } }

Calling a sequel like 'ret' is always a tail call, because it never returns to the caller, it returns elsewhere. In that sense, it is a generalized jump.

Yes, this exact technique is used by Smalltalk programmers

# Isaac Schlueter (14 years ago)

On Sat, May 21, 2011 at 23:07, Sami Samhuri <sami at samhuri.net> wrote:

The semantics have shortcomings though and introducing new syntax to save a few keystrokes is frivolous.

Cutting out many kilobytes of code from JavaScript programs by streamlining the single-most commonly-used idiom is hardly frivolous. In fact, I'd argue that it should be one of the main goals of this list and of TC-39.

Adding new semantics to the language seems frivolous to me, if doing so does not solve problems we actually have today. Lambda-blocks, as proposed currently, adds considerable surface area to the language without providing any real value.

The complications have not been very well explored, and are not trivial, in my opinion.

I am also unconvinced by the "looks like a block" argument. The body of a es-current function also "looks like a block", and no one seems to have a problem with it. Shall we say that "return" in a while loop should be a break, because its body "looks like a function body"?

// 1: a current function
x = someArray.map(function (x) {
    return x * x
})

// 2: a lambda-block
x = someArray.map({ |x|
    return x * x
})

// 3: a current block
for (i in x) {
    return x[i] * x[i]
}

Who's to say that the lambda-block[2] "looks like a block[3]" any more than the regular function[1]? It's extra syntax above and beyond a current block in either case, and in fact, because it can be wrapped in parens (since it's an expression), it behaves in the language much more like a function than like a block.

The fact that {||}-style blocks can be passed and assigned to variables makes it remarkably more complicated to reason about them if their return/break/continue semantics are different from existing functions. Because of the fact that they are expressions which can be manipulated, they are more akin to functions than to blocks.

The examples from Claus and Allen fill me with trepidation. Please don't do this awful thing. It is the problem with Ruby. We don't need to invent new versions of "for" and "while" in every program. JavaScript's minimal semantic surface is a strength, not a shortcoming.

At the very least, the implications of this semantic change need much much more exploration than the other short-function proposals. I realize that it's exciting, but the best footguns typically are.

# Brendan Eich (14 years ago)

On May 22, 2011, at 12:18 PM, Isaac Schlueter wrote:

On Sat, May 21, 2011 at 23:07, Sami Samhuri <sami at samhuri.net> wrote:

The semantics have shortcomings though and introducing new syntax to save a few keystrokes is frivolous.

Cutting out many kilobytes of code from JavaScript programs by streamlining the single-most commonly-used idiom is hardly frivolous.

The counterargument, I don't share it but you seem to be focused on bandwidth, is transfer-encoding compression, gzip.

In fact, I'd argue that it should be one of the main goals of this list and of TC-39.

We should have short syntax as a main goal? That hits return too. It arguably means block lambdas, since they are more concise and more expressive. But you demur from them below, so brevity can't be the top "main goal".

Adding new semantics to the language seems frivolous to me, if doing so does not solve problems we actually have today. Lambda-blocks, as proposed currently, adds considerable surface area to the language without providing any real value.

Please stop asserting and generalizing. The strawman is smaller in syntax than arrow functions. The semantics reuse a lot of existing machinery and I'm not done yet. It's not fair to make unquantified assertions based on taste.

Many hackers testify to the "real value" of block-lambdas in Smalltalk, Ruby, and E. Let's not put them down by asserting only.

The complications have not been very well explored, and are not trivial, in my opinion.

They have been explored in other dynamic languages. They're not that bad. But apart from my "other languages" counterpoint, which matters, we're just pushing opinions at each other.

I am also unconvinced by the "looks like a block" argument. The body of a es-current function also "looks like a block",

No. That's not the relevant comparison. The issue is whether the whole function-or-block-lambda-expression "looks like a block".

If you always write

a.map(function (e) { return e*e; })

then of course return must mean what it has in ES1-5.

If you extend the language to support

a.map {|e| e*e}

then the appearance of function is nowhere in evidence, so completion value as return value and 'return' returning from the outer function are possible to specify. We are not violating an expectation or principle of least surprise based on function syntax.

Indeed in a similar context, e.g.

function f(a, fail) { let b = []; for (let i = 0; i < a.length; i++) { let e = a[i]; if (e === fail) return null; b.push(e*e); } return b; }

the return in the block body of the for loop uses known syntax and semantics, the block-lambda form:

function f(a, fail) { return a.map {|e| if (e === fail) return null; e*e; } }

reuses that return-in-block knowledge. Yes, the head a.map differs from a longer-winded for (;;) loop (with let b = [] etc.). That's true but it does not alter the argument about return-in-block. It just shows how much shorter the block-lambda version is, bonus.

That block-lambdas look like blocks not function expressions is pretty much the whole point. I don't think you addressed it.

Yes, block-lambdas evaluate to values so can be called later, with novel semantics coming into play to build novel programs and control abstractions. That is progress by some lights.

A reductionistic argument that we can't add any new semantics doesn't cut it. There has to be some specific downside beyond "zomg new!" I'm not saying you are reacting that way, but missing the looks-like-function-expression vs. looks-like-block point by singling out the block body of a function does not cut it.

Finally, a word on footguns. JS has some big ones, fixed in Harmony or already in ES5 strict. We cannot call Smalltalk-like blocks footguns based on the history of Smalltalk, though. Same goes for E (far fewer users, but relevant testimony from MarkM). I believe the same goes for Ruby, in spite of the messier block/proc/lambda complexities.

So where's the beef? Trepidation should be addressed by user testing. We'll do that. Meanwhile, we can consider block-lambdas for Harmony and even (I argue) for ES.next, spec'ing and prototyping to user-test as we iterate on that edition.

# Brendan Eich (14 years ago)

On May 22, 2011, at 4:05 PM, Brendan Eich wrote:

That block-lambdas look like blocks not function expressions is pretty much the whole point. I don't think you addressed it.

To reverse the argument you made: if function body looking like a block were a source of confusion, we'd already have trouble:

function outer() { var answer; if (life_universe_everything) { answer = function () { return 42; } } else {...} return answer; }

But people do not read the return above as returning from function outer. Why not? Not sure, maybe they learned the hard way. More likely, the distinctive function () head syntax before the body block made it clear.

Distinctiveness is important. This is why I argue anything with TCP purity about break, return, continue, |this|, and arguments should look quite unlike a function expression. In conjunction with the control absraction support idea that wants paren-free block-argument-bearing calls, it favors something very much like block lambdas (or lighter, if possible).

But anyway, function bodies are not mistaken for blocks. Block lambdas are meant to look like blocks, in a good way. They could be abused, for sure. That's not sufficient reason to reject 'em, though.

# Isaac Schlueter (14 years ago)

On Sun, May 22, 2011 at 16:05, Brendan Eich <brendan at mozilla.com> wrote:

Cutting out many kilobytes of code from JavaScript programs by streamlining the single-most commonly-used idiom is hardly frivolous.

The counterargument, I don't share it but you seem to be focused on bandwidth, is transfer-encoding compression, gzip.

I think you and I agree on this point. Syntax should be judged based on the costs to fingers and brains. In this regard, I think that writing and reading a lot of bytes for a frequent idiom is a bad thing. It's about bandwidth across neurons, not wires.

Towards that end, I see a lot of promise in the arrow-function proposal.

In fact, I'd argue that it should be one of the main goals of this list and of TC-39.

We should have short syntax as a main goal? That hits return too.

Indeed it does! That being said, implicit returns are a bit thornier problem for their security implications.

It arguably means block lambdas, since they are more concise and more expressive. But you demur from them below, so brevity can't be the top "main goal".

I don't think that brevity should be the primary goal of Harmony or es-next, no. But I do think it should be an important consideration.

Brevity is not a value in itself. It is a value when the shorter syntax reduces the overall cognitive burden. Code that is easier for humans to think about, tends to also be easier for humans to not mess up.

The complications have not been very well explored, and are not trivial, in my opinion.

They have been explored in other dynamic languages. They're not that bad.

Yes, I guess we are pushing opinions at each other. I'd say that they are that bad.

I have experience only with Ruby, not SmallTalk or E. Maybe they're handled better in the other two?

I am also unconvinced by the "looks like a block" argument.  The body of a es-current function also "looks like a block",

No. That's not the relevant comparison. The issue is whether the whole function-or-block-lambda-expression "looks like a block".

That was the point I was trying to make. Having block-lambdas behave like functions is not a hazard. "||" is enough of a differentiator, just like "function ()" is, only with a little less to look at.

On Sun, May 22, 2011 at 16:36, Brendan Eich <brendan at mozilla.com> wrote:

To reverse the argument you made: if function body looking like a block were a source of confusion, we'd already have trouble:

I'm not saying it's a source of confusion. I'm saying "this callable thing is dramatically different than that other callable thing" is a bigger hazard than "this curly-brace is dramatically different from that other curly-brace".

But people do not read the return above as returning from function outer. Why not?

Perhaps because callables in JavaScript are all the same, and only ever return out of themselves, never their parent scope. (Of course, ctors called with "new" aren't quite identical to normal functions, because of the implicit "return this". But that is a determined by the call style, not by the function type, and is a much smaller difference.)

More likely, the distinctive function () head syntax before the body block made it clear.

As |arg| would as well.

My point there was that "looks like a block" is something of a stretch as an argument for or against anything. Object-literals look like blocks with labelled lines, but we don't seem to have any problems with those (except when we do, of course; qv. every JS repl.)

Here's the sort of thing that I'm driving at:

function abc (cb) { if (Math.random() < 0.5) cb(100) } function xyz () { abc( {|x| return x } ) return 10 } var p = xyz()

The fact that a conditional in abc() can trigger a return in xyz, without transferring control back to the calling function first, seems surprising to me.

The situation is further complicated when dealing with asynchronous code. If I'm not mistaken, the following will throw if readyToGo() returns false, yes?

function abc (cb) { if (readyToGo()) cb() else setTimeout(cb, 100) } function xyz () { abc( {|x| return x } ) return 10 } var p = xyz()

Changes to flow control semantics have subtle implications. Perhaps user-testing will bear out that my taste is in the minority, and that the abuses are lesser or no worse than what we already deal with, I don't know.

# Brendan Eich (14 years ago)

On May 22, 2011, at 5:21 PM, Isaac Schlueter wrote:

That was the point I was trying to make. Having block-lambdas behave like functions is not a hazard. "||" is enough of a differentiator, just like "function ()" is, only with a little less to look at.

Ok, thanks.

On Sun, May 22, 2011 at 16:36, Brendan Eich <brendan at mozilla.com> wrote:

To reverse the argument you made: if function body looking like a block were a source of confusion, we'd already have trouble:

I'm not saying it's a source of confusion. I'm saying "this callable thing is dramatically different than that other callable thing" is a bigger hazard than "this curly-brace is dramatically different from that other curly-brace".

Yet callables can do all sorts of things in JS today, via host objects. Rhino long ago, via the Cocoon project, gained a Continuation object, object-oriented call/cc!

Calling block-lambda expressions with the paren-free form is pretty tame and well-behaved so long as the downward block-lambda funargs don't escape to outlive the call. That's not the case you are worried about, from what you write above.

The case where a block is passed down less literally, via a local variable, and that block can force a return, is new if you're not using Rhino with Continuations. But it's strictly less powerful than call/cc. You can only return from the function containing the block-lambda expression. You can't force some other function to return.

Here's the sort of thing that I'm driving at:

function abc (cb) { if (Math.random() < 0.5) cb(100) } function xyz () { abc( {|x| return x } ) return 10 } var p = xyz()

The fact that a conditional in abc() can trigger a return in xyz, without transferring control back to the calling function first, seems surprising to me.

You should try call/cc in full in languages that support it :-P.

Yes, it's a novelty to some (many, most? could be) JS hackers. It's not as wild as full call/cc. It is a bigger shift than generators, where the yield operator must be used to make a preemption point (this came up on twitter the other week -- some confusion about generators, but there's no way for any function call f() to suspend the calling code -- you would need yield f()).

So agreed, new semantics. The people who support this acknowledge the new + bigger aspects here and argue that giving users the ability to build control abstractions, using lighter syntax than function, with full Tennent correspondence, is worth it.

I hope we won't have to thumb-wrestle this week to settle this.

Hate to say it, but I wrote it already in reply to Bob Nystrom: arrow function syntax is not going to thumb- or arm-wrestle its way to victory given the GLR parsing requirement. IMHO, sad to say. I'll give it my best shot.

# Brendan Eich (14 years ago)

On May 22, 2011, at 5:21 PM, Isaac Schlueter wrote:

Yes, I guess we are pushing opinions at each other. I'd say that they are that bad.

I have experience only with Ruby, not SmallTalk or E. Maybe they're handled better in the other two?

Could you say more than "that bad", precisely how and why and wherefore, an example or three, about your Ruby experience?

I understand the block vs. Prox.new/&param vs. lambda differences, we aren't copying any of that.

# Jason Orendorff (14 years ago)

On Sun, May 22, 2011 at 7:35 PM, Brendan Eich <brendan at mozilla.com> wrote:

On May 22, 2011, at 5:21 PM, Isaac Schlueter wrote:

Yes, I guess we are pushing opinions at each other.  I'd say that they are that bad.

I have experience only with Ruby, not SmallTalk or E.  Maybe they're handled better in the other two?

Could you say more than "that bad", precisely how and why and wherefore, an example or three, about your Ruby experience?

Oh, I disliked Ruby blocks at first because tutorial after tutorial left me with no real understanding of the feature. I was totally unable to predict what would happen in any slightly unusual case. I had to try it and see. I blame myself for looking at tutorials when I should've been looking at source code, but I also blame the tutorials for taking the attitude "and if you call a block after the enclosing method exits, um.... hey look, cartoon foxes!"

Today I understand continuations better. I would be able to guess the answers (with fewer trials, not zero) and I wouldn't be so frustrated. Not everybody has that background though.

Another possible gripe is that Ruby blocks are syntactically special. They're not expressions. [1, 2, 3].collect {|i| i + 1} # ok x = {|i| i + 1} # syntax error This compounds the first problem by making them a bit harder to play with in the REPL. In Smalltalk, as in your straw-man, blocks are just expressions. That seems much better--but I'd like to know why Ruby went the other way.

# Waldemar Horwat (14 years ago)

On 05/21/11 23:53, Brendan Eich wrote:

That's accurate. But I discounted arrow functions because to be usable, to have the syntax you show above, requires GLR parsing (if bottom up; top-down may be easier, haven't proven it yet).

GLR parsing would be wild in ECMAScript due to the fact that the lexer is dependent on the parser's current state. A GLR parser is working on a quantum superposition of multiple states in parallel, so if it encounters a / then some of the superposed states may direct the lexer to interpret it as a division symbol while others direct it to start scanning a regular expression. So now you need a quantum entanglement of lexers corresponding to the superposed parser states the GLR parser is considering. Semicolon insertion would also be be forced into quantum entanglement with the superposed parser states.

Do we really want to go there?

 Waldemar
# Kam Kasravi (14 years ago)

I've been experimenting with PEG/packrat parsers and how well they do on the ecmascript grammar. Since these do not use lexers and are LL(n) they may be a better fit. There are a few implementations out there written in JS - ometa and pegjs come to mind. They also are a good fit for transpilers though I realize this is not a TC39 goal.

# Brendan Eich (14 years ago)

On May 23, 2011, at 2:09 PM, Waldemar Horwat wrote:

On 05/21/11 23:53, Brendan Eich wrote:

That's accurate. But I discounted arrow functions because to be usable, to have the syntax you show above, requires GLR parsing (if bottom up; top-down may be easier, haven't proven it yet).

GLR parsing would be wild in ECMAScript due to the fact that the lexer is dependent on the parser's current state. A GLR parser is working on a quantum superposition of multiple states in parallel, so if it encounters a / then some of the superposed states may direct the lexer to interpret it as a division symbol while others direct it to start scanning a regular expression. So now you need a quantum entanglement of lexers corresponding to the superposed parser states the GLR parser is considering. Semicolon insertion would also be be forced into quantum entanglement with the superposed parser states.

Do we really want to go there?

Probably not -- you don't, I will bet a donut! ;-) Hence my discounting of arrow function syntax as a likely-to-succeed strawman, in reply to Bob's hope for it.

# Brendan Eich (14 years ago)

On May 23, 2011, at 2:55 PM, Kam Kasravi wrote:

I've been experimenting with PEG/packrat parsers and how well they do on the ecmascript grammar. Since these do not use lexers and are LL(n) they may be a better fit. There are a few implementations out there written in JS - ometa and pegjs come to mind.

Mark and Tom used Ometa for code.google.com/p/es-lab -- slo-o-o-o-wwwww.

They also are a good fit for transpilers though I realize this is not a TC39 goal.

We need to consider browser implementations, which must lex and parse quickly, and which all (AFAIK) use hand-written lexers and parsers.

# Kam Kasravi (14 years ago)

On May 23, 2011, at 4:08 PM, Brendan Eich <brendan at mozilla.com> wrote:

On May 23, 2011, at 2:55 PM, Kam Kasravi wrote:

I've been experimenting with PEG/packrat parsers and how well they do on the ecmascript grammar. Since these do not use lexers and are LL(n) they may be a better fit. There are a few implementations out there written in JS - ometa and pegjs come to mind.

Mark and Tom used Ometa for code.google.com/p/es-lab -- slo-o-o-o-wwwww.

Yeah it has no memoization. Pretty cool nevertheless as far as what he was able to do. Though I really like what Dave is doing on narcissus.

# David Herman (14 years ago)

Mark and Tom used Ometa for code.google.com/p/es-lab -- slo-o-o-o-wwwww.

Yeah it has no memoization. Pretty cool nevertheless as far as what he was able to do. Though I really like what Dave is doing on narcissus.

No credit to me on the Narcissus parser; it was originally written by Brendan, with I think a bunch of follow-on work by Patrick Walton.

# David Herman (14 years ago)

IANA Rubyist, but I think the goal was for blocks to be downwards-only, so that upvars could live on the stack and everything could be nice & speedy. So they had to syntactically restrict blocks to enforce that they couldn't outlive the frame in which they were created.

As Brendan says, that's just restricting expressiveness to make optimization easier. We don't need to do that, and if we work a little harder, we can still stack-allocate in many cases.

# Tom Van Cutsem (14 years ago)

Mark and Tom used Ometa for code.google.com/p/es-lab, code.google.com/p/es-lab -- slo-o-o-o-wwwww.

Yeah it has no memoization. Pretty cool nevertheless as far as what he was able to do. Though I really like what Dave is doing on narcissus.

OMeta does do memoization, but it's still slow ;-) Actually, while Mark and I were writing the ES5 grammar in Ometa, we did discover that it didn't memoize parameterized rules (which we needed for dealing with the 'NoIn' productions). Based on our use case, Alex Warth gladly implemented a limited form of memoization for parameterized rules: < tinlizzie.org/ometa-js/#Memoizing_Parameterized_Rules>

# Lasse Reichstein (14 years ago)

On Sun, 22 May 2011 02:56:28 +0200, Brendan Eich <brendan at mozilla.com>

wrote:

Why is this so bad? JS programmers return, break, and continue from
blocks all the time.

Just dawned on me that there is a significant difference.

JS blocks are executed where they are lexically located. If in a loop,
they are executed sequentially. You can see their control flow scope (e.g., any
local try/catch/finally blocks).

When you make blocks into first-class values (and even throw in
parameterization), you can get blocks being re-entered (e.g., being called recursively), and they can
be called in scope of a finally that you can't see.

Example (using simple block syntax)

function contains(collection, y) { collection.forEach({|x| if (x == y) return true; }); return false; }

but if Collection.forEach is, e.g.,:

function forEach(block) { for (var i = 0; i < this.length; i++) { try { block(this[i]); } finally { // Always iterate to the end! continue; } } }

then what is "obviously" a "local return" in the contains function, isn't
actually doing what it's expected to. In that sense, the block is like a function, and thinking of it as a block
is likely to lead to surprises because they lack the statically detectable runtime
context of blocks.

# Brendan Eich (14 years ago)

On May 25, 2011, at 5:30 AM, Lasse Reichstein wrote:

On Sun, 22 May 2011 02:56:28 +0200, Brendan Eich <brendan at mozilla.com> wrote:

Why is this so bad? JS programmers return, break, and continue from blocks all the time.

Just dawned on me that there is a significant difference.

You're right, in the case you show return from a block-lambda acts like an uncatchable thrown exception. Some languages even model their control statements that way.

Still, the well-behaved block-lambda use-case is block-like compared to function expressions.

Example (using simple block syntax)

function contains(collection, y) { collection.forEach({|x| if (x == y) return true; }); return false; }

but if Collection.forEach is, e.g.,:

function forEach(block) { for (var i = 0; i < this.length; i++) { try { block(this[i]); } finally { // Always iterate to the end! continue; } } }

then what is "obviously" a "local return" in the contains function, isn't actually doing what it's expected to. In that sense, the block is like a function, and thinking of it as a block is likely to lead to surprises because they lack the statically detectable runtime context of blocks.

"likely" is not for sure. Libraries (e.g. the Array extras such as forEach) are generally not written with finally continuing like that, even in the face of an exception.

js> function forEach(block) { for (var i = 0; i < this.length; i++) { try { block(this[i]); } finally { // Always iterate to the end! continue; } } } js> a = [1,2,3] [1, 2, 3] js> a.forEach = forEach

function forEach(block) {for (var i = 0; i < this.length; i++) {try {block(this[i]);} finally {continue;}}} js> a.forEach(function (x) {print(x); throw 42})

1 2 3

This is kind of a trick function. It should be unlikely in practice, among popular libraries.

But you're right, there are new control effects with block-lambdas not possible with blocks, much more like functions that throw. I've been spelling out block-lambda to avoid these being called "blocks" but I'm probably fighting a strong tide.

If users and library authors build functional helpers well, then these "blocks" as proposed probably will not bite back so strangely. That seems have been the case in Smalltalk and Ruby and E.

# David Griffiths (14 years ago)

To emphasize the point... we need meat, not sugar.

And even then, maybe not even the meat.

On 22/05/2011, at 10:28 AM, Isaac Schlueter wrote:

I very much want short functions. However, the semantics of JavaScript lambdas are so wonderfully simple.

Adding a semantically different callable thing is a huge mistake, in my opinion. I'd love to be able to write

{|a, b, c| a + b * c }

or even

(a, b, c) -> { a + b * c }

or

#(a, b, c) { a + b * c }

instead of

function (a, b, c) { return a + b * c }

but I really don't want to replace our existing simple semantics.

I see a lot of the discussions of short function syntax seeming to imply ruby-block semantics, with the "return returns from the parent" idea. It would be great to separate those two concerns. They are very different. One is sugar, the other is a much more radical change to the way the language works, which I'm not altogether convinced is a good or useful thing.

# Allen Wirfs-Brock (14 years ago)

On May 25, 2011, at 7:57 AM, Brendan Eich wrote:

On May 25, 2011, at 5:30 AM, Lasse Reichstein wrote:

"likely" is not for sure. Libraries (e.g. the Array extras such as forEach) are generally not written with finally continuing like that, even in the face of an exception.

js> function forEach(block) { for (var i = 0; i < this.length; i++) { try { block(this[i]); } finally { // Always iterate to the end! continue; } } } ...

This is kind of a trick function. It should be unlikely in practice, among popular libraries.

But you're right, there are new control effects with block-lambdas not possible with blocks, much more like functions that throw. I've been spelling out block-lambda to avoid these being called "blocks" but I'm probably fighting a strong tide.

If users and library authors build functional helpers well, then these "blocks" as proposed probably will not bite back so strangely. That seems have been the case in Smalltalk and Ruby and E

I have to say that in all my years doing Smalltalk development and supporting Smalltalk programmers I don't recall every encountering a bug like this. Smalltalk has both the equivalent of block returns and a "finally" mechanism (which I designed). I'm pretty sure that if this was a common problem I would have heard about it.

Basically, this is just a buggy code. The programmer knows they are calling a block that is being used as the "body" of a control abstraction method and knows that such blocks can contain non-local returns. They also know the normal user expectation for such such returns. If given all that they still have a good reason for doing it, then so be it. But I think that it would be highly unlikely to actually occur as a bug in a forEach method.

More generally, I think this type of abnormal exit from a finally should always be subject to serious scrutiny if you see one. The stack is being unwound for a reason. There may be an exception handler out there above you on the all stack that really needs to handle the exception that was thrown by the block. What makes this foreach think it is ok to unconditionally ignore it.