Concurrency support?

# Neil Mix (19 years ago)

I've been reading the es4 pages and I'm really excited by the
proposed language enhancements. I'm looking forward to writing great
software with this new language version.

I'm a little disappointed to see that there's no plans to address
concurrent programming. Is there any reason that concurrency is not
specifically being addressed at this time?

FWIW, concurrency support (or more specifically, the ability to yield
while waiting for an event) is easily the top item in my list of
enhancement requests for the JavaScript language. I've been building
large-ish client-side "AJAX" applications for several years now, and
I find it painfully difficult to debug asynchronous callbacks and
read asynchronous code. My wife, in particular, would like to see
concurrency in JavaScript so that she might have normal conversations
with me the end of my workday.

Any chance that concurrency support can be put (or already is) on the
radar?

# Brendan Eich (19 years ago)

On Jun 25, 2006, at 6:30 PM, Neil Mix wrote:

FWIW, concurrency support (or more specifically, the ability to
yield while waiting for an event) is easily the top item in my list
of enhancement requests for the JavaScript language. I've been
building large-ish client-side "AJAX" applications for several
years now, and I find it painfully difficult to debug asynchronous
callbacks and read asynchronous code. My wife, in particular,
would like to see concurrency in JavaScript so that she might have
normal conversations with me the end of my workday.

See developer.mozilla.org/es4/proposals iterators_and_generators.html, apologies for the gaps there. They
will be filled in shortly.

The idea in emulating Python 2.5 generators is that you can coroutine
your code -- you can use a standard Ajax library to map a single
function that contains yield expressions across a series of
asynchronous callbacks, without having to break that function up into
a bunch of callback functions.

# Chris Double (19 years ago)

Any chance that concurrency support can be put (or already is) on the radar?

With the generator support being discussed for Javascript it seems it would be possible to build a simple lightweight thread library, as per this Python approach:

www-128.ibm.com/developerworks/linux/library/l-pythrd.html

It's not quite as flexible as a Narrative Javascript's continuation based approach since the 'yield' call can only be done within the generator function, not in functions called by the generator function.

I tried to do an example similar to my lightweight threads example [1] using JS 1.7's generators but struck that issue. I'm seeing if I can work around it to get the example running using generators and yield.

[1] www.bluishcoder.co.nz/2006/06/more-concurrency-in-narrative.html

Chris.

# Nicolas Cannasse (19 years ago)

FWIW, concurrency support (or more specifically, the ability to
yield while waiting for an event) is easily the top item in my list
of enhancement requests for the JavaScript language. I've been
building large-ish client-side "AJAX" applications for several
years now, and I find it painfully difficult to debug asynchronous
callbacks and read asynchronous code. My wife, in particular,
would like to see concurrency in JavaScript so that she might have
normal conversations with me the end of my workday.

See developer.mozilla.org/es4/proposals iterators_and_generators.html, apologies for the gaps there. They
will be filled in shortly.

The idea in emulating Python 2.5 generators is that you can coroutine
your code -- you can use a standard Ajax library to map a single
function that contains yield expressions across a series of
asynchronous callbacks, without having to break that function up into
a bunch of callback functions.

/be

What about full continuations support à la call/cc ? Generators are just a specific application of continuations, which are much more powerful when freely usable.

# Lars T Hansen (19 years ago)

Nicolas Cannasse writes:

FWIW, concurrency support (or more specifically, the ability to
yield while waiting for an event) is easily the top item in my list
of enhancement requests for the JavaScript language. I've been
building large-ish client-side "AJAX" applications for several
years now, and I find it painfully difficult to debug asynchronous
callbacks and read asynchronous code. My wife, in particular,
would like to see concurrency in JavaScript so that she might have
normal conversations with me the end of my workday.

See developer.mozilla.org/es4/proposals iterators_and_generators.html, apologies for the gaps there. They
will be filled in shortly.

The idea in emulating Python 2.5 generators is that you can coroutine
your code -- you can use a standard Ajax library to map a single
function that contains yield expressions across a series of
asynchronous callbacks, without having to break that function up into
a bunch of callback functions.

/be

What about full continuations support à la call/cc ? Generators are just a specific application of continuations, which are much more powerful when freely usable.

Full continuations interact in surprising ways with side effects. Slipping into Scheme for a moment, the issue is exemplified by this implementation of the map function:

