Design of -Xasync

I think that whenever we discuss async/await-like programming or for-comprehension in Scala, we should take a step back and have a look at F#'s Computation Expressions.

They are a variant of Haskell’s do-notation or of Scala’s for-comprehension, but it’s much more generalized and customizable. It doesn’t work only with flatMap <- (as if “monadic” val x = ...) and pure yield, but also works with for/while loops, sequences, pattern matches and resource disposal and try-catch-finally analogs from the “imperative” world.

Here’s a hypothetical example to illustrate the point:

let fetchAndDownload url =
  async {                                         // marks the start of the computational expression, like `for` in Scala, but async is an object which defines how it's all wired up together
    let urlStripped = strip url                   // usual variable binding; it's nice you can do that even as the first thing -- you can't do that in Scala
    let! data = downloadData urlStripped          // `let! x = y` is like `x <- y` in Scala, but it's much visually closer to its imperative cousin, which in Scala would be `val x = y`
    let processedData = processData data          // another usual variable binding
    use! monitor = createMonitor processedData    // like `let! x = ...`, but the resource is disposed of at the end of the (otherwise asynchronous) block, think of cats-effect's `Resource.use` (`use x = ...` is F# normal resource acquisition)
    do! notifyMonitor                             // like `let! () = x`, but nicer syntax than `_ <- x` which is what Scala forces you to do
    return processedData                          // like `pure`, serves the same purpose as Scala's `yield` block
  }

The most important part (even before the generality and customizability) is that the syntax is intentionally made similar to the “imperative counterparts”. There’s just ! added at the end of the keyword!. This results in easy learning of the concept and then in fluent writing and reading of the code.
Playing with other syntax constructs programmers are familiar from the imperative world, like resource acquisition/disposal or exceptions (try-catch-finally) is also handy in practice.

The other useful thing to have, besides looking like imperative code, would be debugging like imperative code, where one can nicely see the stacktrace as is logically expected (even though that’s not how it actually is).

And OCaml has recently gained syntax even for applicative composition.

Whichever path Scala takes, be it the improvement of for-comprehension based on callbacks or this new async/await with state machines transformation, I hope it will align well with the rest of Scala’s syntax and that it will be more general and work with more than just Future‘s. Currently, this is Scala’s weak spot, but we can learn from other languages’ successes.

More info and examples on Computation Expressions

3 Likes