-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Open
Description
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 commentedon Mar 4, 2025
See also
MonadPartialOrder
djspiewak commentedon Mar 5, 2025
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 aPointed
class lifted into an endofunctor category, and it has all the corresponding problems. Withoutrun
, 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 thatDispatcher
behaves in the presence of transformers, fun interactions withTraverse
, 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.