I’m three weeks late, but I just had to drop my 2 cents here …
M. Edward (Ed) Borasky wrote:
On Wed, Apr 1, 2009 at 6:00 AM, Charles Oliver N.
Well, according to the earliest drafts of the Ruby standard, continuations
are not part of “core Ruby”
But yes, this is one area we’ve opted not to be compatible in exchange for
performance. We could implement continuations, but we’d be several times
slower as a result.
IIRC YARV also dropped continuations because they would be slow on the
YARV VM as well.
They are still there, just no longer in the core library. They have
been “downgraded” to the stdlib. They had been removed for a pretty
long period of time, but they reappeared. In fact, for almost two
years, it was actually said to be impossible to implement them on
YARV, but then without much fanfare, the continuation library appeared
in a CVS commit.
Which does raise an interesting point, though. Call/cc has been
standard equipment on Scheme for years and nobody has complained about
performance. How did Ruby manage to paint itself into a “slow
I don’t think the problem is Ruby. The problem is the implementations.
You can implement continuations really really slow in Scheme, too, if
you only implement it badly enough. And there is a lot of bad Scheme
implementations out there.
In MRI, continuations are slow (and apparently they leak memory like
there’s no tomorrow), because MRI is, quite frankly, a bad
implementation. Continuations are only one example; the garbage
collector, the extension API, the (non-existent) FFI, the fact that
concurrency isn’t actually concurrent, are others. And Matz himself
has made it very clear that he does not consider MRI to be a good
implementation, he has always pointed out that he is primarily a
language designer and not necessarily a programmer and definitely
not a VM guru.
In YARV, continuations are faster and the memory leaks are plugged,
but they aren’t really fast, either. In this case, it’s a design
decision: they weren’t meant to be fast, indeed, they weren’t meant
to implemented at all.
And JRuby, well, to paraphrase Clojure’s Rich Hickey (he was talking
about tail calls, but it applies equally to continuations): “Java
interoperability. High performance. Continuations. Pick any two.” This
applies not only to continuations and tail calls but really
everything that doesn’t fit the linear call stack paradigm (like
coroutines). And it also doesn’t only apply to Java, but also to .NET
and even C.
The problem arises when you want to implement advanced control flow
constructs on execution engines that don’t support them natively.
Basically, all you need to implement advanced control flow is either
continuations or reified call stacks. Java has neither. So, you are
left with a couple of choices: use continuation-passing-style,
implement your own stack or use exceptions for control flow. Or, well,
just give up. CPS and managing your own stack both mean that you are
no longer call stack compatible with Java, which complicates (and
slows down) Java interop. Using exceptions isn’t really performant,
although exceptions are much faster on Java than people give them
cedit for, but this particular usage pattern doesn’t fit with the
JRuby and IronRuby chose to emphasize performance and interop and
sacrifice continuations. Even Rubinius is currently converting from a
self-managed spaghetti stack to just the plain C stack, even though
that severely complicates Rubinius’s distinctive “stackless” features
like reified stack frames, lightweight tasks, continuations, actors
and threads, and embeddability and multi-VM. One of the reasons is
again interop, this time with C (FFI).
The Parrot VM on the other hand, has continuations as its only
control flow mechanism built deeply into the VM. Squeak has reified
stack frames, thus allowing Seaside to implement continuations in the
library, even though Squeak doesn’t support them. I suspect similar
things might be true for MagLev, especially since they are actually
willing to change the VM to fit Ruby.