Reputation: 10436
The MonadTrans
document says:
Each monad transformer also comes with an operation runXXX to unwrap the transformer, exposing a computation of the inner monad.
So I wonder why MonadTrans
is not defined as
class MonadTrans t where
type p :: *
type r :: * -> *
lift:: Monad m => m a -> t m a
run :: t m a -> p -> m (r a)
to eliminate the above clause? Is that the above definition not generic enough? If so, which monad transformer does not fit for this definition?
UPDATE
Adjusted a little bit to enable different result type.
Upvotes: 4
Views: 659
Reputation: 10436
I was thought about this again and again, and in additional to the accepted answer I wanted to add more comments.
As we have seen so far, it is not easy to define the run
interface for ALL possible MonadTrans
. But there is exactly one implementation that is guaranteed to be able to suit all cases:
class MonadTrans' t where
type r m a :: *
lift':: Monad m => m a -> t m a
run :: Monad m => t m a => r m a
With fist glance it seems to solved the problem...
But wait, a MonadTrans'
implementer can then treat:
instance MonadTrans t => MonadTrans' t where
type r = t
lift' = lift
run = id
So actually this definition does not constraints anything and the clause about runXXX
is not able to be eliminated...
Anyway, the way run
does above is actually have an interesting usage. In this post the author claim that only a single type class in Haskell is sufficient to have all(most?) type class benefits like overloading etc.
class C l t | l -> t where ac :: l -> t
Upvotes: 0
Reputation: 35089
There is no universal interface for running monad transformers. For example, try running LogicT
or ContT
or FreeT
using your interface.
Even if you could generalize your interface to handle all of these example, you would still be missing the key ingredient: laws. Type class methods should obey equations that allow you to reason about code that uses the type class interface without consulting the source of specific instances. For example, the lift
method from MonadTrans
must obey these to laws:
lift (return x) = return x
lift (m >>= f) = lift m >>= \x -> lift (f x)
There are nice theoretical reasons why lift
should obey these laws, which become more apparent if you write the laws in this point-free style:
(lift .) return = return -- fmap id = id
(lift .) (f >=> g) = (lift .) f >=> (lift .) g -- fmap (f . g) = fmap f . fmap g
In other words, (lift .)
is a functor between two kleisli categories and lift
is therefore a monad morphism.
A lot of thought goes into defining type classes like Functor
, Monad
, and MonadTrans
, and the Typeclassopedia is a great place to start learning more about this topic.
Upvotes: 15