Skip to content

Monad Transformer typeclass #4727

@johnynek

Description

@johnynek
Contributor

I saw this: #4724 and wondered if we have a monad transformer typeclass similar to: https://hackage.haskell.org/package/transformers-0.6.1.2/docs/Control-Monad-Trans-Class.html

I don't see one. I can imagine something like:

trait MonadTrans[T[_[_], _]] {
  def liftK[M[_]: Monad]: FunctionK[M, [A] =>> T[M, A]]
  def lift[M[_]: Monad, A](ma: M[A]): T[M, A]

  implicit def transMonad[M[_]: Monad]: Monad[[A] =>> T[M, A]]
}

And possibly even:

trait MonadTrans[T[_[_], _]] {
  def liftK[M[_]: Monad]: FunctionK[M, [A] =>> T[M, A]]
  def lift[M[_]: Monad, A](ma: M[A]): T[M, A]
  
  implicit def transMonad[M[_]: Monad]: Monad[[A] =>> T[M, A]]
  
  type RunT[_]
  def run[M[_]: Monad, A](fa: T[M, A]): M[RunT[A]]
  def liftRun[M[_]: Monad, A](run: M[RunT[A]]): T[M, A]
}

since I think all the transformers seem to have some notion of a "run" version. The laws would be:

MonadTrans[T].lift(fa) == MonadTrans[T].liftK[M](fa)

MonadTrans[T].lift(Monad[M].pure(a)) == MonadTrans[T].transMonad[M].pure(a)

MonadTrans[T].lift(ma.flatMap(f)) == MonadTrans[T].lift(ma).flatMap(a => MonadTrans[T].lift(f(a)))

MonadTrans[T].liftRun(MonadTrans[T].run(fa)) == fa

The argument against this, I think, would be that it's not clear what useful functions you would write against MonadTrans except perhaps for having a clear way to lift: MonadTrans[OptionT].lift(fa)

Activity

armanbilge

armanbilge commented on Mar 4, 2025

@armanbilge
Member
djspiewak

djspiewak commented on Mar 5, 2025

@djspiewak
Member

since I think all the transformers seem to have some notion of a "run" version. The laws would be:

Definitely not all! You may also be interested in this: https://hackage.haskell.org/package/layers-0.1/docs/Control-Monad-Layer.html This space has been explored quite a bit in the past.

Taking a step back, the first two functions in MonadTrans are basically a Pointed class lifted into an endofunctor category, and it has all the corresponding problems. Without run, you can't really talk much about laws for the pointing itself, though as you demonstrate you can constrain the nature of the categorical lifting, which is not quite the same thing.

Taking a step further back, what you're scratching at (the difficulty of un-layering a transformer stack once composed) is, IMO, the crux of the problem with monad transformers in general. Their generality means it's impossible to take them apart in the same way you put them together, which is very frustrating and causes a whole series of knock-on problems (see also: CE's Outcome.Succeeded case, the way that Dispatcher behaves in the presence of transformers, fun interactions with Traverse, etc etc). Broadly, this is because transformers are general datatypes and functions, rather than restricted handlers of some shape. This is in turn the reason that almost all abstracted machinery around transformers focuses on the construction of such effects rather than the interpretation.

Not to appeal to history, but having spent about a decade and a half grappling with this exact frustration, I think in the end the best compromise solution is MonadPartialOrder. It solves a decent number of problems that have solutions, without attempting to solve anything which doesn't have a general solution, and it kind of bows to the general asymmetry of transformer composition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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

        Participants

        @djspiewak@johnynek@armanbilge

        Issue actions

          Monad Transformer typeclass · Issue #4727 · typelevel/cats