monad transformer library (was: Re: list monad transformer)
Iavor Diatchki
diatchki@cse.ogi.edu
Tue, 20 May 2003 10:10:24 -0700
hello,
(appologies for the long post)
Andrew J Bromage wrote:
> G'day.
>
> On Mon, May 19, 2003 at 05:15:30PM -0700, Iavor Diatchki wrote:
>>i
>>think the main probelm with adding transformers to the library is that
>>the amount of code grows quadratically in the number of transformers
>>(as one has to specify how each one interacts with every other one).
>
>
> I personally think that there is too much of that in the existing
> library. Consider, for example, the declarations at the end of
> Control.Monad.State:
>
> instance (MonadState s m) => MonadState s (ReaderT r m) where
> get = lift get
> put = lift . put
>
> instance (Monoid w, MonadState s m) => MonadState s (WriterT w m) where
> get = lift get
> put = lift . put
>
> IMO, this is a misfeature. As a maintainer, working out what
> instances are correct is a hassle and, as you note, leads to quadratic
> code complexity in the worst case. As a user, I know what monad
> transformers I have stacked on top of each other and I know where to
> find the "lift" function.
actually i disagree with this quite strongly :-) ideally i want no lifts
in my program. things like: lift $ lift $ lift $ raise "error" are,
well, annoying.
also if i have a state and an environment (Reader they call it in the
library) i can decide to switch them around as they do not inetract with
each other. now all the lifts need to be changed. and besides if one
has 1 of each transformer the whole "lift" thing is pointless as there
is no ambiguity as to what you mean (i.e. put clearly refers to the
state transformer wherevr it is). when there is more than one copy of a
transformer things are different. then some sort of addressing is
necessary, which is what motivated adding the "indexes" to my library.
they work quite well, but i still think there must be a better way to
achieve the same effect...
as for maintenance, for a number of methods (i.e. ones where computions
do not appear as arguments) there are standard ways of lifting. for
example, what i've been using lately (don't remember if it is on my
website) is:
get' = lift get
and then all instances for get use get'
unfortunatelly i can't quite capture this commonality with a single
instance. perhaps (as bellow) overlapping instances (in some form)
could help.
> The one exception is liftIO, which is very useful because if IO is
> anywhere in your stack of monad transformers, it must be at the
> bottom. I think there's also an argument for liftST.
actually the library can be generalized there. in my library i have a class:
class (Monad m, Monad n) => HasBase m n | m -> n where
inBase :: n a -> m a
for each monad transformer there is an instance:
instance HasBase m n => HasBase (t m) n
inBase = lift . inBase
for every base monad (i.e. one not made out of transformers) there is an
instance:
instance HasBase m m where
inBase = id
we can cut down the number of instances if overlapping intsnaces are
used, but it is not quite clear (at least to me) how they interact with
functional dependencies. using this calss you don't need liftIO and
liftST, and liftId, and liftFudgets, etc...
bye
iavor