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