Mail merge (Parsec string formatting) example
Instead of notFollowedBy, you can use noneOf in chunk:

chunk :: Parser (Merge String)
chunk = Chunk &lt;$&gt; many1 (noneOf "&lt;") &lt;|&gt; try (sequence [char '&lt;', noneOf "&lt;"])


That prevents a lookahead but it is no longer that nice to look at. 



format can be rewritten without explicit recursion if we use concatMap:

format :: HM.HashMap String String -&gt; [Merge String] -&gt; String
format hmap xs = concatMap go xs
  where
    go (Chunk c) = c
    go (Key   k) = maybe (error $ "missing key: " ++ k) id (HM.lookup k hmap)


However, this would make our function partial, which we try to prevent. So let's use Either instead:

format :: HM.HashMap String String -&gt; [Merge String] -&gt; Either String String
format hmap xs = concat &lt;$&gt; mapM go xs
  where
    go (Chunk c) = Right c
    go (Key   k) = maybe (Left $ "missing key: " ++ k) Right (HM.lookup k hmap)