Dependency

Sbt Example

The current prerelease is |0.9.21| for using with Scala |3.3.3|_.

libraryDependencies += "com.github.rssh" %% "dotty-cps-async" % "0.9.21"

JavaScript and Native targets are also supported.

libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.21"

Note: %%% automatically determines whether we are in a Scala/JVM or a Scala.js or a Scala.Native project (see Scala.js Cross-Building).

Compiler Plugin

For using direct context encoding (now marked as @experimental) you also need to add compiler-plugin:

for sbt:

autoCompilerPlugins := true
addCompilerPlugin("com.github.rssh" %% "dotty-cps-async-compiler-plugin" % "0.9.21")

for mill:


def scalacPluginIvyDeps = Agg(ivy”com.github.rssh::dotty-cps-async-compiler-plugin:0.9.21”)

Loom support on JVM

If you use JDK-21 or later you can find helpful loom-based support for transforming arguments of high-order functions. To enable one, add dotty-cps-async-loom module to the dependencies:

libraryDependencies += "com.github.rssh" %% "dotty-cps-async-loom" % "0.9.21"

Basic Usage

Traditional async/await interface

The usage is similar to working with async/await frameworks in Scala 2 (e.g. scala-async) and in other languages.

We define two ‘pseudo-functions’ async and await [1] :

def async[F[_], T](using am: CpsMonad[F])(expr: T) => F[T]

def await[F[_], T](f: F[T])(using CpsMonad[F]): T

Inside the async block, we can use the await pseudo-function.

import cps._

def myFun(params) = async[MyMonad] {
  // ... here is possible to use await:
  val x = await(something)
  // ...
}

In the above code, the type MyMonad must implement one of the two type classes CpsMonad or CpsTryMonad (which supports try/catch).

The minimal complete snippet looks as follows:

package com.example.myModule

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.util.{Failure, Success}
import cps.*                  // async, await
import cps.monads.{*, given}  // support for built-in monads (i.e. Future)

object Example:

  def fetchGreeting(): Future[String] =  // dummy async function
    Future successful "Hi"

  def greet() = async[Future] {
    val greeting = await(fetchGreeting())
    println(greeting)
  }

  def main(args: Array[String]): Unit =
    val f = Await.ready(greet(), Duration(1.seconds))
    f.failed.map { ex => println(ex.getMessage) }

This minimal example is for Future monad and depends on library dotty-cps-async to be added to our project file build.sbt :

// https://mvnrepository.com/artifact/com.github.rssh/dotty-cps-async
libraryDependencies += "com.github.rssh" %% "dotty-cps-async" % "0.9.19"

Note: The Integrations section lists further library dependencies needed for integration with well-known monadic frameworks such as Cats Effect, Monix, ScalaZ IO or ZIO and streaming frameworks like Akka Streams and Fs2.

A monad can also be abstracted out as in the following example:

trait Handler[F[_]: CpsTryMonad]:

  def run(): F[Unit] = async[F] {
    val connection = await(openConnection())
    try
      while
        val command = await(readCommand(connection))
        logCommand(command)
        val reply = await(handle(command))
        if !reply.isMuted then
          await(connection.send(reply.toBytes))
        !command.isShutdown
      do ()
    finally
      connection.close()

The async macro will transform the code block into something like

transformed code
m.flatMap(openConnection())(a => {
  val connection: Connection[F] = a
  m.withAction({
    def _whilefun(): F[Unit] =
      m.flatMap(
        m.flatMap(readCommand(connection))((a: Command) => {
          val command: Command = a
          logCommand(command)
          m.flatMap(handle(command))((a: Reply) => {
             val reply: Reply = a
             m.flatMap(
               if (!reply.isMuted)
                 connection.send(reply.toBytes)
               else
                  m.pure(())
             )( _ => m.pure(!command.isShutdown))
          })
        }))(c => if (c) _whilefun() else m.pure(()))
    _whilefun()
  })(
    m.pure(connection.close())
  )
})

Since we use optimized monadic transform as the transformation technique, the number of monadic brackets will be the same as the number of await s in the source code. You can read the notes about implementation details.

Direct context encoding. (experimental)

Direct context encoding allows the representation of asynchronous API as ordinary synchronous calls using context parameter CpsDirect[F]. The signature above is an example of a function in direct encoding:

def  fetch(url:String)(using CpsDirect[Future]): String

Usage:

def fetchAccessible(urls:List[String])(using CpsDirect[Future]): Map[String,String] =
       urls.flatMap{ url =>
            try
                Some((url, fetch(url)))
            catch
                case NonFatal(ex) =>
                   logger.log(s"Can't fetch $url, skipping", ex)
       }.toMap

Our minimal example in this style:

import scala.annotation.experimental
import scala.concurrent.*
import scala.concurrent.duration.*
import scala.concurrent.ExecutionContext.Implicits.global

import cps.*                         //  import cps
import cps.monads.{*,given}          //  import support for build-in monads (i.e. Future)


@experimental
class TestMinimalExample:

  def fetchGreeting()(using CpsDirect[Future]): String =
    "Hi."  // assume this is a real async operation

  def greet()(using CpsDirect[Future]) =
    val greeting = fetchGreeting()
    println(greeting)

  def main(args: Array[String]): Unit =
    val f = async[Future]{ greet() }
    Await.ready(f, Duration(1.seconds))
    f.failed.map { ex => println(ex.getMessage) }

I.e. function accept external context parameter of form CpsDirect[F] and return type is an ordinary value not wrapped in monad. The developer can call such function from an async block or other function with the direct context. Note, that signature also can be written in carried form: def fetchGreeting(): CpsDirect[F] ?=> String.

We can freely use await inside this direct context functions. Sometimes, we need to transform the synchronous style into asynchronous. We can do this using nested async expression or pseudo operator asynchronized (reified with reify/reflect syntax), which uses current context for inferring the monad type. For example, here is a version of fetchAccessibe which fetch url-s in parallel:

def fetchAccessible(urls:List[String])(using CpsDirect[Future]): Map[String,String] =
       urls.map{ url =>
              asynchronized(fetch(url))
            }
           .flatMap{ fetchingUrl =>
            try
                Some((url, await(fetchingUrl)))
            catch
                case NonFatal(ex) =>
                   logger.log(s"Can't fetch $url, skipping", ex)
       }.toMap

Note, that in current version (0.21) direct context encoding is marked to be experimental.

Alternative names

async(asynchronized)/await names is appropriate for Future-s and effect monads. There are other monads where a direct style can be helpful in applications such as probabilistic programming, navigation over search space, collections, and many other. We define alternative names for macros: reify(reifed)/reflect, which can be more appropriate in the general case:

def bayesianCoin(nFlips: Int): Distribution[Trial] = reify[Distribution] {
      val haveFairCoin = reflect(tf())
      val myCoin = if (haveFairCoin) coin else biasedCoin(0.9)
      val flips = reflect(myCoin.repeat(nFlips))
      Trial(haveFairCoin, flips)
 }
import cps.*
import cps.monads.{*,given}

def allPairs[T](l: List[T]): List[(T,T)] = reify[List] {
      (reflect(l),reflect(l))
 }

Yet one pair of names ‘lift/unlift’, used for example in the monadless library by Flavio W. Brasill, can be enabled by importing cps.syntax.monadless.*.

import cps.*
import cps.syntax.monadless.*

class TestMonadlessSyntax {

 import cps.monads.FutureAsyncMonad

 val responseString: Future[String] = lift {
   try {
     responseToString(unlift(badRequest.get))
   } catch {
     case e: Exception => s"received an exceptional result: $e"
   }
 }

}

Footnotes