haskell / mtl

The Monad Transformer Library
http://www.haskell.org/haskellwiki/Monad_Transformers
Other
362 stars 63 forks source link

Add MonadError instances for ContT #71

Closed Atry closed 3 years ago

Atry commented 4 years ago

ContT e (ContT r (a lot of other transformers...)) could be more efficient than ExceptT e (ContT r (a lot of other transformers...)), because >>= and return for ContT e (ContT r m) are as efficient as Cont, while the performance of ExceptT depend on the number of layers of transformers.

chessai commented 4 years ago

We certainly do not want overlapping instances here. We need to pick just one of these definitions.

Atry commented 4 years ago

The purpose of overlapping instances is to support multiple MonadError instances, similar to https://github.com/haskell/mtl/pull/73

Suppose we have a type:

type X = ContT IOException (ContT AllocationLimitExceeded (ContT Int IO))

It should support both the following MonadErrors:

chessai commented 4 years ago

I'm not a fan of overlapping instances in general, especially not with FunDeps. Overlapping allows you to subvert some of the guarantees that FunDeps give you in the first place, those guarantees which have thus far been a trademark of MTL typeclasses. See https://gitlab.haskell.org/ghc/ghc/issues/15927 for some more discussion.

Atry commented 4 years ago

Then how can we gain the ability of multiple MonadError?

chessai notifications@github.com于2020年1月18日 周六下午1:27写道:

I'm not a fan of overlapping instances in general, especially not with FunDeps. Overlapping allows you to subvert some of the guarantees that FunDeps give you in the first place, those guarantees which have thus far been a trademark of MTL typeclasses. See https://gitlab.haskell.org/ghc/ghc/issues/15927 for some more discussion.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/haskell/mtl/pull/71?email_source=notifications&email_token=AAES3OV3AXVUFV7NW43SOFTQ6NX2NA5CNFSM4KISIJP2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEJKCKLI#issuecomment-575939885, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAES3OTYM2KEEGGSPKKQZADQ6NX2NANCNFSM4KISIJPQ .

Atry commented 4 years ago

I did not really understand the initial purpose of the function dependency m -> e on MonadError. Why not just remove m -> e?

Lysxia commented 4 years ago

I did not really understand the initial purpose of the function dependency m -> e on MonadError. Why not just remove m -> e?

The dependency is there for type inference, otherwise you would have to annotate error types in a lot of places. Overlapping instances, which is your motivation for removing those dependencies, can also be quite unintuitive, which is a fairly strong reason not to use them.

For instance, this overlapping instance makes it impractical to use ContT with a MonadError constraint on an abstract m:

-- Doesn't typecheck
throwe :: MonadError e m => e -> ContT r m a
throwe = throwError
Atry commented 4 years ago

It typechecks: https://repl.it/repls/CornsilkStrongDonateware

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE IncoherentInstances #-}

class MonadError e m where
  throwError :: e -> m a

newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r }
instance {-# OVERLAPPING #-} Monad m => MonadError e (ContT e (ContT r m)) where
  throwError e = undefined

instance MonadError e m => MonadError e (ContT r m)  where
  throwError e = undefined

throwe :: MonadError e m => e -> ContT r m a
throwe = throwError

main = return ()