
MonadReader, MonadWriter and MonadState classes have fundeps from the monad type to the environment / output / state type (I added them a few months ago). MonadError doesn't. I thought that it's desirable to make a class with more than one exception type, such that for example each catchError would catch the corresponding throwError only, or even with simulation of some subtyping policy. Now I'm not sure that it was the right choice. I have an example when having this fundep would be very helpful: I am experimenting with building monadic parsing combinators from monad transformers. In particular I already have a Parsec-like parsing monad composed from pieces. It would be bad to hardwire the error type. But without a fundep in MonadError overloaded functions can't be written with MonadError in the context and without mentioning the error type in their type (which would be impractical). My workaround is to have a separate class, similar to MonadError but with a fundep, with MonadError in its superclass, and with its own instances. Fortunately this class can be empty, taking implementation of pushing the error handling through monad transformers from MonadError. But it would be more straightforward to use MonadError directly. I make use of the fundep in MonadState too. So I think that usually one wants these fundeps. Other argument for having a fundep in MonadError: monad transformers provided by modules in package lang don't provide a monad which could handle multiple errors anyway, and it would be impossible to do it generically without overlapping instances. An instance would have to recognize "its" error type, so it must work on a hardwired type constructor. If there was a fundep, the behavior of different error types can be simulated by providing functions defined in terms of throwError and catchError which throw a specific type or catch only a specific type (this works only in the case where handling of all errors is done at the same level of monad transformers, but I doubt that anyone would make a monad without this property and still wanted to use generic throwing and catching functions). This can be even done generically in terms of classes which coerce a specific error type up or recognize it, without touching all monads, as I am doing in the workaround in the opposite direction. What do you think? If nobody answers, I think I will add the fundep... BTW, another question: should MonadPlus instead of just Monad be a superclass of MonadError? It has a natural definition in terms of catchError. -- __("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/ \__/ ^^ SYGNATURA ZASTÊPCZA QRCZAK

On Thu, May 10, 2001 at 09:24:36PM +0000, Marcin 'Qrczak' Kowalczyk wrote:
MonadReader, MonadWriter and MonadState classes have fundeps from the monad type to the environment / output / state type (I added them a few months ago).
Why? This makes composing and "subtyping" impossible: instance (MonadTrans t, MonadState s m, Monad (t m)) => MonadState s (t m) where get = lift get put = lift . put This is really useful, because you can thread multiple states (or read multiple environments, or write multiple outputs, or whatnot), and _directly_ use functions that operate on a particular state, without having to do explicit lifting (to the correct level) all the time. Fundeps add convenience, yes, but they restrict what you can do with the monads. A bad tradeoff, IMHO. Ah well, I'm kind of used to having to use customized versions of all the common libs... Lauri Alanko la@iki.fi

On Fri, 11 May 2001, Lauri Alanko wrote:
Why? This makes composing and "subtyping" impossible:
instance (MonadTrans t, MonadState s m, Monad (t m)) => MonadState s (t m) where get = lift get put = lift . put
This instance is illegal anyway. One of types in the instance head must be a type constructor applied to something (type variables in Haskell 98, anything with -fglasgow-exts). Even if it was legal, it would overlap with instance Monad m => MonadState s (StateT s m) Also MonadReader and MonadWriter can't have such generic instances anyway because their methods have values of type 'm a' as arguments. OTOH without the fundep there are ambiguities. For example: class ParsingState s where stateInput :: s -> String stateSkip :: Int -> s -> s instance ParsingState String where ... instance ParsingState s => ParsingState (s, Pos) where ... input :: (ParsingState s, MonadState s m) => m String -- Ambiguous without the fundep. input = gets stateInput -- Marcin 'Qrczak' Kowalczyk

