cats-mtl Design
Overall, cats-mtl has similar design to cats: instances package, syntax package, implicits package.
There's also a hierarchy package, because cats-mtl uses a different typeclass encoding than cats to avoid implicit ambiguity.
The problem looks like this:
import cats._
type Err
type Log
def f[F[_]: MonadError[?, Err]: MonadWriter[?, Log]]: Unit = {
      // implicitly[Monad[F]] // ambiguity between derived monads from MonadError and MonadWriter
      // 1.pure[F].flatMap(_ => 1.raise) // same ambiguity affects MonadSyntax
      ()
}The cause is that the implicit conversions MonadError[F, Err] => Monad[F] and 
        MonadWriter[F, Log] => Monad[F] are the same priority. If those conversions 
        were different priorities, the issue would be fixed, and that's exactly what cats-mtl's encoding does.
Similarly to the Scato project, instead of inheritance the transformer classes in cats-mtl use aggregation. Mtl classes thus contain instances of their superclasses.
Motivation
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 toFunctororApplicativewhenever possible, with the result that there's now a notion of aFunctortransformer stack andApplicativetransformer stack in addition to that of aMonadtransformer stack.
- the most used operations on MonadWriterandMonadReaderaretellandask, and the other operations severely restrict the space of implementations despite being used much less. To fix thisListenandLocalare subclasses ofTellandAsk, 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[_]: Stateful[?[_], S] isn't enough
        for a Monad[F] to be visible in implicit scope, despite Stateful containing a Monad
        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 Monad[F] instance for the type 
        from cats will always conflict with a derived instance.
Thus F[_]: Stateful[?[_], S], translated, becomes F[_]: Monad: Stateful[?[_], S].
For some historical info on the origins of cats-mtl, see:
https://github.com/typelevel/cats/issues/1210
https://github.com/typelevel/cats/pull/1379
https://github.com/typelevel/cats/pull/1751
Laws
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.