[Haskell] monad transformers
ajb at spamcop.net
ajb at spamcop.net
Sun Jan 30 19:33:09 EST 2005
G'day all.
Quoting John Meacham <john at 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
More information about the Haskell
mailing list