Gabriella439 / Haskell-MMorph-Library

Monad morphisms
BSD 3-Clause "New" or "Revised" License
47 stars 26 forks source link

Add MonadMorph type class #14

Open wdanilo opened 10 years ago

wdanilo commented 10 years ago

Hello! Is it possible to add a MonadMorph type class to the library? Its implementation is very simple:

class MonadMorph m n where
    morph :: m a -> n a

It allows to define possible morphisms between monads. Right now I and some of my friends are using such class in connection to mmorph lib and I think it should be just built in, what do you think? :)

Davorak commented 10 years ago

A problem that I see with MonadMorph is that there can be multiple valid monad morphisms between two monads. For example:

Just . flip S.evalState (0 :: Int) :: S.State Int a -> Maybe a

(\(x, s) -> if s > 5 then Just x else Nothing) . flip S.runState (0 :: Int)
  :: S.State Int a -> Maybe a

There are probably, similar, typeclasses more specific to what they apply to and what they produce that would conform to useful laws however.

wdanilo commented 10 years ago

@Davorak I know it, but still this functionality is often and widely used. Maybe the name is wrong, maybe it should be named

class DefaultMorph m n where
    defaultMorph :: m a -> n a

I'm talking about providing instances for some default monad morphs. This starts to be interesting when using the hoist function. With the above implementation, the hoist defaultMorph has signature of DefaultMorph m n => t m b -> t n b which is very handy sometimes. What do you think about it?

Gabriella439 commented 10 years ago

My main issue with this is that it only works for the special case where you transform just one layer. To illustrate this, consider the case where I want morph to transform two layers at once, like this:

morph :: ReaderT s Identity r -> StateT s IO r

I might try to write an instance like this:

    instance MMorph (ReaderT s Identity) (StateT s IO) r
        morph = readOnly . hoist generalize

    -- `readOnly` is a monad morphism
    readOnly :: Monad m => ReaderT s m r -> StateT s m r
    readOnly (ReaderT k) = StateT (\s -> do
        r <- k s
        return (r ,s) )

That instance obeys the monad morphism laws, but now we have to write N^2 such instances, where N is the number of monad morphisms that change just one layer. Really, we'd like to only define instances that change just one layer, like this:

    instance Monad m => MMorph (ReaderT s m) (StateT s m) where
        mmorph = readOnly

    instance Monad m => MMorph Identity IO where
        mmorph = generalize

... and then somehow compose them into instances that change multiple layers at a time.

However, we have no good way to "connect" those two instances into one that would correctly auto-convert ReaderT s Identity to StateT s IO. And we can't do mmorph . mmorph because then the compiler can't infer what the intermediate type would be unless we explicitly annotate the intermediate types, but that is no less verbose than just explicitly picking the monad morphisms we wish to apply.

turion commented 3 years ago

I think the closest to this is the already existing MonadBase: https://hackage.haskell.org/package/transformers-base/docs/Control-Monad-Base.html#t:MonadBase