FS2 v3.0.0-M1 Release Notes

Release Date: 2020-10-07 // almost 2 years ago
  • ๐Ÿš€ FS2 3.0.0-M1 is the first milestone in the 3.x release series, featuring support for Cats Effect 3. Like Cats Effect 3, we expect a number of milestone releases before a 3.0.0 final release. Neither binary nor source compatibility with FS2 2.x is provided, though APIs are largely the same and porting should not be a large task.

    ๐Ÿš€ The primary feature of FS2 3.0 is support for the Cats Effect 3 type class hierarchy. The Cats Effect 3.0.0-M1 release notes provide a great overview of the changes.

    FS2 3 also includes some notable changes:

    Stream Compilation

    Streams are converted to effects via expressions like s.compile.toList and s.compile.drain. Calling s.compile on a Stream[F, O] requires an implicit Compiler[F, X] (where X is chosen based on the type of compilation requested).

    ๐Ÿ”€ In FS2 2.x, getting a Compiler[F, X] instance for the effect F generally required a Sync[F] (this is not true for the Pure and Fallible effects but is otherwise accurate). In FS2 3, compilation now only requires a Concurrent[F]. If a Concurrent[F] is not available, then compilation falls back to using a Sync[F] -- this allows stream compilation for effects like SyncIO which do not have a Concurrent instance.

    ๐Ÿ‘ In a polymorphic context, compilation is supported by either adding a Concurrent[F] constraint, or if compilation of non-concurrent effects is needed as well, a Compiler.Target constraint:

    import fs2.{Compiler, Stream}import cats.effect.Concurrent// Allows compilation for any Concurrent[F] but not for things like SyncIOdef compileThis[F[\_]: Concurrent, O](f: Stream[F, O]): F[Unit] = f.compile.drain// Allows compilation for any Concurrent[F] or any Sync[F]def compileThat[F[\_]: Compiler.Target, O](f: Stream[F, O]): F[Unit] = f.compile.drain

    No More Blocker

    ๐Ÿ”€ The cats.effect.Blocker type was removed in CE 3 -- instead, Sync[F] supports blocking calls via Sync[F].blocking(thunk). As a result, FS2 APIs that took a Blocker have been simplified and in some cases, operations have been combined (e.g., lines & linesAsync have been combined to just lines).


    ๐Ÿšš The deprecated Sink trait has finally been removed and we now represent sinks via a stream transformation that discards all output values -- Stream[F, O] => Stream[F, Nothing] aka Pipe[F, O, Nothing]. This is different than the old definition of Sink which emitted an undefined number of Unit values -- and as a result of the behavior being undefined, implementations varied widely. Implementations included emitting:

    • 0 unit values
    • 1 unit at termination of the source stream
    • 1 unit per output element from source stream
    • 1 unit per output chunk from source stream
    • etc.

    There are a few ergonomic improvements related to the new encoding of sinks:

    • ๐Ÿ–จ Stream.exec(IO.println(...)) - Stream.exec takes an F[Unit] and returns a Stream[F, Nothing]
    • ๐Ÿ–จ Stream(1, 2, 3).foreach(IO.println) - The foreach method on Stream takes a O => F[Unit] and returns a Stream[F, Nothing]
    • s.unitary - The unitary method on Stream converts a Stream[F, Nothing] to a Stream[F, Unit] which emits a single unit at termination of s
    • Calling flatMap on a Stream[F, Nothing] no longer compiles, which avoids mistakes that result in unexpected behavior.

    Capability Traits

    ๐Ÿ”€ The fs2-io project provides support for working with files and networks, abstracting the Java NIO APIs. The implementations require either Async[F] or Sync[F] depending on whether the underlying NIO API is non-blocking or blocking.

    One of the main features of CE3 is the position of the Sync and Async type classes in the hierarchy. As the most powerful type classes, they are now firmly at the bottom of the hierarchy. To avoid requiring Sync / Async constraints in code that uses fs2-io, we are adding capability traits -- traits which limit the capabilities of an effect to a discrete set of operations. The new fs2.io.file.Files[F] capability trait provides the ability to work with files in the effect F. For example:

    def readAllText[F[\_]: Files](directory: Path): Stream[F, String] =Files[F].directoryStream(directory).flatMap { f =\>Files[F].readAll(f, 1024\*1024) .through(text.utf8Decode) .through(text.lines) }

    ๐Ÿ‘ The readAllText method takes a Files[F] capability instead of an Async[F], providing better parametric reasoning.

    In future FS2 3 milestones, we'll be adding additional capability traits -- e.g., for networking / sockets.


    ๐Ÿš€ Our goal is to release FS2 3.0.0 final in conjunction with Cats Effect 3.0.0 and Scala 3.0.0. We're planning on including some additional improvements in the milestones between now and then, including:

    • Simplifying time limited pull operations via a new TimedPull construct (#2062)
    • โšก๏ธ Improving Queue -- both supporting the planned Queue in CE3 as well as providing a new, optimized queue implementation that supports stream concepts more directly (e.g., termination & error propagation).

    Please provide feedback, both on existing features and the roadmap.