Announce: first public version of dotty-cps-async [async/await transformer for dotty]

I’m glad to announce the first public release of dotty-cps-async. This release depends on the published version of the compiler and can-be downloaded from maven central.

Showcase:

  • Full implementation of async/await: all language constructions are supported.

  • Produce optimized code: the number of monad brackets is equal to the number of async operations, sequential parts run without additional overhead.

  • Pluggable interface for continuation monads.

  • Limited support for high-order functions:
    urls.map( httpClient.fetch(_) ).map(await(_)) can be an idiomatic way to fetch all URLs from the list in parallel.

  • Miscellaneous additional futures, such as automatic coloring, custom value discard, and sip22 compatibility layer.

Files:

Some extra words about compatibility with scala2:

import cps.compact.sip22 provides a SIP22-like interface. All test cases from the original Scala-Async distribution successfully passed with a change of imports only and included in the regression suite.

It is also possible to compile sip22 async code without changing the source code with shim’s help: add to the dependencies’ shim-scala-async-dotty-cps-async’ to scala3 dependencies instead scala-async for scala2.

Note that compatibility was not a primary goal during the development of dotty-cps-async. The generated code is quite different, so if you need a bug-to-bug compatible version of scala2 async, you should wait for the port of the original XAsync compiler plugin.

16 Likes

0.9.0 is published:
What was changed since 1-st public release, in the version, which is available for scala 3.0.1 final:

Main points behind bug fixing:

  • Implemented async variants for most high-order functions from scala standard library; Now if you use await inside for loop or fold – usually, it just works.

  • Simplified setting of shifted version of high-order functions: now developer can declare one inplace, instead creating an extra typeclass.

  • Implemented support oft custom call-chain builders: I.e. the following expression
    for( url <- urls if await(score(url)) > 0) yield await(fetch(url))
    will traverse through urls only once, because we can merge filter and map in WithFilter call-chain builder.

  • Implemented support of automatic coloring:
    I.e. we can write something like:

  async {
       val auth = db.retrieveAuth(url) 
       val data = fetchData(auth,url)
       if (isInteresting(data)) then
             db.increaseScoring(url)
}

the compiler will insert needed awaits automatically.

This works not only for plain Futures but also for effect monads, such as cats-effects IO, Monix, and ZIO. (see note about this: notes/2021_06_27_automatic-coloring-for-effects.md at master · rssh/notes · GitHub )

  • ScalaJS:
    – Implemented support for non-monadic objects in awaited statements (i.e., js.Promise) - now it is possible use await(js.Promise) inside async block.
    – Implemented JSFuture, which looks like ‘promise’ from the js side, but as typed monad on scala side.

It was tested in few internal projects and open-source libraries, depending from dotty-cps-async

13 Likes

So this is similar to something like F# Computation Expression?

  • Yes: the main similarity - it’s a way to embed arbitrary ‘generic (monad + optional extras)’ computation into control flow.
  • Differences ( ‘no’ smaller than ‘yes’).
    • Note, that F# has no HKT, so the meaning of ‘generic computation’ in F# and scala is quite different.
    • F# computation expression differs from base language, in dotty-cps-async internal and base language is literally the same.
4 Likes

This looks very interesting!

Please, is it possible to write a friendly basic usage page?
https://rssh.github.io/dotty-cps-async/BasicUsage.html#basic-usage

The current one really assumes a lot of familiarity specific to scala’s async libraries and a research niche.

CpsAsyncMonad and the like are really not that common terminology for someone that is coming from python/javascript/c#. To be honest, even tough I’ve written parsers in scala and async-await AST transformations in other languages I really can’t follow this introduction (I find it easier to go over the code).

5 Likes

Thanks. Will think what is possible to-do.

[btw, will be grateful for for the comments/patches in the ticket: Improve introduction documentation · Issue #48 · rssh/dotty-cps-async · GitHub ]

3 Likes

The terminology gave me a reminder how noob I am. :joy: Thanks for that.

0.9.18. bring a compiler plugin with support for direct context encoding, which can be a practical next intermediate step in the journey of asynchronous API development.

@experimental
class DToyLogger(ref: Ref[IO,Vector[String]]):

  def log(message: String)(using CpsDirect[IO]): Unit =
    await(ref.update(lines => lines :+ message))


  def all()(using CpsDirect[IO]): Vector[String] =
    await(ref.get)
    
@experimental    
object DToyLogger:    
  
  def make()(using CpsDirect[IO]): DToyLogger =
    val ref = await(Ref.of[IO,Vector[String]](Vector.empty))
    DToyLogger(ref)

2 Likes