Transformer typeclasses for cats.
Provides transformer typeclasses for cats' Monads, Applicatives and Functors.
As well, there are some abstractions thrown in that allow you to generically lift MTL typeclasses through transformers.
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.
libraryDependencies += "org.typelevel" %%% "cats-mtl-core" % "0.0.2"
Here's a map from cats typeclasses to cats-mtl typeclasses:
MonadReader --> ApplicativeLocal
MonadWriter --> FunctorListen
MonadState --> MonadState
FunctorFilter --> FunctorEmpty
TraverseFilter --> TraverseEmpty
cats typeclass parameters and context bounds have to be rewritten, to include base classes. For example:
[F[_]: FunctorFilter]will have to be adjusted to
[F[_]: Functor: FunctorEmpty],
[F[_]: MonadReader[?[_], E]]will have to be adjusted to
F[_]: Monad: ApplicativeLocal[?[_], E]
The root cause for this is addressed in the motivation section.
cats-mtl provides the following "MTL classes":
All of these are typeclasses built on top of other "base" typeclasses to provide extra laws. Because they are commonly combined, the base typeclasses are ambiguous in implicit scope using the typical cats subclass encoding via subtyping. Thus in cats-mtl, the ambiguity is avoided by using an implicit scope with a different priority to house each conversion
Subclass => Base.
Compared to cats and Haskell's mtl library, cats-mtl's typeclasses have the smallest superclass dependencies practically possible and in some cases smaller sets of operations so that they can be lifted over more transformers.
Several other typeclasses are provided to lift typeclasses through transformer data types, like
They form a hierarchy:
MonadLayerControl \ || || || || || MonadLayerFunctor <----------- ApplicativeLayerFunctor <----------------- FunctorLayerFunctor \ \ \ || || || || || || || || || || || || || || || || || || || || || || MonadLayer <---------------- || ApplicativeLayer <------------------- || FunctorLayer |\______________ \______________ \______________
<X>Layer[M, Inner] is three things:
- an instance of
<X>[Inner]and an instance of
FunctionK[Inner, M]which respects the
- a higher-kinded variant of
cats.Invariant, which maps
(Inner ~> Inner, Inner ~> Inner)) to
M ~> M)
It lets you lift monadic values into other monads which are "larger" than them, for example monads that are being used inside monad transformers. For example,
MonadLayer to lift through a transformer, because all of its operations are invertible.
<X>LayerFunctor is a
<X>Layer but instead a variant of
cats.Functor, which maps
Inner ~> Inner) to
M ~> M).
Mapping natural transformations over the inner
MonadLayerControl allows you to lift operations that consume
M using the
layerControl method, which allows one the ability to "unravel" an
M[A] to an
Inner[State[A]] temporarily, as long as the value produced with it is already in the
M[_] context. For example,
FunctorListen requires this typeclass to lift through a transformer.
Why not MonadTrans, etc?
MonadTrans forces the shape
T[_[_], _] on anything which can "contain" another monad, but newtyping around an instantiated transformer eliminates that shape. I don't see usecases in the wild which lift transformations
M ~> N inside a transformer to implement any MTL class, so the extra power was removed.
From the scaladoc:
ApplicativeAsk[F, E] lets you access an
E value in the
F[_] context. Intuitively, this means that an
E value is required as an input to get "out" of the
ApplicativeLocal[F, E] lets you substitute all of the
ask occurrences in an
F[A] value with
ask.map(f), for some
f: E => E which produces a modified
FunctorEmpty[F] allows you to
map and filter out elements simultaneously.
FunctorListen[F, L] is a function
F[A] => F[(A, L)] which exposes some state that is contained in all
F[A] values, and can be modified using
FunctorRaise[F, E] expresses the ability to raise errors of type
E in a functorial
F[_] context. This means that a value of type
F[A] may contain no
A values but instead an
E error value, and further
map calls will not have any values to execute the passed function on.
FunctorTell[F, L] is the ability to "log" values
L inside a context
F[_], as an effect.
MonadState[F, S] is the capability to access and modify a state value from inside the
F[_] context, using
set(s: S): F[Unit] and
The motivation for cats-mtl's existence can be summed up in a few points:
- using subtyping to express typeclass subclassing results in implicit ambiguities, and doing it another way would result in a massive inconsistency inside cats if only done for MTL classes. for a detailed explanation, see Adelbert Chang's article here.
- most MTL classes do not actually require
Monadas a constraint for their laws. cats-mtl weakens this constraint to
Applicativewhenever possible, with the result that there's now a notion of a
Functortransformer stack and
Applicativetransformer stack in addition to that of a
- the most used operations on
ask, and the other operations severely restrict the space of implementations despite being used much less. To fix this
ApplicativeLocalare subclasses of
ApplicativeAsk, which have only the essentials.
The first point there means that it's impossible for cats-mtl type classes to expose their base class instances implicitly; for example
F[_]: FunctorEmpty isn't enough for a
Functor[F] to be visible in implicit scope, despite
FunctorEmpty containing a
Functor instance as a member. The root cause here is that prioritizing implicit conversions with subtyping explicitly can't work with cats and cats-mtl separate, as the
Functor[F] instance for the type from cats will always conflict with a derived instance.
F[_]: FunctorFilter, translated, becomes
F[_]: Functor: FunctorEmpty.
For some historical info on the origins of cats-mtl, see:
Type class laws come in a few varieties in cats-mtl: internal, external, and free.
Internal laws dictate how multiple operations inter-relate. One side of the equation can always be reduced to a single function application of an operation. These express "default" implementations that should be indistinguishable in result from the actual implementation.
External laws are laws that still need to be tested but don't fall into the internal laws.
Free laws are (in theory) unnecessary to test, because they are implied by other laws and the types of the operations in question. There will usually be rudimentary proofs or some justification attached to make sure these aren't just made up.
People are expected to follow the Typelevel Code of Conduct when discussing cats-mtl on the Github page, Gitter channel, or other venues.
We hope that our community will be respectful, helpful, and kind. If you find yourself embroiled in a situation that becomes heated, or that fails to live up to our expectations, you should disengage and contact one of the project maintainers in private. We hope to avoid letting minor aggressions and misunderstandings escalate into larger problems.