What does the @suspendable annotation in Scala do?

Viewed 55

While reading the Scala.React implementation on GitHub I've stumbled across the @suspendable annotation:

object Reactor {
  def loop[A](op: FlowOps => Unit @suspendable): Reactor = new Reactor {
    def body = while (!isDisposed) op(this)
  }
}

In the paper Deprecating the Observer Pattern with Scala.React, the Reactor object is used in the following way:

Reactor.loop { self =>
  // step 1
  val path = new Path((self await mouseDown).position)
  self.loopUntil(mouseUp) { // step 2
    path.lineTo(m.position)
    draw(path)
  }
  path.close() // step 3
  draw(path)
}

Notably, the code in the Reactor body can wait for Events, like mouseDown. Therefore it looks like the code is executed asynchronously, even though there is no explicit use of threads. Because I couldn't find what the @suspandable annotation does, I feel like I'd need to understand it, before I can understand the rest of the implementation. Therefore:

  • What does the @suspendable annotation do?
  • Are there scenarios, where it is required?
  • When would I use it?

My suspicion is, that it somehow abstracts over, and allows for asynchronous execution. If that is true:

  • How does it work "under the hood" / how is it implemented?
2 Answers

@suspendable is an annotation introduced by the now-abandoned Scala Continuations library (which for a while was part of the Scala standard library) and used by the associated compiler plugin.

The project explored adding delimited continuations to Scala, and about the best extant documentation for it is in the doc-comments here. @suspendable is an alias for @cps[Unit], which basically signals the compiler plugin to perform a CPS transform at shifts when it's called within a reset block.

A rough idea of what the plugin does is:

def five(): Int @cps[Int] = shift { k: (Int => Int) => k(5) }

reset { five() + 1 }

ultimately gets translated to something as simple as

val kont: Int => Int = _ + 1
five(kont)

The basic idea is to translate the remainder of the expression into a function which takes the result up to that point as an argument and pass it into a function which will calculate the result up to that point and call the function representing the remainder of the expression. This is basically what you do manually when using a callback-based API (e.g. in Node.js).

The Future APIs (as well as the async/await compiler plugins for working with Futures) simplified and the use of CPS for asynchronous programming (which is, outside of writing compilers, the only wide non-academic use of CPS) as well as adding more intuitive support for concurrency and asynchrony, which led to the deprecation and removal of continuation support.

Related