(define (map f xs)
  (let loop ((acc '()) (xs xs))
    (if (null? xs)
        (reverse! acc)
        (loop (cons (f (car xs)) acc) (cdr xs)))))

Suppose f captures it continuation and that code outside the call to map invokes that continuation later. Then the result returned from the first call to map will be observably changed by this invocation:

(map (lambda (x) (+ x 1)) (list 1 2 3))

(2 3 4)

(define (fn x)

  (if (= x 2) 
      (set! c (call-with-current-continuation 
                (lambda (k) k))))
  (+ x 1))

(define v (map fn (list 1 2 3)))

v

(2 3 4)

(c 4)

v

(4 3 2 3 4)

This is surprising in the sense that this is not really the behavior you expect from map, and indeed an implementation of map that does not share structure between its intermediate data and returned data behaves more as expected. It just needs to be constructed more carefully, and will cons twice as much (or use O(n) stack).

It's not obvious that this has huge implications for the standard libraries in ECMAScript as they stand, but this type of problem will tend to make libraries more brittle in general.

The point I want to make is that we probably do not want to provide full continuations as an abstraction on which to build threads, coroutines, generators, exceptions, and so on, but instead to provide these other more controllable forms directly instead.

# Brendan Eich (19 years ago)

On Jun 26, 2006, at 4:09 AM, Lars T Hansen wrote:

The point I want to make is that we probably do not want to provide full continuations as an abstraction on which to build threads, coroutines, generators, exceptions, and so on, but instead to provide these other more controllable forms directly instead.

Thanks for the complete example. I wanted to back up Lars here by
affirming that we've discussed call/cc in TG1 meetings and come to
this conclusion, already. So this is a consensus position, as far as
I know.

Rhino (www.mozilla.org/rhino) has a call/cc implementation
from the Apache Cocoon folks, exposed as a Continuation object.
Chris Double knows this well, including a bug (www.google.com url?sa=t&ct=res&cd=4&url=http%3A%2F%2Fwww.bluishcoder.co.nz%2F2006% 2F03%2Fjavascript-partial-continuations.html&ei=AuSfRKjACoSo- gHd9_jTCQ&sig2=6ADqe10tNY06KRjiGXW46g).

So far, the bugs or limitations combined with the promise of call/cc
generality seem to me to indicate overkill, or overreach. If anyone
on this list has an example use-case of call/cc in JS that you think
is important, and that can't be mapped to coroutine-generators
(Python 2.5, PEP 342), please post it.

# Nicolas Cannasse (19 years ago)

It's not obvious that this has huge implications for the standard libraries in ECMAScript as they stand, but this type of problem will tend to make libraries more brittle in general.

The point I want to make is that we probably do not want to provide full continuations as an abstraction on which to build threads, coroutines, generators, exceptions, and so on, but instead to provide these other more controllable forms directly instead.

I agree that call/cc is not for the average user, and should be used with the appropriate care. However, while it's important to provide the different call/cc traditional usages (which you listed), it's also important to provide the low-level call/cc access for users that want to use it.

There's been a lot of creativity in the past years about the way of using continuations. I think we can except new paradigms in the future as well. As a compiler writer, I prefer to be able to directly use call/cc in the generated code and libraries instead of rewrapping and sometimes abusing the standard ES4 library.

# Brendan Eich (19 years ago)

On Jun 26, 2006, at 9:53 AM, Nicolas Cannasse wrote:

There's been a lot of creativity in the past years about the way of using continuations. I think we can except new paradigms in the future as well. As a compiler writer, I prefer to be able to directly use call/cc in the generated code and libraries instead of rewrapping and sometimes abusing the standard ES4 library.

This gets to the heart of an issue that Nicolas has discussed with me
in private email: is ES4/JS2 likely to be a high-level language used
by programmers who write large parts of their programs by hand, or is
it likely to be a mid-level or "variable level" safe target language
whose programs compilers generate based on other inputs (haXe, Google
GWT, etc.)?

It's a good question, and I don't have an uncracked crystal ball.

I have a feeling that even with the rise of compilers (which our
introduction of a new version of ES/JS may cause to become even more
common), "a lot of" future ES4 code will be written by hand, and by
people who do not need full call/cc, and who probably should not have
access to it.

# Lars T Hansen (19 years ago)

Nicolas Cannasse writes:

It's not obvious that this has huge implications for the standard libraries in ECMAScript as they stand, but this type of problem will tend to make libraries more brittle in general.

The point I want to make is that we probably do not want to provide full continuations as an abstraction on which to build threads, coroutines, generators, exceptions, and so on, but instead to provide these other more controllable forms directly instead.

I agree that call/cc is not for the average user, and should be used with the appropriate care. However, while it's important to provide the different call/cc traditional usages (which you listed), it's also important to provide the low-level call/cc access for users that want to use it.

There's been a lot of creativity in the past years about the way of using continuations. I think we can except new paradigms in the future as well. As a compiler writer, I prefer to be able to directly use call/cc in the generated code and libraries instead of rewrapping and sometimes abusing the standard ES4 library.

I don't think that being a compiler target is among the core use cases for ECMAScript 4. (Nor do I think it should be.) I also don't think that a pragmatic programming language like ECMAScript should follow Scheme, say, in providing a minimalist and powerful core on which a great variation of abstractions can be written by the initiated. Instead I think ECMAScript needs to be a rich language with features that are immediately useful to its core audiences, and which play well with each other in the sense that they have unsurprising consequences when composed.

# John Cowan (19 years ago)

Brendan Eich scripsit:

So far, the bugs or limitations combined with the promise of call/cc
generality seem to me to indicate overkill, or overreach. If anyone
on this list has an example use-case of call/cc in JS that you think
is important, and that can't be mapped to coroutine-generators
(Python 2.5, PEP 342), please post it.

I believe that the facilities of PEP 342, while necessary, is insufficient, as it does not allow subroutines invoked by a coroutine to yield for it, where some of the subroutines on the dynamic chain are coroutine-blind (or if it does, it's too subtle for me to see how).

Lua (www.lua.org ) provides a particular flavor of coroutines as its sole nonlinear control abstraction. Lua coroutines unite generators, user-level multitasking, and backtracking into a single fairly straightforward construct; I urge that they be considered.

Lua coroutines are asymmetric; that is, each coroutine returns only to its caller, not to some arbitrary coroutine (though a coroutine trampoline can overcome this restriction). Furthermore, a yield can happen after arbitrarily many nested subroutine calls within a coroutine. There's a short paper, which I strongly recommend, at www.inf.puc-rio.br/~roberto/docs/corosblp.pdf that explains the issues, the Lua coroutine syntax, a formal semantics, and points to some interesting literature, including a demonstration that coroutines are equivalent to one-shot delimited continuations; as is well known, call/cc cannot provide arbitrary delimited continuations without recompiling all uses.

Lua uses a slick implementation of its coroutines in pure C: Lua is properly tail-recursive, and each Lua coroutine keeps its own stack in the heap, but since coroutine invocation is entirely stack-like, invocation is a recursive call into the Lua interpreter, and yield is a return from the interpreter. The only restriction is that a C routine called from Lua cannot yield.

# Neil Mix (19 years ago)

On Jun 26, 2006, at 9:41 AM, John Cowan wrote:

I believe that the facilities of PEP 342, while necessary, is insufficient, as it does not allow subroutines invoked by a coroutine to yield for it, where some of the subroutines on the dynamic chain
are coroutine-blind (or if it does, it's too subtle for me to see how).

Lua (www.lua.org ) provides a particular flavor of coroutines as its sole nonlinear control abstraction. Lua coroutines unite generators, user-level multitasking, and backtracking into a single fairly straightforward construct; I urge that they be considered.

+1

I'd also point out that more powerful coroutines would be useful
whether or not JavaScript becomes a target language for compilers.
For hand-coding it makes lots of operations easier to read and
debug. For compiler targeting, it would allow compilers to
instrument code for interactive debugging against the original source.

# Graydon Hoare (19 years ago)

John Cowan wrote:

I believe that the facilities of PEP 342, while necessary, is insufficient, as it does not allow subroutines invoked by a coroutine to yield for it, where some of the subroutines on the dynamic chain are coroutine-blind (or if it does, it's too subtle for me to see how).

I've stared at PEP 342 for an hour now and cannot exactly tell.

It clearly points out this problem in the second paragraph of its "motivation" section:

 Also, generators cannot yield control while other functions are
 executing, unless those functions are themselves expressed as
 generators, and the outer generator is written to yield in response
 to values yielded by the inner generator.

I think the proposed solution is in the 3rd paragraph:

 a simple co-routine scheduler or "trampoline function" would
 let coroutines "call" each other without blocking

But I'm having a hard time picturing the meaning of that, and how it addresses the problem. I think it means that the problem is not going to be addressed directly, but indirectly. Let's work through an example, say a network server:

def http_service_loop(): while true: s = socket.accept() http_serve_connection(s)

def http_serve_connection(s): req = http_read_requests(s) f = filesystem.load_file(req.filename) s.write(f.data())

def http_read_requests(s): buf = s.readline() ...

Suppose we want this to yield any time it does something that might block on i/o, so inside the OS-level accept, read, load, and write methods. How does PEP 342 recommend we rewrite this?

I think it says that you must still structure all the functions containing generators as generators, but that the yields you sprinkle all over the intermediate calls can have yield-expression results fed back into them by an outer "trampoline" function. So I think it says we rewrite as such:

def http_service_loop(): while true: s = yield socket.accept() yield http_serve_connection(s)

def http_serve_connection(s): req = yield http_read_requests(s) f = yield filesystem.load_file(req.filename) yield s.write(f.data())

def http_read_requests(s): buf = yield s.readline() ...

Or something; I surely am getting the notation they have in mind wrong. But I think the idea is that there's to be an outer function that does something like this:

def trampoline(): x = http_service_loop() try y = x.send(None) while true: do_some_other_work_multiplexed_with_the_server_io() y = x.send(y) catch StopIteration: pass

stepping the coroutine through its work by acting as a sort of auxiliary return slot. And this would let you -- with some more code -- similarly multiplex N service loops together, keeping track of the next value to feed back into each as it's re-scheduled (putting aside the issue of a call to sleep-until-one-of-these-io-channels-has-an-event).

If this is what PEP 342 is proposing, then I must admit the lua strategy seems much more appealing: make any "yield" expression return control to the nearest dynamic "resume". That would let the low level i/o functions know about yield points, and all the logic inbetween the scheduler and the i/o functions ignore them.

So, follow-on question: what's wrong with the lua strategy? Moreover, why did the python strategy turn out this way? Did the python group just not understand the better strategy? Were they concerned about the restriction of being unable to yield through C stack frames? That seems unlikely since the same restriction probably applies to PEP 324 yields.

Maybe they were bound by semi-compatibility with the existing (and even weaker) iterator/generator scheme in earlier python versions?

# John Cowan (19 years ago)

Graydon Hoare scripsit:

If this is what PEP 342 is proposing

I think your analysis is right, though I had been hoping there was something better available.

then I must admit the lua strategy seems much more appealing: make any "yield" expression return control to the nearest dynamic "resume". That would let the low level i/o functions know about yield points, and all the logic inbetween the scheduler and the i/o functions ignore them.

Indeed.

So, follow-on question: what's wrong with the lua strategy? Moreover, why did the python strategy turn out this way? Did the python group just not understand the better strategy? Were they concerned about the restriction of being unable to yield through C stack frames? That seems unlikely since the same restriction probably applies to PEP 324 yields.

Maybe they were bound by semi-compatibility with the existing (and even weaker) iterator/generator scheme in earlier python versions?

That seems plausible. In Python without this PEP, the caller need not know whether a procedure is being invoked as a coroutine or a subroutine, whereas in Lua any procedure can be invoked either way: natively as a subroutine, or using the coroutine creation functions as a coroutine. The Python situation can be trivially emulated in Lua by creating a facade that is invoked as a subroutine and invokes the real coroutine as a coroutine.

# Brendan Eich (19 years ago)

On Jun 26, 2006, at 1:49 PM, Graydon Hoare wrote:

[Accurate summary of Python trampoline scheduler]

So, follow-on question: what's wrong with the lua strategy?
Moreover, why did the python strategy turn out this way? Did the
python group just not understand the better strategy? Were they
concerned about the restriction of being unable to yield through C
stack frames?

Yes, and that is a concern for us, for Rhino's Continuation object
implementation (which cannot cross Java native frames), as for
Python. Several TG1 members have expressed concern about having to
save and restore even N > 1 interpreted frames, IIRC. Adobe and

Opera folks, please comment.

That seems unlikely since the same restriction probably applies to
PEP 324 yields.

No, it doesn't. There is no yield across more than one level of call.

Maybe they were bound by semi-compatibility with the existing (and
even weaker) iterator/generator scheme in earlier python versions?

I don't think so, but I'd have to ask around to be sure.

# Chris Double (19 years ago)

On 6/27/06, Brendan Eich <brendan at mozilla.org> wrote:

Yes, and that is a concern for us, for Rhino's Continuation object implementation (which cannot cross Java native frames), as for Python.

There are ways around the inability to yield across C stack frames. A Jit was recently announced for Lua that supports yielding from C functions and returning to them (provided by the Coco patch):

luajit.luaforge.net/coco.html

I like the addition of generators to Javascript but not being able to yield from functions called from the generator is a pain. But that model has been in use for a while in the Python world - do they find it a practical limitation?

I am a big fan of providing the tools for people to build these sorts of things as libraries though. Native delimited continuations in Javascript would enable coroutines, etc to be added on as libraries.

Chris.

# Brendan Eich (19 years ago)

On Jun 26, 2006, at 5:09 PM, Chris Double wrote:

I am a big fan of providing the tools for people to build these sorts of things as libraries though. Native delimited continuations in Javascript would enable coroutines, etc to be added on as libraries.

If this were 1995, and we had a sound library mechanism with the
right identity and security guarantees, I would agree. But at this
point I agree with Lars: "ECMAScript needs to be a rich language with
features that are immediately useful to its core audiences, and which
play well with each other in the sense that they have unsurprising
consequences when composed."

# Bob Ippolito (19 years ago)

On Jun 26, 2006, at 2:09 PM, Chris Double wrote:

On 6/27/06, Brendan Eich <brendan at mozilla.org> wrote:

Yes, and that is a concern for us, for Rhino's Continuation object implementation (which cannot cross Java native frames), as for Python.

There are ways around the inability to yield across C stack frames. A Jit was recently announced for Lua that supports yielding from C functions and returning to them (provided by the Coco patch):

luajit.luaforge.net/coco.html

I like the addition of generators to Javascript but not being able to yield from functions called from the generator is a pain. But that model has been in use for a while in the Python world - do they find it a practical limitation?

In the Python world you don't "yield across" anything. Functions that
use yield, when called, return a generator object with a next method.
The next method executes the function until a yield or an exception [1] and returns the value yielded. Generators help out considerably
for a lot of use cases, but they're definitely not equivalent to
coroutines in any practical sense. This isn't generally considered to
be a pain, because generators aren't purporting to be coroutines and
most users aren't going to be familiar with something like call/cc
anyway.

PEP 342 turns yield into an expression that has a value or may raise
an arbitrary exception depending on what the caller does (by adding
the send and throw methods). It allows for coroutine-like behavior,
but the user has to implement it with a trampoline and write code in
a sort of communicating sequential processes style. It does not allow
for "magical" behavior like implicit cooperative threading on I/O.
From the Python design perspective, this is generally considered to
be a good thing according to EIBTI[2] ("explicit is better than
implicit").

[1] bare return and falling off the end of the function are
equivalent to "raise StopIteration". [2] www.python.org/dev/peps/pep-0020

# Brendan Eich (19 years ago)

On Jun 26, 2006, at 5:43 PM, Bob Ippolito wrote:

In the Python world you don't "yield across" anything. Functions
that use yield, when called, return a generator object with a next
method.

[Bob knows all this, but I bet others on the list don't, so I'll
elaborate here:]

This makes the returned object an iterator, so it is a subsequent
g.next() call that starts at the top of the generator function, or
that resumes "just after" the last yield expression (with value
undefined) if the generator has already been started. And for-in
loops and related structures call .next implicitly (implicit hook
calling is good when unambiguous and intentional).

g.send(v) resumes so that the send expressions result is v. You have
to g.next() or g.send(undefined) once to get things started. You can
g.throw(e) to resume as if the send expression threw e.

It does not allow for "magical" behavior like implicit cooperative
threading on I/O. From the Python design perspective, this is
generally considered to be a good thing according to EIBTI[2]
("explicit is better than implicit").

Apart from the EIBTI doctrine, and I think as important for the
members of ECMA TG1, is the practical problem of over-constraining
implementations to be able to save and restore whole native+scripted- function call stacks, not simply resume a generator function via
next, send, or throw. An optimizing compiler may have to deoptimize
based on the indirect call relation, which in the presence of
packages and eval is undecideable, so speculative code generation
with fallback would be required in full. A tiny tree-walking
interpreter may have to switch from natural host recursion to an
explicit control stack, just to support the proposed feature.

# John Cowan (19 years ago)

Brendan Eich scripsit:

Apart from the EIBTI doctrine, and I think as important for the
members of ECMA TG1, is the practical problem of over-constraining
implementations to be able to save and restore whole native+scripted- function call stacks, not simply resume a generator function via
next, send, or throw. An optimizing compiler may have to deoptimize
based on the indirect call relation, which in the presence of
packages and eval is undecideable, so speculative code generation
with fallback would be required in full. A tiny tree-walking
interpreter may have to switch from natural host recursion to an
explicit control stack, just to support the proposed feature.

I don't follow this. A tiny interpreter will have to escape from natural host recursion anyway just in order to do simple Python/Icon generators, and I don't see that yielding within C functions is so essential to the concept -- I'd be happy with just what Lua (unJITted) provides.

# Bob Ippolito (19 years ago)

On Jun 26, 2006, at 3:58 PM, John Cowan wrote:

Brendan Eich scripsit:

Apart from the EIBTI doctrine, and I think as important for the members of ECMA TG1, is the practical problem of over-constraining implementations to be able to save and restore whole native+scripted- function call stacks, not simply resume a generator function via next, send, or throw. An optimizing compiler may have to deoptimize based on the indirect call relation, which in the presence of packages and eval is undecideable, so speculative code generation with fallback would be required in full. A tiny tree-walking interpreter may have to switch from natural host recursion to an explicit control stack, just to support the proposed feature.

I don't follow this. A tiny interpreter will have to escape from natural host recursion anyway just in order to do simple Python/Icon generators, and I don't see that yielding within C functions is so essential to the concept -- I'd be happy with just what Lua (unJITted) provides.

I have no idea what you mean by "natural host recursion", but I have
a feeling that you're mistaken. Python's implementation of generators
simply keep the generator's frame around (the gi_frame attribute on
the generator object). The frame stores all of the locals and the
bytecode offset. Calling the next() method just resumes the
interpreter at whatever bytecode it was at. It's not written in CSP
style and it doesn't require any weird tricks.

The only real difference between a generator frame and a regular
frame is that the regular frame is tossed after the function returns,
where a generator's frame is kept around until the generator is
garbage collected.

You can't really write generators in C, but you can easily write
iterators -- objects that have a next() method that behave according
to the iterator protocol. From a user's perspective, these two kinds
of objects are intentionally indistinguishable since generators are
just a convenient syntax for implementing an iterator.

# Neil Mix (19 years ago)

On Jun 26, 2006, at 4:12 PM, Brendan Eich wrote:

If this were 1995, and we had a sound library mechanism with the
right identity and security guarantees, I would agree. But at this
point I agree with Lars: "ECMAScript needs to be a rich language
with features that are immediately useful to its core audiences,
and which play well with each other in the sense that they have
unsurprising consequences when composed."

I'd like to think of myself as a member of the "core audience" (I
have no formal PL theory background, I've been hacking web apps my
entire career, I've never designed a language, and Wikipedia is the
only reason I'm able to keep up -- albeit minimally -- with this
discussion). I have to say, I believe this feature is immediately
useful, and (perhaps this is my naivety talking) I believe it can
avoid surprising consequences if done correctly.

Does the distinction between canonical continuations and "delimited
continuations" matter at all? If we drop canonical continuation
support (which is a good idea IMO) and focus instead on delimited
continuations, and furthermore concede that native calls are not
allowed in the delimited portion of the stack (which I think is
reasonable -- I'm not really sure how they'd show up there anyway),
isn't this reasonably doable? Granted, the tiny tree-walking parser
can't rely on host recursion. And granted, figuring out the right
syntax for including the delimited stack as a parameter to the higher- up stack frames is a bit treacherous (especially since this is
relatively new territory).

OTOH, I think the nature of JavaScript with it's callback-based APIs
makes it ripe for this kind of enhancement. Keep the simplicity of
onReadyStateChange, setTimeout, addEventHandler et al., and yet
provide a concurrency mechanism that is IMO a very natural (nay,
expected) way to tie things together. When I first started using
JavaScript 5+ years ago, I was looking for a concurrency mechanism
(even though I didn't know enough to call it "concurrency") within a
month of starting my first project. I was astonished to find that
none existed. For the past 6 months or so I've been slowly teaching
myself PL theory (in my spare time) so that I could take a stab at
solving this problem by building a code generator (also in my spare
time). I didn't do this to be cool or l33t, I honestly did it so
that the next time I build a JS app I'll be able to use a concurrency
mechanism, so that my code is easier to follow and the asynchronicity
doesn't drive me batty. (too late, most would say ;)

When I researched for my code generator I found postings all over the
web from people seeking concurrency in JS. Many of them didn't have
the vocabulary to describe it (just like me 5 years ago), but they're
out there. So I think I disagree with the assertion that this isn't
useful to the core audience. Whether it's doable in an unsurprising
way I'm less certain of, but I'm still naively optimistic.

# Brendan Eich (19 years ago)

I don't follow this. A tiny interpreter will have to escape from natural host recursion anyway just in order to do simple Python/Icon generators, and I don't see that yielding within C functions is so essential to the concept -- I'd be happy with just what Lua (unJITted) provides.

I mean by natural recursion that each activation of an ES function
is interpreted by a host (let's say C) function. Unwinding and
winding back through C functions requires mangling them to use
explicit control state, or else using something akin to a user-level
thread (setjmp/longjmp, setcontext, etc.).

With the Pythonic generators currently proposed for ES4, there's no
need for such host stack saving and restoring. As Bob pointed out,
you just need to save the generator function's frame.

Native C functions don't have to be involved for this stack saving/ restoring to be an unwanted burden, but if you propose to allow yield
to "return across" many frames, then it's hard to avoid a native
frame creeping into the stack that's being crossed, and then you
definitely need user-level threads, safe stack allocation with
redzones for overflow, etc. That's too costly for small
implementations.

Murphy was an optimist, and abstraction favors allowing arbitrary
native or ES implementation of any function in a large program.

# Graydon Hoare (19 years ago)

Brendan Eich wrote:

That seems unlikely since the same restriction probably applies to PEP 324 yields.

No, it doesn't. There is no yield across more than one level of call.

Oh. Somehow I was picturing a C python extension calling back into python, and trying to suspend itself in order to yield back to its caller. I guess they can just "not do that in an extension"; doesn't affect the python code on either side.

In any case, consider our two options:

  1. copying python: "no yield across more than one level of call"
  2. copying lua: "no yield across a language barrier"

We have a putative concept of "language barrier" in ES4 already: methods marked as 'native'. We could easily adopt #2. I claim that #2 is a much less severe restriction than #1, and gives the users (who mainly write 100% script) much more power.

Aside from the painful fact that we've already implemented something more like #1, is this really so unreasonable?

# Graydon Hoare (19 years ago)

Brendan Eich wrote:

That seems unlikely since the same restriction probably applies to PEP 324 yields.

No, it doesn't. There is no yield across more than one level of call.

Oh. Somehow I was picturing a C python extension calling back into python, and trying to suspend itself in order to yield back to its caller. I guess they can just "not do that in an extension"; doesn't affect the python code on either side.

In any case, consider our two options:

  1. copying python: "no yield across more than one level of call"
  2. copying lua: "no yield across a language barrier"

We have a putative concept of "language barrier" in ES4 already: methods marked as "native". We could probably adopt #2. I claim that #2 is a much less severe restriction than #1, and gives the users (who mainly write 100% script) much more power.

Aside from the painful fact that we've already implemented something more like #1, is this really so unreasonable?

# Lars T Hansen (19 years ago)

Graydon Hoare writes:

Brendan Eich wrote:

That seems unlikely since the same restriction probably applies to PEP 324 yields.

No, it doesn't. There is no yield across more than one level of call.

Oh. Somehow I was picturing a C python extension calling back into python, and trying to suspend itself in order to yield back to its caller. I guess they can just "not do that in an extension"; doesn't affect the python code on either side.

In any case, consider our two options:

  1. copying python: "no yield across more than one level of call"
  2. copying lua: "no yield across a language barrier"

We have a putative concept of "language barrier" in ES4 already: methods marked as "native". We could probably adopt #2. I claim that #2 is a much less severe restriction than #1, and gives the users (who mainly write 100% script) much more power.

Aside from the painful fact that we've already implemented something more like #1, is this really so unreasonable?

In most Scheme implementations that provide a foreign-function interface with callbacks to Scheme the user program can't reliably capture continuations in the callback. This is generally considered a hardship and implementations that fix it are considered superior. Of course, that's Scheme, and its users expect nothing but full generality. But I'd be worried about option #2 for that reason.

ECMAScript already has potential script-native-script call stacks in library functions that accept callbacks, like Array.prototype.sort and RegExp.prototype.replace. So option #2 would be a restriction in all implementations that are not prepared to write their libraries in ECMAScript. So it would be implementation dependent whether you could yield from a callback to these methods. This does not seem like a feature.

Granted, the programmer is not likely to write a generator that yields out of a callback to sort(), but I object to him having to discover that he can't do so reliably. And if a dominating product in a market (eg, MSIE) decides to implement sort() in ECMAScript and thus allow this pattern, other implementations will have little choice but to follow suit.

I'm with Brendan: "abstraction favors allowing arbitrary native or ES implementation of any function in a large program."

# Nicolas Cannasse (19 years ago)

There's been a lot of creativity in the past years about the way of using continuations. I think we can except new paradigms in the future as well. As a compiler writer, I prefer to be able to directly use call/cc in the generated code and libraries instead of rewrapping and sometimes abusing the standard ES4 library.

This gets to the heart of an issue that Nicolas has discussed with me
in private email: is ES4/JS2 likely to be a high-level language used
by programmers who write large parts of their programs by hand, or is
it likely to be a mid-level or "variable level" safe target language
whose programs compilers generate based on other inputs (haXe, Google
GWT, etc.)?

Although I would support that, this is of course not what I'm proposing.

ES4 already has the goal of being an high level language, and Brendan clearly explained to me why this is important for people on board at TG1.

What I'm proposing is to open the black box. For instance let's compare two possibilities regarding the current continuations subject :

a) call/cc is available on the native side only. Some native libraries (threads, coroutines, generators) are using it - with care - for their implementations.

b) same than (a), but call/cc is also available as a "lowlevel" function that can be called from any ES4 program.

It's just a matter of offering maximum access to users of the system. In that case, "offering" is of course different from "promoting". There should be one clearly promoted way of getting things done, yet I don't see any reason for forbidding other usage possibilities.

The success of a language is greatly influenced by the stuff good programmers will comeup with, and how it will impress the community. While it's very good to focus of the average user case when designing APIs and writing documentation, somehow locking the system and saying "there is one single way to do things that we decide" can result in frustration for future ES4 "hackers" that want to do things differently.

I clearly understand that this is not the only thing at stake with call/cc and continuations. There is also design/implementations concerns which I fully understand. But I think this way of thinking when designing a language or implementing a library can be applied to a lot of other different topics.

Of course, I'm biased there but I think this way of thinking is shared with a lot of people outside.

# Graydon Hoare (19 years ago)

I'm with Brendan: "abstraction favors allowing arbitrary native or ES implementation of any function in a large program."

Ok. I was trying to make two points and I'm not sure if they made it:

  • You're making a stronger restriction by making yields only ever one frame deep; essentially this PEP technique is syntactic sugar for special sorts of call/return. Control never yields across multiple frames. With the technique lua uses, control "sometimes" yields across multiple frames. A strict superset of the functionality.

  • That "sometimes" need not be vague or hard to determine. We have a way for code to indicate native-ness of a function already: the 'native' keyword. We could easily reflect that (and there's no sensible argument one can make that it's extra baggage to carry around; the implementation must know) so the user's script can find out and insulate against it.

    Keep in mind that the problem case is only yielding through a native frame. So if your native sort() calls script, script can yield to the native frame if the yielding is part of the sort operation. Only if the script callback yields, expecting to yield through the native sort(), do you have a problem. I'd argue that such yields are only likely to arise in a co-operative tasking system, and probably don't suffer so much from being insulated by being wrapped in a run-to-completion helper function. The yield is not being used as a part of an iteration protocol, just as a time slicer. Bigger slice, no big deal. All speculation, of course.

But I don't really care passionately; I was just arguing as devil's advocate because it didn't seem like you were giving the feature a fair shake. I also thought that by introducing script-level coroutines you might be able to appease some users who argue for further threads (or, gasp, call/cc) in the browser.

# Brendan Eich (19 years ago)

On Jun 27, 2006, at 1:57 AM, Graydon Hoare wrote:

Aside from the painful fact that we've already implemented something more like #1, is this really so unreasonable?

There's nothing painful for Mozilla's C implementation in doing more
work, apart from the cost of more work which is minor if the design
is right. The question is, what's the best design?

If the design should not be compromised by implementation issues,
then we should agree on that. ECMA TG1 was not of one mind on "damn
the implementation costs, full speed ahead". We have JITted runtimes
already tracking ES4 (Adobe), based on Waldemar's old drafts
(JScript.NET). We have small-ish to tiny interpreters (Apple,
Opera). We do not have the Lua single-implementation code-is-spec
open source codebase.

The design questions that bother me are:

  1. Comprehensibility to mere mortals.

  2. No arbitrary/painful-in-extremis restrictions, such as "can't
    cross abstraction barrier B (but may cross A)".

  3. On the other hand, Nicolas's argument in favor of opening the
    black box.

By following Python we are using some leverage for 1, and even if you
don't know Python, the current design (a kind of return from an
iterator that allows resuming the iterator's underlying generator
function where it left off) is simpler by one measure.

I'm with Lars on 2.

I'm still pondering 3.

# Neil Mix (19 years ago)

On Jun 27, 2006, at 5:10 AM, Brendan Eich wrote:

If the design should not be compromised by implementation issues,
then we should agree on that. ECMA TG1 was not of one mind on
"damn the implementation costs, full speed ahead". We have JITted
runtimes already tracking ES4 (Adobe), based on Waldemar's old
drafts (JScript.NET). We have small-ish to tiny interpreters
(Apple, Opera). We do not have the Lua single-implementation code- is-spec open source codebase.

I'm very sympathetic to this concern. Fortunately, I had a bit of an
epiphany this morning and realized that it may be possible to
implement a "poor man's CPS" using generators. It's fairly ugly and
cumbersome, and I'm not yet sure it makes things any less mind- bending, but I thought I'd throw this out in the hopes that it might
inspire modifications to the generator proposal that strike a solid
middle ground.

It works like this: if we assume that

a) arguments can be passed into a generator function and referenced
as normal in the generator body (I assume that's true, but the spec
doesn't explicitly say so), and

b) a generator can obtain a reference to itself (is that what
arguments.callee would do at present?)

then you can chain generators together using something that looks
similar to CPS. In essence, we're now pushing the burden of stack
management onto the coder, so that the runtime only ever yields
across one stack frame at any given time. Here's a silly example:

function doHttpReq(url, callingGen) { // XHR is a hypothetical class that abstracts // XmlHttp functionality var xhr = new XHR();

// grab a reference to this generator instance?
var ourGen = arguments.callee;

// set a callback handler to "resume" us
xhr.onLoad = function() { ourGen.next() }
xhr.send(url);

// wait for the xhr response
yield;

// return the fetched data to the generator that called us
callingGen.send(xhr.data);

// do a final yield to prevent a stopIteration
// exception from being thrown into the xhr callback
yield;

}

// load a bunch of URLs sequentially function loadItems(urls, callingGen) { var items = []; for (var i = 0; i < urls.length; i++) { // request an individual url, // passing in a reference to this generator instance. // Note the call to next() doHttpReq(urls[i], arguments.callee).next();

	// receive the send from doHttpReq
	var data = yield;
	
	items.push(data);
}

callingGen.send(items);

// do a final to prevent an exception from being thrown
// into doHttpReq
yield;

}

