Skip to content

Rebranding Cats MTL to emphasize the Capabilities, not the Monad Transformers #516

@benhutchison

Description

@benhutchison

An issue I raised recently with Cats Effect, around WriterT logging not working well with the concurrent parEvalMap in FS2, revealed that the thinking on Cats MTL is shifting.

Cats MTL consists of 2 things:

  • Capability traits like Raise, Tell, Handle and Stateful. These are attached to an abstract effect type F[_] to give it an additional capability. For the 4 traits listed, these capabilities are raising errors, emitting logs, handling errors, and read/writing state, respectively.
  • Implementations of these capability traits, in terms of monad transformer (MT) "stacks" that include an MT suited to the capability; WriterT for emitting logs with Tell, for example.

The documentation currently emphasizes these implementations as the primary role of the library. The homepage leads with the somewhat underwhelming:

Provides transformer typeclasses for cats' Monads, Applicatives and Functors.

You can have multiple cats-mtl transformer typeclasses in scope at once without implicit ambiguity, unlike in pre-1.0.0 cats or Scalaz 7.

However, recent thinking sees more value in the capability traits than the implementations, for at least 2 reasons:

  1. It turns out that MTs prove troublesome in larger, industrial-scale programs that use CE3's Concurrent & friends. WriterT[IO.. losing logs in parEvalMap hints at problem with Concurrent instance for WriterT ? cats-effect#3765 is one example of a rash of issues reported in the last couple of years where MTs do surprising things in the presence of concurrency, and consensus is that these surprises are not "fixable". Therefore, we see advice being issued against MT usage, at least in more demanding scenarios.

  2. However, many capability traits can be usefully implemented even without MTs, in ways that are better suited to industrial software development, by backing the trait with a mutable cats.effect.Ref for Tell & Stateful, or by "submarine error propagation" in the case of Raise and Handle.

    Pleasingly, abstraction of the capability is maintained, in that the trait can be implemented either by Ref, or by MT, without touching application code written in terms of the capability. This allows eg unit tests to use a simpler MT-based solution, while larger applications use a Ref approach.

    An example implementation of a Ref-backed Tell:

  def refTell[F[_]: Sync, L, M[_]](ref: Ref[F, M[L]])(using a: Applicative[M], mo: Monoid[M[L]]): Tell[F, L] =
    new Tell[F, L]:
      def tell(l: L): F[Unit] = ref.update(mo.combine(_, a.pure(l)))
      def functor = Functor[F]

The shifting thinking around MTs affects the role that Cats MTL plays in the Typelevel ecosystem. The capability traits are the gold. We ought to:

  • Revise the Homepage and Getting Started to emphasize the value of abstract capabilities, and deemphasize MTs as the only way to fulfill such capability. Instead mention MT, Refs and "submarine error propagation" as implementation options.
  • Show example code on non-MT solutions.

Such a rebranding, perhaps accompanied by a blog post at https://typelevel.org/blog/, could see Cats MTL become relevant to a wider range of Typelevel users and lead to a well-deserved increase in library adoption.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions