G'day all. Quoting John Meacham <john@repetae.net>:
However, now we come to the crux, I want the various interesting monad operations (MonadReader, MonadWriter, etc.. ) to be able to pass through StatT [...]
Why do you want to do that? Think carefully before you answer. I've never liked the O(n^2) instances. First off, it's unmaintable. Secondly, it's extremely fragile (all of your carefully-written code which uses "read" is going to fail when you need to stack on another Reader for some small part of the code). Thirdly, and most importantly, the "raw" monad operations are almost certainly meaningless and sometimes are misleading. Your "mticks" operation is a perfect example of a good operation. That means something that a mere "tell" does not. Indeed, when you have "mticks", you don't need "tell" at all. So, quite correctly, you don't expose it. Why should things be any different for the other raw monad operations? There are some exceptions, of course. I think that MonadIO, for example, is a very reasonable class to expose because it's unambiguous, meaningful and robust. OK, now here's the way that I do it. First, some state values: data RState = {- detail omitted -} data WState = {- detail omitted -} newtype CounterState = CounterState Int -- Example instance Monoid WState where {- detail omitted -} Now we can create our monad: type MyMReader = ReaderT RState IO type MyMCounter = StateT RWState MyMReader type MyMWriter = WriterT WState MyMCounter newtype MyM a = MyM (MyMWriter a) deriving (Monad, MonadIO) And some internal lift operations (not exported to the MyM clients!): liftMyMtoWriter :: MyMWriter a -> MyM a liftMyMtoWriter = MyM liftMyMtoCounter :: MyMCounter a -> MyM a liftMyMtoCounter = liftMyMtoWriter . lift liftMyMtoReader :: MyMReader a -> MyM a liftMyMtoReader = liftMyMtoCounter . lift Then you can define your meaningful operations: incrementCounter :: MyM Int incrementCounter = liftMyMtoCounter (do RWState x <- get put (RWState (x+1)) return x ) Using the internal lift operations and exposing only meaningful operations gives both you and the client insulation against the transformer stack changing, which it will. As an added bonus, it lets you define your own "lift" instance if that makes sense: type MyMReader m = ReaderT RState m type MyMCounter m = StateT RWState (MyMReader m) type MyMWriter m = WriterT WState (MyMCounter m) newtype MyM m a = MyM (MyMWriter m a) deriving (Monad, MonadIO) liftMyMtoWriter :: (Monad m) => MyMWriter m a -> MyM m a liftMyMtoWriter = MyM liftMyMtoCounter :: (Monad m) => MyMCounter m a -> MyM m a liftMyMtoCounter = liftMyMtoWriter . lift liftMyMtoReader :: (Monad m) => MyMReader m a -> MyM m a liftMyMtoReader = liftMyMtoCounter . lift liftMyMtoBase :: (Monad m) => m a -> MyM m a liftMyMtoBase = liftMyMtoReader . lift instance MonadTrans MyM where lift = liftMyMtoBase
*** Monad transformers rock.
Yes they do, but never forget that they're building blocks for monads which have meaning on their own, independent of implementation. Cheers, Andrew Bromage