Gabriella439 / Haskell-MMorph-Library

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

Distributive type class? #36

Open jacobstanley opened 7 years ago

jacobstanley commented 7 years ago

I found myself wanting distribute for a number of transformers, including transformer stacks. It seems like this is similar to Distributive but for transformers.

Does this makes sense as a type class?

I found myself having to reach for an associated type to implement it because it seems that a bunch of constraints are needed for every transformer in the stack. Would love to know if anyone can think of a better way as it feels like an enormous and inelegant hammer.

class Distributive g where
  type Transformer
    (f :: (* -> *) -> * -> *)
    (g :: (* -> *) -> * -> *)
    (m :: * -> *) :: Constraint

  type Transformer f g m = (
        Monad m
      , Monad (f m)
      , Monad (g m)
      , Monad (f (g m))
      , MonadTrans f
      , MFunctor f
      )

  distribute :: Transformer f g m => g (f m) a -> f (g m) a

instance Distributive MaybeT where
  distribute x =
    lift . MaybeT . pure =<< hoist lift (runMaybeT x)

instance Distributive (ExceptT x) where
  distribute x =
    lift . ExceptT . pure =<< hoist lift (runExceptT x)

instance Monoid w => Distributive (WriterT w) where
  distribute x =
    lift . WriterT . pure =<< hoist lift (runWriterT x)

instance Distributive (ReaderT r) where
  distribute x =
    join . lift . ReaderT $ \r ->
      pure . hoist lift $ runReaderT x r

For larger stacks, just an example:

newtype Example m a =
  Example {
      unExample :: ExceptT String (WriterT [String] (ReaderT Int m)) a
    }

distributeExample :: Transformer t Example m => Example (t m) a -> t (Example m) a
distributeExample =
  hoist Example .
  distribute .
  hoist distribute .
  hoist (hoist distribute) .
  unExample

instance Distributive Example where
  type Transformer t Example m = (
      Transformer t (ReaderT Int) m
    , Transformer t (WriterT [String]) (ReaderT Int m)
    , Transformer t (ExceptT String) (WriterT [String] (ReaderT Int m))
    )

  distribute =
    distributeExample

I'm not sure if this is possibly related to / would help with my other question https://github.com/Gabriel439/Haskell-MMorph-Library/issues/34

I'm not tied to the names of anything here, I just wanted to have a discussion about whether this class makes sense and whether it would be useful to add to mmorph.

Gabriella439 commented 7 years ago

Is there a way to simplify the Transformer constraint? I try to avoid constraints like Monad (f (g m)) in order to simplify inferred types. If there were a simpler constraint that didn't need to be stored as an associated type constraint then I wouldn't mind adding this to mmorph

jacobstanley commented 7 years ago

Is there a way to simplify the Transformer constraint? I try to avoid constraints like Monad (f (g m)) in order to simplify inferred types.

That's part of the question I suppose, I don't think it's possible, but I may be thinking about it the wrong way.

If you look at the type for distribute in pipes it has the equivalent of the Monad (f (g m)) in the form of Monad (t (Proxy a' a b' b m))):

distribute ::
     Monad m
  => MonadTrans t
  => MFunctor t
  => Monad (t m)
  => Monad (t (Proxy a' a b' b m)))
  => Proxy a' a b' b (t m) r 
  -> t (Proxy a' a b' b m) r

I think you can get away without Monad (g m) for many transformers.

Also it seems like as soon as you have a transformer stack you need the associated type constraint to get all the Monad instances you need. It's like there needs to be a way to say "this is a monad and so are all the things it's on top of".