
I've run into a situation where I need to 'weave' two monads together, but I can't figure out how it's done. The first monad is "Parser a" from Attoparsec, and the second is one that I've created called "CBC a", with the following relevant functions: -- Decrypts a word in cipher-block-chaining mode decryptCBC :: Word128 -> CBC Word128 -- Performs a cipher-block-chaining mode operation with the given -- key and initialization vector. cbc :: Key -> IV -> CBC a -> a I've also defined a special parser for Attoparsec, for extracting the next 128 bit word from the stream: word128 :: Parser Word128 My problem is that I would like to write a function which reads a word, decrypts it, and then uses the result to make a decision about whether to read further words or return a result. So far, I think my function would have a result type of: func :: Parser (CBC foo) but within the function, if I do something like: func = do x <- word128 let r = do y <- decryptCBC x if (predicate y) then return bar else -- somehow read more from the parser? return r But within this function I run into the situation where my types turn into something like Parser (CBC (Parser a)). I have a feeling that I'm just going about this in a wrong way. I realize that I probably don't need to use Parser here, since I'm just reading a stream of words, but I'd still like to learn how to do this in the general case. Is there anyone that could point me in a better direction? Thanks, Ron

Hi Ron Generally you would want a monad transformer for combining the effects of of two monads - one monad remains as it is, the other is modified slightly to make it a "transformer" rather than a regular monad. It looks like Attoparsec doesn't supply a transformer instance so that obliges you to make CBC a transformer instead - prosaically this means making CBC an instance of monad transformer class Trans and extending the type of CBC to be something like "CbcT m a" rather than "CBC a" so the monad it transforms can be supplied as a parameter "m". As Attoparsec isn't a transformer you will have to "lift" Attoparsec operations into the CbcT monad so func will look something like this - note the change on the second line: func = do x <- lift $ word128 let r = do y <- decryptCBC x if (predicate y) then return bar else -- somehow read more from the parser? return r Best wishes Stephen

Hello Ron Further, here's a code sketch that shows roughly what you will need to do. I've stubbed the CBC monad and CbcT transformer to be the Identity monad (with different names) - the Identity Monad is the simplest monad. -- Code follows: -- Stubbing CBC just as a warm up and so you can see the -- difference between the monad and monad transformer -- versions: newtype CBC a = CBC { getCBC :: a } -- The bind (>>=) and return of the Identity monad. -- instance Monad CBC where return a = CBC a (CBC a) >>= mf = mf a func :: Parser (CBC Word128) func = do x <- word128 let r = do y <- decryptCBC x if (y == 255) then return 0 else undefined -- somehow read more from the parser? return r --- With a transformer: -- Make a transformer version of CBC: -- -- For simplicity again I'm stubbing the monad as the Identity -- monad transformer. -- newtype CbcT m a = CbcT { getCbcT :: m a } -- Note the instance of Monad for the transformer can use (>>=) -- and return from the base monad. -- -- The bind (>>=) and return here are equivalent to (>>=) and -- return of the Identity monad. -- instance Monad m => Monad (CbcT m) where return a = CbcT (return a) (CbcT ma) >>= mf = CbcT $ ma >>= (getCbcT . mf) -- Make a type synonym for the combination of the CbcT -- transformer and the base monad Parser. -- type CbcParser a = CbcT Parser a -- Make an instance of lift. -- -- With a more powerfully monad than Identity - lift will -- be more complicated. -- -- Often, if the monad is based on a stack of transformers -- lift is in the form -- -- > lift = MyMonadT . lift . lift -- -- instance MonadTrans CbcT where lift = CbcT -- Again, a stub of the decryptCBC function. -- -- Note the type signature here is more general than -- necessary allowing it to be used with any base monad -- not just Parser. -- decryptCBC_T :: Monad m => Word128 -> CbcT m Word128 decryptCBC_T x = return (x-1) -- just a dummy -- The "func" function again. -- -- Because we are using the combined monad CbcParser -- (aka CbcT <transformer> + Parser <base>), we don't need -- the "let r = ..." code that previously "ran" the CBC monad. -- func_T :: CbcParser Word16 func_T = do x <- lift $ word128 y <- decryptCBC_T x if (y == 255) then return 0 else (lift $ word128) -- silly idea, -- just return next Word128

On Sun, Mar 28, 2010 at 06:03:04PM +0100, Stephen Tetley wrote:
It looks like Attoparsec doesn't supply a transformer instance so that obliges you to make CBC a transformer instead
I should note that in general monad transoformers do not commute. That is, ParserT CBC a and CbcT Parser a may not mean the same thing. For example, suppose you get a parse failure and you need to backtrack. Do you want to maintain the same CBC state, or do you want to backtrack the CBC state as well? I'd guess the latter, so probably having a CbcT won't help you, I think. -- Felipe.
participants (3)
-
Felipe Lessa
-
Ron Leisti
-
Stephen Tetley