github typelevel/cats-effect v3.2.0

latest releases: v3.7.0-RC1, v3.6.3, v3.6.2...
4 years ago

This is the sixth major release in the Cats Effect 3.x lineage. It is fully binary compatible with every 3.x release, and fully source-compatible with every 3.2.x release.

This release sees several major improvements and fixes to the library, but despite that, most of the pull requests in this release cycle were focused on improving documentation and general library convenience. We've made a lot of progress in this area but there's still plenty of work to do; come join us! Writing a bit of scaladoc or contributing a paragraph of explanation to the website is a great way to dip your toes into contribution, as the many invaluable authors in this release cycle can attest.

Notable Changes

Fixes for guarantee (and bracket) When Used with Monad Transformers

In previous versions, guaranteeCase (and by extension, bracketCase) was inconsistent with Fiber#join, despite the fact that both produce an Outcome representing the results of some effect. This inconsistency was particularly jarring when observed through the lens of monad transformers which have internal short-circuiting, such as OptionT or EitherT. The following snippet demonstrates the problem quite succinctly:

import cats.data.OptionT
import cats.effect._
import cats.effect.syntax.all._

OptionT.none[IO, Unit].guarantee(OptionT.liftF(IO.println("guaranteed?"))) >> 
  OptionT.liftF(IO.println("definitely not"))

On Cats Effect 3.1.1 and earlier, the above snippet would print nothing at all. The fact that "definitely not" fails to print is probably intuitive: after all, calling flatMap on None is equivalent to None, which is to say the flatMap is "skipped". However, the fact that guarantee was decidedly not "guaranteed" was a little weird, and in some cases, could actually result in deadlocks.

This was an oversight in implementation. In fact, the design of Outcome was always intended to take this possibility into account; it is the reason that Outcome.Succeeded contains an F[A] rather than just an A. Fiber#join leverages this design, but guaranteeCase was never adjusted to do so.

This oversight has now been corrected, and a new set of laws relating guaranteeCase and Fiber#join have been added. These laws codified a set of assumptions that were already generally being made by consumers of the library (the authors included!), and thus they strengthen the guarantees around Cats Effect's semantics rather than making any changes to them. These added laws also have the effect of clarifying the intended semantics of more complex compound effect types (such as monad transformers or polyfunctorial constructors), which should improve compatibility between such types and the vast middleware ecosystem (such as Fs2).

Experimental async/await Syntax

Thanks to the tireless work of @Baccata, Cats Effect has added a new incubator library which implements async/await syntax for all types which form an Async (including IO). This incubator can be found in the typelevel/cats-effect-cps library. Depending on user adoption and feedback, as well as the ongoing work on the Scala 3 compiler, this functionality will likely be added to the Cats Effect core distribution at some point in the future.

async/await syntax has seen a surge of popularity across many language ecosystems in recent years, with JavaScript's implementation being the most notable. Roughly speaking, it is an alternative to for-comprehensions in which imperative statement flow is rewritten into flatMap (and similar). For example:

async[IO] {
  IO.print("Enter your age: ").await
  
  if (IO.readLine.await.toInt % 2 == 0)
    IO.println("You're an even number of years old!").await
  else
    IO.println("Unfortunately, your age is very odd").await
}

The above is equivalent to the following written in a more direct style:

val init = IO.print("Enter your age: ") >> IO.readLine

init flatMap { ageS =>
  val age = ageS.toInt

  if (age % 2 == 0)
    IO.println("You're an even number of years old!")
  else
    IO.println("Unfortunately, your age is very odd")
}

This syntax is supported on Scala 2 using the -Xasync compiler flag, and on Scala 3 through the use of @rssh's dotty-cps-async library. Let us know what you think!

Fiber Tracing and Enhanced Exceptions

One of the most popular features ever added to Cats Effect 2 was support for asynchronous fiber tracing, and in particular, the automatic enhancement of exception stack traces that came along with it. Building on the original work of @RaasAhsan, @vasilmkd has now brought this functionality to Cats Effect 3, and in the process further improved and optimized its implementation.

Exception in thread "main" java.lang.Throwable: Boom!
        at Example$.$anonfun$program$5(Main.scala:25)
        at apply @ Example$.$anonfun$program$3(Main.scala:24)
        at ifM @ Example$.$anonfun$program$3(Main.scala:25)
        at map @ Example$.$anonfun$program$3(Main.scala:24)
        at apply @ Example$.$anonfun$program$1(Main.scala:23)
        at flatMap @ Example$.$anonfun$program$1(Main.scala:23)
        at apply @ Example$.fib(Main.scala:13)
        at flatMap @ Example$.fib(Main.scala:13)
        at apply @ Example$.fib(Main.scala:13)
        at flatMap @ Example$.fib(Main.scala:13)

In particular, one limitation of the Cats Effect 2 tracing implementation is that it is prone to classloader leaks when using it with frameworks like OSGi. This issue has been eliminated in the reimplementation of this functionality by leveraging java.lang.ClassValue (a concurrent hashtable specialized to java.lang.Class keys). Several additional features have been added, including significant optimizations to the full tracing mode.

As in Cats Effect 2, the performance impact of tracing is extremely minimal, but still measurable in synthetic benchmarks. For this reason, tracing defaults to on if not reconfigured by setting -Dcats.effect.tracing.mode=none when launching the application process. You can read more details on the Tracing documentation page.

User-Facing Pull Requests

Special thanks to each and every one of you!

Don't miss a new cats-effect release

NewReleases is sending notifications on new releases.