Technology in Stockholm, Sweden • Previous positions at Typesafe Inc., Stanford University, and EPFL • PhD 2010 EPFL • Creator of Scala’s first widely-used actor library, co-author of Scala futures, Scala Async, spores, and others, Akka contributor • Research interests in programming language design and implementation, type systems, concurrency, and distributed programming
the day of the year as a string wrapped in a future: def dayOfYear(): Future[String] • However, sometimes, for unknown reasons, dayOfYear completes the returned future with "", or null, or the string "nope". • Task: create a future which is completed either • with "It's <month>!" where <month> is the textual representation matching the month returned by dayOfYear, or • with "Not a date, mate!" when that's really the best we can do. "04/11" for April 11, or "06/13" for June 13
"""(\d+)/(\d+)""".r dayOfYear().flatMap { dayString => dayString match { case date(month, day) => nameOfMonth(month.toInt).map(name => s"It's $name!") case _ => Future.successful("Not a date, mate!") } } } Returns a value of type Future[String]
"""(\d+)/(\d+)""".r dayOfYear().flatMap { dayString => dayString match { case date(month, day) => nameOfMonth(month.toInt).map(name => s"It's $name!") case _ => Future.successful("Not a date, mate!") } } } Evaluates to a value of type Future[String] which is completed with “It’s April!” when future returned by nameOfMonth(4) is completed with “April”
"""(\d+)/(\d+)""".r dayOfYear().flatMap { dayString => dayString match { case date(month, day) => nameOfMonth(month.toInt).map(name => s"It's $name!") case _ => Future.successful("Not a date, mate!") } } } Evaluates to a value of type Future[String]
"""(\d+)/(\d+)""".r dayOfYear().flatMap { dayString => dayString match { case date(month, day) => nameOfMonth(month.toInt).map(name => s"It's $name!") case _ => Future.successful("Not a date, mate!") } } } (a) flatMap returns a future f (b) f is completed (asynchronously) as follows: once dayOfYear() completes with value dayString, run closure { dayString => .. } which results in a future g; when g completes with value v, complete f with v
val date = """(\d+)/(\d+)""".r await(dayOfYear()) match { case date(month, day) => s"It's ${await(nameOfMonth(month.toInt))}!" case _ => "Not a date, mate!" } } async { <body> } creates a future which is completed asynchronously with result of <body> await(f) suspends current async block until f is completed with value v which is returned A simple, sequential program!
date = """(\d+)/(\d+)""".r for { dayString <- dayOfYear() response <- dayString match { case date(month, day) => for (name <- nameOfMonth(month.toInt)) yield s"It's $name!" case _ => Future.successful("Not a date, mate!") } } yield response } Readability OK not great
val date = """(\d+)/(\d+)""".r await(dayOfYear()) match { case date(month, day) => s"It's ${await(nameOfMonth(month.toInt))}!" case _ => "Not a date, mate!" } } No need to name all intermediate results No need for explicit Future.successful() Fewer closures allocated: async {} = 1 closure
of converting a single string "04/11" (or "06/13" etc.), "", null, or "nope", we should consume a stream of multiple such strings • Output should be a publisher which produces a stream of results
Interfaces: j.u.c.Flow.{Publisher, Subscriber, Subscription} • Some key prior work: • Observer design pattern (Gamma et al. 1994)1 • Reactive Extensions (Meijer 2012)2 • Key innovation of Reactive Streams: backpressure control 2Erik Meijer, Your mouse is a database, Commun. ACM 55(5) (2012) 66–73. 1Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object Oriented Software, Addison-Wesley Professional, 1994 Composition of observable streams using higher-order functions
are received by subscribers if they have expressed interest. public static interface Flow.Publisher<T> { void subscribe(Flow.Subscriber<? super T> subscriber); }
from publishers to which s has subscribed and from which s has requested to receive (a certain number of) events. public static interface Flow.Subscriber<T> { void onSubscribe(Flow.Subscription subscription); void onNext(T item); void onError(Throwable throwable); void onComplete(); }
string "04/11" (or "06/13" etc.), "", null, or "nope", we should consume a stream of multiple such strings • Output should be a publisher which produces a stream of results • Problem: • await only takes futures as arguments, but we need to await stream events! • async creates a future, but we need to create a stream publisher which yields multiple events instead of producing just one result We can’t use async/await! :-(
await[T](future: Future[T]): T • Now: await events produced by different asynchronous objects (Future[T], Flow.Publisher[T], etc.) • An asynchronous object must provide the following methods: def getCompleted: Try[T] def onComplete[S](handler: Try[T] => S) given (executor: ExecutionContext)
= await(dateStream) while (dateOpt.nonEmpty) { dateOpt.get match { case date(month, day) => yieldNext(s"It's ${await(nameOfMonth(month.toInt))}!") case _ => yieldNext("Not a date, mate!") } dateOpt = await(dateStream) } yieldDone() } Type of stream: Flow.Publisher[String] Here, we are awaiting a future!
= Promise[Int]() var x: Option[Int] = _ def apply(tr: Try[Int]): Unit = state match { case 0 => x = None val sub = flow.pubToSub(s) val completed = sub.getCompleted if (completed == null) { state = 2 sub.onComplete(evt => apply(evt)) } else if (completed.isFailure) { result.complete(completed) } else { x = completed.get state = 1 apply(null) } case 1 => result.complete(x.get) case 2 => if (tr.isFailure) { result.complete(tr) } else { x = tr.get state = 1 apply(null) } }} Generated state machine
Goal: JVM features for supporting lightweight, high-throughput concurrency constructs • Key features: • Delimited continuations • Fibers (“user-mode threads”) • Project under active development • Sponsored by OpenJDK HotSpot Group • Talk by Ron Pressler today at 16:15!
into a suspendable computation • Suspend and resume <body> at yield points • Transfer values into and out of <body> using higher-level abstractions val cont = new Continuation(SCOPE, new Runnable { def run(): Unit = { <body> } })
Runnable { def run(): Unit = { println("hello from continuation") while (!continue) { println("suspending") Continuation.`yield`(SCOPE) println("resuming") } println("all the way to the end") } }) cont.run() println("isDone: " + cont.isDone()) continue = true cont.run() println("isDone: " + cont.isDone()) hello from continuation suspending isDone: false resuming all the way to the end isDone: true Output: Example
flow for Flow[T] = new Flow[T] ... val cont = new Continuation(SCOPE, new Runnable { def run(): Unit = { try { val v = body flow.emitNext(v) flow.emitComplete() } catch { case NonFatal(error) => flow.emitError(error) } } }) ... flow }
semantics for direct-style asynchronous observables. Journal of Logical and Algebraic Methods in Programming 105 (2019) 75–111 https://doi.org/10.1016/j.jlamp.2019.03.002 • Formalization of programming model • Type soundness proof • Proof of protocol conformance • Describes macro-based implementation Example: a stream created with rasync { … } never emits a next event after emitting a done event
variable capture Idea: constrain variable capturing of closures. See: H. Miller, P. Haller, M. Odersky: Spores: A Type-Based Foundation for Closures in the Age of Concurrency and Distribution. ECOOP 2014 • Issue 2: Data races due to top-level singleton objects Idea: ensure object-capability safety of objects created within rasync blocks. See: P. Haller, A. Loiko: LaCasa: lightweight affinity and object capabilities in Scala. OOPSLA 2016 Example: var x = 5 rasync[Int] { x = 10 yieldNext(5) 3 } val y = x + 1
a suspendible method (given Flow[T]) requires the caller to be suspendible, too • Need two variants for each higher-order function: regular and suspendible • Loom continuations don’t suffer from that problem; can suspend as long as there is an active continuation • Relaxing need for Flow[T] capabilities may be feasible