Now and then I find myself mapping over a deep stack of functors, e.g. a parser for some collection of optional values:
-- parse a rectangular block of characters to a map of
-- coordinate to the character, or Nothing for whitespace
parseRectangle :: Parser (Map (Int, Int) (Maybe Char))
data Class = Letter | Digit | Other
classify :: Char -> Class
parseClassifiedRectangle :: Parser (Map (Int, Int) (Maybe Class))
parseClassifiedRectangle = fmap (fmap (fmap classify)) parseRectangle
What are some good ways around the nested fmap
s? Oftentimes it's not as clear as here, and I end up adding fmap
s until the code type checks. Simple code ends up as a mess of fmap
boilerplate, where what I really want to express is "lift this function to the appropriate depth and apply it to the contained type".
Some ideas, none of which I've found particularly satisfactory so far:
fmap2 :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b)
and friendsmapMaybeMap :: (a -> b) -> Map k (Maybe a) -> Map k (Maybe b)
newtype
wrappers for the functor stack, and make those instances of Functor
, like newtype MaybeMapParser a = Parser (Map (Int, Int) (Maybe a))
Do others run into this problem in large codebases? Is it even a problem? How do you deal with it?
Let me break the ice on this interesting question that people seem shy about answering. This question probably comes down to more of a matter of style than anything, hence the lack of answers.
My approach would be something like the following:
parseClassifiedRectangle :: Parser (Map (Int, Int) (Maybe Class))
parseClassifiedRectangle = doClassify <$> parseRectangle
where
doClassify = Map.map (fmap classify)
I try to use <$>
for the top level Functor, and save fmap
for interior functors; although that doesn't always work too well in practice.
I've used a local named binding. But even if doClassify
were left as f
it sometimes helps clarify a high level view of whats happening: "on the parsed value we are doing a thing, see below for what thing does." I don't know what the efficiency concerns are for making a binding.
I've also used the specific instance of fmap
for the Map instance. This helps orient me within the stack and gives a signpost for the final fmap
.
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With