Fibers: The Fundamental Abstraction in Cats Effect 3

Reading Time: 2 minutes

Fibers are the fundamental abstraction in Cats Effect, similar to threads, they represent a sequence of actions which will ultimately be evaluated in that order by the underlying hardware.

Fibers are very lightweight. Since fibers diverge from threads is in their footprint and level of abstraction.

In thread each step is a statement, and defined in sequence by writing them in particular order within a text file, whicjh is combined together using the semicolon (;) operator.

In fiber each step is an effect, and defined in sequence by explicitly composing them using the flatMap function. For example:

import cats.effect._

IO.println("Hello") flatMap { _ => 
  IO.println("World")
}

Alternatively, it is also quite common to use for-comprehensions to express the same thing:

for {
_ <- IO.println("Hello")
_ <- IO.println("World")
} yield ()

Since flatMap is just a method like any other, rather magic syntax such as ;, it is possible to build convenience syntax and higher-level abstractions which encode common patterns for composing effects.

e.g, the pattern where we put together two effects, ignoring the result of the first one, is common enough to merit its own operator: >>:

IO.println("Hello") >> IO.println("World")

Every application has a “main fiber”, defined using IOApp, and in particular by the effect returned by the run method:

object Hi extends IOApp.Simple {
val run = IO.println("Hello") >> IO.println("World")
}

Here, Hi is a main class and When it is externally invoked it will start the main fiber and run until that fiber completes, at which point the process will be shut down.

Cancelable

By default, fibers are cancelable at all points during their execution, so unneeded calculations can be promptly terminated and their resources gracefully released as early as possible.

In practice, fiber cancelation most often happens in response to one of two situations: timeouts and concurrent errors. As an example of the former:

import scala.concurrent.duration._

lazy val loop: IO[Unit] = IO.println("Hello, World!") >> loop
loop.timeout(5.seconds) // => IO[Unit]

The above constructs an IO which starts a fiber defined by loop. This fiber prints “Hello, World!” infinitely to standard out. Left to its own devices, the loop fiber will run forever.

However, the timeout function delays for 5 seconds, after which it calls cancel on the fiber.

Conclusion

With Fibers , Abstraction is great, Safer, more composable, higher level.

Leave a Reply