This is a prototype library for async
/await
syntax in Cats Effect, currently targeting Scala 2's native -Xasync
support and building on dotty-cps-async for Scala 3. The async
/await
functionality within the Scala compiler is itself quite experimental, and thus this library should also be considered experimental until upstream support stabilizes. Once that happens and this implementation is considered to be finalized, the functionality in this library will be folded into Cats Effect itself and this library will be archived.
"CPS" stands for "Continuation Passing Style". This functionality is quite similar to similar functionality in JavaScript, Rust, Kotlin, and many other languages. The primary difference being that, in this library, the async
marker is a lexical block, whereas in other languages the marker is usually a modifier applied at the function level.
Special thanks to Jason Zaugg for his work on the implementation of -Xasync
within scalac. Also Ruslan Shevchenko for his work on dotty-cps-async.
libraryDependencies += "org.typelevel" %% "cats-effect-cps" % "<version>"
// if on Scala 2
scalacOptions += "-Xasync" // required to enable compiler support on Scala 2
Published for Scala 2.13, 2.12, and 3.0, cross-build with ScalaJS 1.8. Depends on Cats Effect 3.3.0 or higher. Scala 3 support depends on dotty-cps-async 0.8.1.
Consider the following program written using a for
-comprehension (pretend talkToServer
and writeToFile
exist and do the obvious things, likely asynchronously):
import cats.effect._
for {
results1 <- talkToServer("request1", None)
_ <- IO.sleep(100.millis)
results2 <- talkToServer("request2", Some(results1.data))
back <- if (results2.isOK) {
for {
_ <- writeToFile(results2.data)
_ <- IO.println("done!")
} yield true
} else {
IO.println("abort abort abort").as(false)
}
} yield back
Using cats-effect-cps, we can choose to rewrite the above in the following direct style:
import cats.effect.cps._
async[IO] {
val results1 = talkToServer("request1", None).await
IO.sleep(100.millis).await
val results2 = talkToServer("request2", Some(results1.data)).await
if (results2.isOK) {
writeToFile(results2.data).await
IO.println("done!").await
true
} else {
IO.println("abort abort abort").await
false
}
}
There are no meaningful performance differences between these two encodings. They do almost exactly the same thing using different syntax, similar to how for
-comprehensions are actually flatMap
and map
functions under the surface.