On Fri, May 11, 2001 at 02:14:24PM +0200, Marcin 'Qrczak' Kowalczyk wrote:
On Fri, 11 May 2001, Lauri Alanko wrote:
Why? This makes composing and "subtyping" impossible:
instance (MonadTrans t, MonadState s m, Monad (t m)) => MonadState s (t m) where get = lift get put = lift . put
This instance is illegal anyway. One of types in the instance head must be a type constructor applied to something (type variables in Haskell 98, anything with -fglasgow-exts).
Ah. So it seems. Pardon. It works in Hugs, though.
Even if it was legal, it would overlap with instance Monad m => MonadState s (StateT s m)
Yep, but in hugs +o the latter overrides the first one. Which is quite convenient.
Also MonadReader and MonadWriter can't have such generic instances anyway because their methods have values of type 'm a' as arguments.
Oh? translift :: (MonadTrans t, Monad m, Monad (t m)) => (m a -> m b) -> t m a -> t m b translift f m = m >>= lift . f . return instance (MonadTrans t, MonadReader r m, Monad (t m)) => MonadReader r (t m) where ask = lift ask local = translift . local instance (MonadTrans t, MonadWriter w m, Monad (t m), Monoid w) => MonadWriter w (t m) where tell = lift . tell listen = translift listen pass = translift pass
OTOH without the fundep there are ambiguities. For example:
class ParsingState s where stateInput :: s -> String stateSkip :: Int -> s -> s
instance ParsingState String where ... instance ParsingState s => ParsingState (s, Pos) where ...
input :: (ParsingState s, MonadState s m) => m String -- Ambiguous without the fundep. input = gets stateInput
So it is, and why not? Is it inconceivable that m might actually have multiple ParsingStates, and thus you really have to specify which one you want to use to get the input? Lauri Alanko la@iki.fi

On Fri, 11 May 2001, Lauri Alanko wrote:
Yep, but in hugs +o the latter overrides the first one. Which is quite convenient.
I doubt that it works predictably in all cases (when state types are not known statically). I can try to construct an example if you wish.
translift :: (MonadTrans t, Monad m, Monad (t m)) => (m a -> m b) -> t m a -> t m b
translift f m = m >>= lift . f . return
instance (MonadTrans t, MonadReader r m, Monad (t m)) => MonadReader r (t m) where ask = lift ask local = translift . local
instance (MonadTrans t, MonadWriter w m, Monad (t m), Monoid w) => MonadWriter w (t m) where tell = lift . tell listen = translift listen pass = translift pass
This gives wrong results (but I haven't checked). For example listen :: Monoid w => ReaderT r (Writer w) a -> ReaderT r (Writer w) (a, w) doesn't listen what the action tells, but listens to 'return' which always tells mempty. Similarly 'local' first runs the action in the original environment and then provides a new environment to 'return' which doesn't look at it. I did most monad transformer forwarding instances in ghc-5.00 and hope that I got them right, but I haven't tested them much. It's not that mechanical (except MonadState), and some combinations can't be done at all. It could be advantageous to put something like translift in an extension of MonadTrans. AFAIR many liftings of this type are similar (but the function must be provided separately for each state transformer), so it would simplify making forwarding instances.
Is it inconceivable that m might actually have multiple ParsingStates, and thus you really have to specify which one you want to use to get the input?
The idea is to use a single state and abstract over the way in which interesting components are contained in it. It has these advantages: * It works. I doubt that automatic recognition of the state type would work. * It allows to have multiple components of the same type in the state. Now I see that my simulation of a fundep without the fundep (an extra class which generates the dependency, instantiated separately for each monad transformer, with MonadError as a superclass) doesn't work that well: throwError would still be ambiguous so it needs a wrapper with a type which tells how to determine the error type using the new class. So I'm now convinced that MonadError should have the fundep too. Some other mechanism could be invented to make it easier to embed various components in the same type (for MonadReader & MonadState) or various alternatives (for MonadError). I have a rather heavy proposal for the first case (a language extension which redesigns records). OCaml has a dual mechanism for the second (polymorphic variants). If my records succeed, I will try to cover variants too. -- Marcin 'Qrczak' Kowalczyk

G'day all. On Thu, May 10, 2001 at 09:24:36PM +0000, Marcin 'Qrczak' Kowalczyk wrote:
BTW, another question: should MonadPlus instead of just Monad be a superclass of MonadError? It has a natural definition in terms of catchError.
I can see how mplus has a "natural" definition (I can think of one or two circumstances where it's not semantically "natural", even if it is operationally "natural", but that's beside the point). What about mzero, though? What "natural definition" did you have in mind? Cheers, Andrew Bromage

Fri, 11 May 2001 14:40:27 +1000, Andrew J Bromage
What about mzero, though? What "natural definition" did you have in mind?
You are right, mzero is not as automatic as mplus. It surely means 'throwError something', but the value of something must be determined separately. 'fail' belongs here too: I think it's always 'throwError something' in practice. -- __("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/ \__/ ^^ SYGNATURA ZASTÊPCZA QRCZAK
participants (3)
-
Andrew J Bromage
-
Lauri Alanko
-
Marcin 'Qrczak' Kowalczyk