Earth Engine
Earth Engine

Reputation: 10436

Why runXXX is not part of MonadTrans definition?

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

Answers (2)

Earth Engine
Earth Engine

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

Gabriella Gonzalez
Gabriella Gonzalez

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

Related Questions