function init() { loadItems([url1, url2], arguments.callee).next(); var items = yield;

// do stuff with the items array.

}

addEventHandler("load", function() { init().next(); });

Now, I'll plainly state that this is ugly I'd much prefer more
natural coroutines than this messy CPS-like goo, but I'll take what I
can get. ;) Here's a few things that would clean this up quite a bit:

  1. a more intuitive reference to the generator instance
    (arguments.callee is just wrong)

  2. direct access to an implicit callback for the generator's send
    method so that we don't have to create it manually in each frame

  3. elimination of the final 'yield' which exists solely to prevent
    exception propagation

One way to solve these problems would be to invent an implicit
'generator' object, which then becomes the keyword that identifies a
generator function. Turn yield into a method on the generator
object. Finally, create a property generator.callback, a method that
calls send() and discards any stopIteration exceptions.

Thus the above example would become:

function doHttpReq(url, callingGen) { var xhr = new XHR(); xhr.onLoad = generator.callback; xhr.send(url); generator.yield(); callingGen.send(xhr.data); }

function loadItems(urls, callingGen) { var items = []; for (var i = 0; i < urls.length; i++) { doHttpReq(urls[i], generator).next(); var data = generator.yield(); items.push(data); } callingGen.send(items); }

function init() { loadItems([url1, url2], generator).next(); var items = generator.yield();

// do stuff with the items array.

}

addEventHandler("load", function() { init().next(); });

# Neil Mix (19 years ago)

On Jun 27, 2006, at 5:10 AM, Brendan Eich wrote:

The design questions that bother me are:

  1. Comprehensibility to mere mortals. [snip] By following Python we are using some leverage for 1, and even if
    you don't know Python, the current design (a kind of return from an
    iterator that allows resuming the iterator's underlying generator
    function where it left off) is simpler by one measure.

I've been contemplating this, and I'm wondering how the success of
comprehensibility as a design objective would be defined.

If you ask the question, "what are the components of this language
and how do they work?" then yes, I agree that python-style generators
are a bit more comprehensible.

If you ask the question, "how do I code my application using this
language?" then I would argue that coding in JavaScript is hard and
fairly incomprehensible (to mere mortals and immortals) for certain
classes of applications.

This difference in perspective looks like a tradeoff between
learnability and usability. Does comprehensibility as described
above equate solely to learnability? Does either concern have a
higher priority in the es4 design process?