In learning Parsec, I’ve found that somewhat verbose rules like
type PhonemeClassMap = Map Char String
type ContextElement = Parser String
phonemeContext :: Parsec String PhonemeClassMap ContextElement
phonemeContext = do
c <- lower
return $ char c
can be simplified by lifting functions like char into the Parsec / ParsecT monad.
phonemeContext :: Parsec String PhonemeClassMap ContextElement
phonemeContext = liftM char lower
Now I’m trying to simplify a rule which modifies the user state:
import Data.Map (insert)
phonemeClassDefinition :: Parsec String PhonemeClassMap ()
phonemeClassDefinition = do
upperChar <- upper
lowerChars <- char ':' >> spaces >> many1 lower
modifyState (insert upperChar lowerChars)
I can easily lift insert :: Char -> String -> PhonemeClassMap -> PhonemeClassMap to make the following improvement:
phonemeClassDefinition = do
f <- liftM2 insert upper (char ':' >> spaces >> many1 lower)
modifyState f
Is there any way to state these two expressions as one? The same lifting technique does not work for modifyState :: Monad m -> (u -> u) -> ParsecT s u m ().
In this case, you are looking for monadic bind
>>= :: Monad m => (a -> m b) -> m a -> m bwhich allows you to apply a function that takes a pureaand returns a monadic action to a monadica(i.e. apply the function “through” the monad). This function is actually an integral part of the monadic type class, and is what the<-indonotation desugars to under the hood.(Side note, unlike
liftM2,liftM3…, there doesn’t appear to be predefinedbindM2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c(orbindM3etc) for convenience. (Hoogle draws a blank.))Also, Parsec parsers often benefit (stylistically and otherwise) from using its Applicative and Functor instances, not just its Monad one, specifically
<$>(alias offmap/liftM) and the various (semi-)equivalents of monadicap&>>:<*>,<*&*>.(Note that @melpomene’s
=<<is justflip (>>=), i.e. with the arguments swapped.)