Transformation under Transformers
Asked Answered
L

1

7

I'm having a bit of difficulty with monad transformers at the moment. I'm defining a few different non-deterministic relations which make use of transformers. Unfortunately, I'm having trouble understanding how to translate cleanly from one effectful model to another.

Suppose these relations are "foo" and "bar". Suppose that "foo" relates As and Bs to Cs; suppose "bar" relates Bs and Cs to Ds. We will define "bar" in terms of "foo". To make matters more interesting, the computation of these relations will fail in different ways. (Since the bar relation depends on the foo relation, its failure cases are a superset.) I therefore give the following type definitions:

data FooFailure = FooFailure String
data BarFailure = BarSpecificFailure | BarFooFailure FooFailure
type FooM = ListT (EitherT FooFailure (Reader Context))
type BarM = ListT (EitherT BarFailure (Reader Context))

I would then expect to be able to write the relations with the following function signatures:

foo :: A -> B -> FooM C
bar :: B -> C -> BarM D

My problem is that, when writing the definition for "bar", I need to be able to receive errors from the "foo" relation and properly represent them in "bar" space. So I'd be fine with a function of the form

convert :: (e -> e') -> ListT (EitherT e (Reader Context) a
                     -> ListT (EitherT e' (Reader Context) a

I can even write that little beast by running the ListT, mapping on EitherT, and then reassembling the ListT (because it happens that m [a] can be converted to ListT m a). But this seems... messy.

There's a good reason I can't just run a transformer, do some stuff under it, and generically "put it back"; the transformer I ran might have effects and I can't magically undo them. But is there some way in which I can lift a function just far enough into a transformer stack to do some work for me so I don't have to write the convert function shown above?

Leninist answered 14/5, 2013 at 20:31 Comment(0)
D
3

I think convert is a good answer, and using Control.Monad.Morph and Control.Monad.Trans.Either it's (almost) really simple to write:

convert :: (Monad m, Functor m, MFunctor t)
           => (e -> e')
           -> t (EitherT e m) b -> t (EitherT e' m) b
convert f = hoist (bimapEitherT f id)

the slight problem is that ListT isn't an instance of MFunctor. I think this is the author boycotting ListT because it doesn't follow the monad transformer laws though because it's easy to write a type-checking instance

instance MFunctor ListT where hoist nat (ListT mas) = ListT (nat mas)

Anyway, generally take a look at Control.Monad.Morph for dealing with natural transformations on (parts of) transformer stacks. I'd say that fits the definition of lifting a function "just enough" into a stack.

Distilled answered 14/5, 2013 at 23:52 Comment(3)
Yeah, I was boycotting ListT. Use pipes for a correct ListT. Also, you can use fmapLT from the errors package for modifying the left value.Sniffle
I was looking for fmapLT...! But I could have sworn it was in either and didn't hoogle it.Distilled
Excellent; thank you both. This is exactly what I was wondering about. :)Leninist

© 2022 - 2024 — McMap. All rights reserved.