
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