
Why Continuations Are Coming to Java - lelf
https://www.infoq.com/presentations/continuations-java/
======
twic
> I serve as a technical lead for Project Loon. That is the project that's
> intended to add continuations and fibers to the JDK.

> However, actually, Project Loom, the goal of the project is to add
> continuations, fibers, and tail call elimination.

I'm guessing that the project is called either Loom or Loon (and i believe
it's the former), but i like the idea that there are actually two cooperating
projects, each of which occasionally suspends and lets the other run.

~~~
thaumasiotes
It's probably Loom. It's thematically connected to "fibers" and doesn't make
the immediate statement that you think your own project is doomed. Neither of
those is true of Loon.

~~~
twic
Doesn't seem to have slowed the Canadians down much:

[https://en.wikipedia.org/wiki/Loonie](https://en.wikipedia.org/wiki/Loonie)

------
trias
Honest question, maybe obvious: Why is the java-scheduler/ thread model so
much better than the OS-level one? What does the java one do, or better what
does it not do? And why can't the principles which make java-scheduling fast
be applied to OS-threads?

~~~
pron
There are a couple of issues here: scheduling, and managing the stack. The
reason a runtime can do better than the kernel is that its constraints are
(hopefully) less constraining than those of the kernel.

In the case of managing the stack, the kernel must be able to support
languages like C/C++ and Rust, that can have internal pointers pointing into
the stack itself. This makes it hard to reallocate stacks dynamically and move
them around in memory. The JVM doesn't allow such pointers in application
code, and internal bookkeeping pointers are known.

As to the scheduler, the kernel must support very different kinds of threads:
threads that serve server transactions that block very often, and threads
that, say, encode a video, that rarely block at all. These different kinds of
threads are best served by different schedulers (e.g. the "frequently
blocking" kind of thread is best served by a work-stealing scheduler, but that
may not be the best scheduler for other kinds of threads). When you implement
fibers in the runtime, you can let the developer choose a scheduler for their
needs.

~~~
jimmaswell
That sounds like the runtime has more constraints thus it can make more
assumptions, not less constraints, while the OS one must be more general.

~~~
lilactown
It depends on your perspective.

The OS one has more _requirements_ (the number of "it must do..." is much
higher), which constrains it's design space much more. I think you can call
that as "having more constraints" because it must meet more needs to be even
viable as a solution.

The number of requirements for JVM threads is much lower, thus less
constraining, allowing it more freedom to implement solutions that can meet
its narrower window of features.

------
ScottBurson
Ron Pressler!? Isn't that our very own 'pron'?

~~~
oblio
You should see him go on /r/java!

I'm kidding, his posts are awesome :)

~~~
kasperni
> You should see him go on /r/java!

Yeah, if just someone had banned him, we could have had Loom by now.

Just kidding as well... great work pron

------
kristianp
The Project Loom webpage:
[https://wiki.openjdk.java.net/display/loom/Main](https://wiki.openjdk.java.net/display/loom/Main)

------
dancek
We had a weird network issue in a production system, and because the interplay
of different components was very difficult to debug I briefly investigated
each component. This lead me to run Jersey (a JAX-RS implementation, ie. a
REST library) with a debugger.

Jersey is written in a continuation passing style. That's the only time I've
seen the style outside academic discussions of Scheme. I found the code flow
difficult to grok and thought continuation passing didn't fit Java very well.
That may all be just due to my lack of experience, though.

Granted, the alternatives to handling concurrency each have their own
problems. Maybe this is a good idea; it will be interesting to see in
practice.

~~~
mbrock
Having native access to continuation control lets you avoid writing in
continuation passing style. It’s common to implement native continuation
control with a compiler pass that automatically converts code into
continuation passing style. (Similar to how you would transpile async/await in
JavaScript.)

------
zeveb
Wow, continuations are pretty awesome, because with them you can build any
control structure you like. Now if Java only had macros, so that homegrown
control structures could be concise & succinct, like built-ins …

It's kinda of funny how languages are slowly becoming more and more Lisp-like
— even Lisp is available, still offering what it offers.

As an aside: argh, in Firefox it stole the spacebar, so there's no way to
scroll down the page, even if I click outside of the video.

Why do web pages do that?

------
waste_monk
Lack of Continuations / coroutines has been a huge pain in my rump for a while
now; it is fantastic that they're finally adding support for it.

------
Decabytes
Is tail call elimination the same as tail call optimization? I know that
schemes use this to keep the stack from blowing up during recursive function
calls, and that currently the JVM, and by extension Clojure is not able to
handle this.

I also remember reading somewhere that this wasn't possible to add/not on the
road map for the JVM. Does project Loom change this?

~~~
dan-robertson
Yesish.

The way I read it, tail call elimination is the guaranteed application of tail
call optimisation with a well defined meaning of what a tail call is. Eg
scheme requires TCE.

Tail call optimisation is a transformation a compiler may choose to do to make
code that looks like “return foo(...)” run faster. A compiler may choose to
not do it because it might not be implemented or faster or it could make
debugging harder. There is no guarantee it will happen.

TCO makes some code faster. TCE allows one to write different looking programs
knowing they won’t blow up the stack.

This all being said I think most people mean what I have referred to as TCE
when they say TCO.

------
jonathanstrange
Seems like every sufficiently advanced language at one time or another turns
into LISP.

~~~
pjmlp
"Invited Talk - Guy Steele" at ClojureTV.

 _We were not out to win over the Lisp programmers; we were after the C++
programmers. We managed to drag a lot of them about halfway to Lisp._

------
avodonosov
The question is not why, but when. When?

Our server has long waiting http handlers, which occupy java threads while
waiting, thus limiting the server throughput to the number of threads java can
maintain, and I'm holding off a rewrite on async servlets for several years
already to avoid complicating the code, because I hope cheap threads (fibers)
will become available, but there is no timeline for Loom. When can we hope it
will be released?

~~~
digaozao
Maybe with kotlin coroutines you can rewrite the application partially. Is
there a difference between that and fibers?

~~~
avodonosov
Kotlin coroutines, as well as js and c# async/await, require us to change
declaration of calling function. We can call a "suspend" function only from a
"suspend" function, which in turn can only be called by another "suspend"
function. So if I change some of my functions to "suspend" everyone upper in
the call stack should be changed to "suspend", which is not always possible
because that may be a 3rd party code.

That's not necesserily a deal breaker, but that's the difference.

For Java there is Quasar lib, which introduces async/await through bytecode
instrumentation. It's by the same authors who work on the project Loom
currently.

I don't use it because: a) the project web site feels a bit abandoned; I guess
they concentrate on Loom now 2) I need analogues of wait/notify, sychronized,
etc which are used in this code and not provided by Quasar; Quasar only
provides lower-level primitives. One either need to implement such constructs
on top of Quasar, or reimplement the logic. So it's not a drop-in replacement.

The project Loom aims to be mostly a drop-in, in particular fibers will
support synchronized, wait/notify, etc.

~~~
jarym
Quasar was created by the same guy (Ron Pressler) that is the tech lead for
Project Loom. The reason Quasar feels a bit abandoned is because he's bringing
the thinking right into the JVM.

------
stcredzero
Because Java eventually implements everything that was done years ago in
Smalltalk. Despite some having said it was unnecessary back then. Also, expect
that a feature that was implemented in Smalltalk by a single dev as a library
(this applies to continuations) will take a small team in Java. (Though the
Java implementation may well be more robust and faster than the Smalltalk one
from years ago.)

------
eatonphil
The bits about multi-node computation reminds me of Racket's Places [0]. Have
any other mainstream languages (Racket doesn't count) tried this technique
before?

[0] [https://docs.racket-lang.org/distributed-
places/index.html](https://docs.racket-lang.org/distributed-places/index.html)

~~~
lilactown
On its face, sounds like Erlang/OTP.

~~~
eatonphil
Ah duh! Good catch.

------
punnerud
30min in and I think:

Are they now trying to make Java to work like PL/SQL, Cobol, SAP (ABAP =
SQL+COBOL)..?

I think it is a good idea, and is a way to bridge the gaps between the old and
“new” way of writing programs. The real reason is not just scalability but
traceability, that you can change code at runtime, closer connected to the
database..

------
nimbix
What ever happened with continuations in Scala? A lot of people were excited
about them back in 2009, but if I google them now I still only get pages from
10 years ago. I haven't used Scala in a very long time, so I stopped paying
attention to any changes in the language.

~~~
hderms
as far as I can tell scala concurrency basically boils down to a few different
subclasses that have some overlap: 1\. streaming/observable focused like Monix
2\. IO effect type like Cats Effect and ZIO 3\. Twitter futures/Stdlib futures
which are different from each other in some crucial ways

~~~
wmfiv
Probably need to add 4. Akka.

------
rzzzt
Apache Commons Javaflow is an early implementation of continuations:
[https://commons.apache.org/sandbox/commons-
javaflow/tutorial...](https://commons.apache.org/sandbox/commons-
javaflow/tutorial.html)

~~~
mcv
I vaguely seem to recall that Rhino, Cocoon's pre-Node serverside javascript,
had continuations too. That much have been around 2006 or thereabouts.

~~~
bokchoi
I remember them too. I had to dig a little to find the links I remember from
the past.

[http://web.archive.org/web/20190127111220/https://wiki.apach...](http://web.archive.org/web/20190127111220/https://wiki.apache.org/cocoon/RhinoWithContinuations)

From the Rhino Google group, it looks like some people are still using
continuations in Rhino:

[https://groups.google.com/forum/#!topic/mozilla-rhino/Gfo-
dO...](https://groups.google.com/forum/#!topic/mozilla-rhino/Gfo-dOzQXCw)

[https://github.com/szegedi/spring-web-
jsflow](https://github.com/szegedi/spring-web-jsflow)

~~~
dmix
That Rhino js engine stuff was all written in Java too.

------
dfgdghdf
In the talk he says that continuations compose much better than monads. Can
someone elaborate on this?

~~~
noelwelsh
Monads don't compose, in the sense that if you two monads of different types
you can't always mash them together to create a single monad that encompasses
both of the effect the two monads represent. This is not surprising as
(side-)effects don't in general compose so there is no reason that monads
should.

Now I disagree with Ron's assertion that continuations compose better than
monads. Here's why:

All monads can be expressed in terms of the continuation monad. As the name
suggests this is just a monadic encoding of continuations. (See
[https://www.schoolofhaskell.com/school/to-infinity-and-
beyon...](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-
of-the-week/the-mother-of-all-monads)).

If your language has continuations built-in then all programs are effectively
running in some "ambient" monad that can express all other monads. Just like
if you have exceptions you're effectively running within an error-handling
monad.

So essentially rather than writing `F A` (or `F[A]` or `F<A>` or whatever
syntax your language supports) to represent effectful programs all programs
are implicitly running within such an effect type.

~~~
pron
It's true that monads and delimited continuations are equivalent, but what we
have here aren't any delimited continuations but what's known as _multi-
prompt_ delimited continuations, and I call them "scoped continuations"
([http://blog.paralleluniverse.co/2015/08/07/scoped-
continuati...](http://blog.paralleluniverse.co/2015/08/07/scoped-
continuations/)). Those compose well, and their pure-FP counterpart is
algebraic effect systems (based on monads, but don't use monad transformers).

------
darkpuma
What impact, if any, would this have on Clojure?

~~~
didibus
I had a discussion about this with some other folks recently.

Clojure already supports forms of delimited continuations. You can use
core.async for example, or cloroutine. That said, it can't yield across stack
frames. One problem with that is that if you use a go block for example,
calling anything inside it which will block can sabotage your go thread. With
Project Loom, all blocking call made within a Fiber will yield it instead of
blocking the thread. That would be the big advantage, and possibly something
Clojure could leverage as well, so that say making a blocking IO call even
indirectly inside a go block would park instead of blocking.

------
agumonkey
anybody get this feeling that all languages are converging to a similar core
set ?

~~~
drcode
All languages are becoming Lisp, except for the added challenge of avoiding
parentheses.

~~~
pjmlp
Dylan did it first though.

~~~
pantulis
Modula-2 had coroutines!

~~~
pjmlp
Indeed, but I was replying to _" All languages are becoming Lisp, except for
the added challenge of avoiding parentheses."_.

Now if we are speaking about coroutines, there are other languages even older
than Modula-2, although it was probably the most mainstream one at its peak.

------
oddthink
Does anyone have a good link to the state of the modern thinking on
continuations?

I remember from back in the Scheme R5RS era there was a conflict between
call/cc and dynamic-wind.

If you have a dynamic extent (like for example a try {} finally {} block that
does resource allocation/deallocation), and you exit, but then jump back into
the block, how far do you try to re-wind the state? You probably don't re-open
files, but you would probably want to re-set any dynamic-scoped variables.

I don't remember seeing this raised as an issue recently, so it must have been
solved somehow, and I'm just wondering how.

~~~
ghusbands
Dynamic-wind was created to tame call/cc and exceptions, rather than being in
conflict with it. It directly provides the facilities for re-re-setting things
when you re-enter a left scope.

However, you might want to look up delimited continuations as a different
approach to similar kinds of control flow.

~~~
oddthink
Yes, thank you. I was thinking of Kent Pitman's critique that unwind-protect
didn't have the right semantics when implemented with dynamic-wind, and that
(in general) it seemed like fluid-let and unwind-protect wanted different
sorts of continuations to implement them. (And now I need to go read more
about one-shot vs multi and delimited continuations.)

Someone (I forget who) had a critique about dynamic-wind itself that (IIRC)
involved capturing continuations from within the before- and after-thunks of
dynamic-wind, but that seems more like an edge case.

------
exabrial
How will ThreadLocal behave under different fiber context switches?

~~~
pron
A fiber will appear to be a thread. You can even call Thread.currentThread and
get a consistent Thread object representing the fiber. This is done so that as
much existing code as possible would be able to run, unchanged, in fibers. We
_may_ (or may not), however, introduce newer, better constructs that new code
will be encouraged to use.

~~~
exabrial
Oooo cool! It may be important or useful to include the 'old' contract
elsewhere in the SDK.

------
cryptos
What is called continuations in Java is available as "coroutines" in Kotlin,
today. Both terms means more or less the same, from a programmers point of
view, actually Continuations are part of Kotlins coroutines.

[https://kotlinlang.org/docs/reference/coroutines-
overview.ht...](https://kotlinlang.org/docs/reference/coroutines-
overview.html)

~~~
raverbashing
Is there some special jvm/dalvik support for it?

~~~
lupajz
It's a compiler plugin that generates bytecode for you, so there's no need for
support from the runtime.

------
SeanLuke
How does this differ from scheme-style continuations? IIRC, full continuations
in the scheme sense of the term are currently impossible in Java as they
require forbidden stack manipulation. Or is what's being proposed here
something entirely else?

~~~
aardvark179
We're adding delimited one-shot continuations to the JVM. The common public
interface to these is likely to be fibers rather than raw continuations, as
they are much easier to work with and allow the standard library and lots of
existing code to take advantage of the new features with little to no change.

~~~
maxiepoo
By one-shot you mean dynamically enforced, right? I.e. if you invoke it a
second time it will raise an exception?

~~~
ufo
Not exactly, since they are using a fiber API instead of one built with raw
continuations. In this context, being one-shot means that there is no
operation for cloning or "forking" a fiber.

Each time you resume the execution of a fiber, it continues to execute until
it reaches its next yield point. There is no way to go "back in time" and
resume execution from a previous state of the fiber, which is what multishot
coroutines or the fork system call would let you do.

------
teilo
So will fibers in the JVM be as fast, robust, and as cheap as processes in
Erlang's BEAM?

------
yarg
Here's a decent video with some code included:
[https://www.youtube.com/watch?v=vbGbXUjlRyQ](https://www.youtube.com/watch?v=vbGbXUjlRyQ).

The intro music's a little weird for a dev con.

------
mamcx
One thing I get learning about implementing continuations is that at the end
them have huge runtime cost and complicate implementation and debugging
greatly.

Even do a simple interpreter with them quickly get out of control.

Exist update info in how tame it?

~~~
_delirium
One approach, which Java is taking, is to implement a more structured form of
continuation, rather than say the Scheme-style call/cc ones. A quote from the
article: "To be more precise, if you're interested in the theory of
continuations in the academic literature, the kind of continuation of this
class implements are called one-shot multi prompt delimited continuations..."

~~~
mamcx
That kind of continuation don't have much info that I remember and probably
I'm not the only one:

[https://www.reddit.com/r/ProgrammingLanguages/comments/9r2kt...](https://www.reddit.com/r/ProgrammingLanguages/comments/9r2ktu/rain1_blog_are_first_class_continuations_a/)

but still look interesting if somehow manage to be performant-enough and tame
enough. I think continuations are best for the internals of the compiler/vm
than to surface to the end user...

------
cetpa
JAVA is threaded go down to the OS level and is relatively resource-expensive
(RAM and context switching) so don't scale well. If you want to know more
visit CETPA INFOTECH PVT LTD

------
mapcars
>if a continuation gets into an infinite loop, we can ask it to preempt
itself, and forcefully remove it from the CPU.

This is interesting, any idea of how they can do it?

~~~
repolfx
That's easy. All Java threads regularly pass through "safe points" where the
runtime can choose to suspend them. Think about garbage collection - the
runtime must be able to reliably and regularly pause threads so their stacks
can be scanned. Whether interpreted or JIT compiled, there are ways to queue a
piece of code 'into' a thread that's currently running and a short time later
that code will execute. Safe points are used for many different things in
HotSpot.

So whilst I haven't looked at the code, presumably continuations are pre-
empted by forcing the host thread to a safepoint and then unmounting it.

Note that forced pre-emption + a scheduler + a userspace notion of a thread
gives you the tiniest core of an operating system. The next big leap in
operating system design is certainly language-generic virtual machines fused
with the basics of an OS.

------
mb720
There's an error in the presentation's slides: The type of returnIO is "a → IO
a" and not "IO a → IO a".

~~~
pron
Correct. That was a typo that was then copied and pasted so the mistake
appears twice.

------
vkaku
About time. I definitely could use a yield keyword, if someone is reading
this.

------
gigatexal
So it’s like python’s yield statement aka generators?

~~~
topspin
It's more general than generators. You can create generators using it, and
also other control flow constructs.

~~~
billsix
Yup, I did exactly that in Scheme

[http://billsix.github.io/bug#_make_generator](http://billsix.github.io/bug#_make_generator)

------
y4mi
I find it amusing that whenever people talk about Java, they're somehow using
magic to describe it. It's like Java programmers see themselves as
magicians...

This is especially widespread with spring boot. I've even read sentences like
"It's hard to say what it does, you can only see it's effects!"

~~~
twic
I wonder if it's because there is such a sharp line between what a Java
programmer can do in Java, and what the JVM does to make that possible. Much
of what the JVM does is impossible to even describe in normal Java. That line
doesn't exist in, say, C++, where the compiler is just another C++ program.

With the caveat that there are JVMs written in Java, so that line can be
crossed:

[https://www.jikesrvm.org/](https://www.jikesrvm.org/)

The same mechanism could apply at the Spring Boot boundary. Using Spring Boot
(for simple applications) is easy enough. Implementing it takes code that is
beyond bizarre.

~~~
groestl
The JVM is not magic, neither are Java programmers magicians, but I think the
reason magical terms are used to describe Java systems is that the ecosystem
had lots and lots of time to develop libraries and abstractions (a few of
which happen to be of the right kind, read: leak just the right amount). Your
typical Java programmer is well aware what it takes to implement a battle-
proof connection pool, and will not attempt to implement their own. To them,
adding the config stanza to enable a connection pool indeed feels like magic.
Other platforms, that come with preconfigured connection pools, do not leak
this to the programmer, and 80% of their users will not know what a connection
pool is.

~~~
james-mcelwain
The way I would phrase it is that the Java ecosystem is simultaneously both
higher level and lower level (in terms of abstraction, not the machine) than
many other platforms.

The ability to swap out basically any component of a framework like Spring
simply by including a different dependency or replacing a bean at runtime
allows for fine grained tuning that isn't possible in many other frameworks.

But this flexibility and/or modularity is enabled by a heap of pretty magical
abstractions that glues everything together.

~~~
twic
The Java library developer's aesthetic has traditionally been to make things
possible, rather than to make things easy. So you may have to write a lot of
code or config to get something to happen, but you will have a lot of control
over what happens.

This is the opposite emphasis to, say, the Ruby community, which values a
simple, lickable, surface interface with a large amount of opaque magic hiding
behind it.

------
mruts
Will this mean that Akka can support true multitasking like Erlang? If so,
that sounds amazing. Being able to write blocking code in actors is such a big
win.

