hoisting as well as lifting and returning

Hi, Happy Easter! I've been using monad transformers for the first time in anger this week. They certainly do the job. However, there's an operation that I keep defining over and over again. It is sort of like lift and return in that it's glue to get operations and values into the monad from outside it. Here are two examples. hoistList :: (Monad m) => [a] -> ListT m a hoistList = foldr (mplus . return) mzero hoistMaybe :: (Monad m) => Maybe a -> MaybeT m a hoistMaybe = maybe mzero return The general idea is that you have some legacy, non-transform operation in some monad MonadFoo, and you are writing an operation in MonadFooT. You want to get the monadFoo value into MonadFooT. So, you say something like: do vInMFT <- hoist vInMF Is this a common enough operation on a monad transformer and it's 'raw' monad to warrant a class of its own? I have 'hoist' methods for several monad transformers, including RamdomMonadT, although I've probably defined them in ways that are overly eager or eat stack/heap, so I'd prefer knowledgable people to write them :) There must be other operations that link the base monad with its transformed version, other than 'hoist' - the runFooT method for example. Perhaps 'hoist' already exists and I'm re-inventing the wheel? Matthew

Matthew Pocock wrote:
I've been using monad transformers for the first time in anger this week.
I hope your enjoyment in using monads has helped your anger to subside. :)
...there's an operation that I keep defining over and over again... hoistList :: (Monad m) => [a] -> ListT m a hoistMaybe :: (Monad m) => Maybe a -> MaybeT m a do vInMFT <- hoist vInMF Perhaps 'hoist' already exists and I'm re-inventing the wheel?
You are correct. This is a fundamental operation. It exists for just about every monad, but in a haphazard and inconsistent way. In my opinion, its type needs to be more polymorphic in a slightly different direction than what you are suggesting. Here's a sampling of what we have now: State :: (s -> (a, s)) -> State s a StateT . return :: Monad m => (s -> (a, s)) -> StateT s m a liftList :: Monad m => [a] -> ListT m a -- ListT_Done_Right ErrorT . return :: Monad m => Either e a -> ErrorT e a You get the picture. Yes, it's a bit of a mess. A general "hoist" function that would work for disparate kinds of monads would require yet an additional parameter to the MonadFoo class for each monad. Or an additional MonadHoist type class. (Or whatever the corresponding additional complexity will be when we move to associated types.) This would be needed to specify what the "underlying structure" is that needs to be hoisted from. I'm not sure that kind of polymorphism would be worth the additional complexity - unless someone can suggest a more beautiful way to capture this generality. Otherwise, I think it would be enough to have a "hoist" function for each monad, based on the name of the monad. What I do sorely feel the need for is a "hoist" for each pair of base/transformer monads: i.e., polymorphic monad constructors. So, for example, if we had mkState :: (st -> (a, st)) -> m a as a member of the MonadState st m class, then it would be so much easier to write functions f :: MonadState st m => ... that could be used without having to refactor it every time the monad stack changes. In general, each monad Foo would have a MonadFoo class (even the monads that don't have one yet) containing (at least) a mkFoo method that lifts the "underlying structure" polymorphically either to Foo or to FooT. btw, a variation on this is to provide only a "hoist" or "mkFoo" for the transformer version of the monad, and then use only transformers, basing every monad stack at the Identity monad. This is what Iavor Diatchki does in his monadlib library. I don't particularly like that approach, though. In the most common simple case, I like being able to specify whether my monad is a transformer or not. Regards, Yitz

Hi Yitz, I was thinking along the lines of a class linking the monad transformer with the base monad: class (MonadTrans mt, Monad b) => MonadTForm mt b | mt -> b where hoist :: (Monad m) => b a -> mt m a This is to restrict it directly between the base monad and the transformed version. If you wish to 'adapt' any other pair of monads, then I think that's another operation, just as lift is a different operation to hoist. Matthew On Sunday 23 March 2008, Yitzchak Gale wrote:
You are correct. This is a fundamental operation. It exists for just about every monad, but in a haphazard and inconsistent way. In my opinion, its type needs to be more polymorphic in a slightly different direction than what you are suggesting.
...
What I do sorely feel the need for is a "hoist" for each pair of base/transformer monads: i.e., polymorphic monad constructors.
So, for example, if we had
mkState :: (st -> (a, st)) -> m a
as a member of the MonadState st m class, then it would be so much easier to write functions
f :: MonadState st m => ...
that could be used without having to refactor it every time the monad stack changes. In general, each monad Foo would have a MonadFoo class (even the monads that don't have one yet) containing (at least) a mkFoo method that lifts the "underlying structure" polymorphically either to Foo or to FooT.
...
Regards, Yitz
participants (2)
-
Matthew Pocock
-
Yitzchak Gale