Back to Index

Don't worry, your Parser is a functor

If you’re delving into the world of applicative parsers, at the very start you’ll encounter the following abstract type (or something similar):

newtype Parser a = P (String -> Maybe (String, a))

This type represents a function that takes an input string and maybe produces a pair containing the parsed token with the rest of the string.

Once you’ve grasped the concept of the Parser type, you can explore how to make it a functor. Defining the Functor instance for Parser is a straightforward task:

instance Functor Parser where
    fmap f (P p) = P (\s -> f' (p s))
       where
            f' (Just (x, y)) = Just (x, f y)
            f' Nothing       = Nothing

The harder part is proving this definition is respecting functor laws. Even if these proofs are also straightforward, they require a bunch of writing and case analysis.

A Shortcut

Fortunately, there’s a quicker way to prove functor laws for Parser. We can leverage the compositionality of functors. Notice that Parser is the composition of three abstract types, all of which are functors: (,) String, Maybe, and (->) String. This compositionality extends to the fmap level, allowing us to express the Functor Parser instance more concisely:

instance Functor Parser where
    fmap f (P p) = P $ fmap (fmap (fmap f)) p

The only thing left is to prove that the composition of functors is a functor. But that is straightforward:

Lemma: for any two functions $f$ and $g$ such that $g\circ f$ is defined, and any two functors $L$ and $H$ such that $H \circ L$ is defined, it holds that $H\circ L$ is also a functor because $$H \left(L \operatorname{id}\right) = H \operatorname{id}= \operatorname{id},$$ $$H \left(L \left(g\circ f\right)\right) = H \left(L g\circ L f\right) = H \left(L g\right)\circ H\left(L f\right).$$

By applying this lemma, we can conclude that Parser is indeed a functor.

Challenges with Applicative

Now, when it comes to making Parser an Applicative instance, the journey becomes more intricate. The Applicative class introduces the pure and <*> methods, which are not as straightforward to adapt as fmap. For instance, defining pure for Parser requires the input string to be present in the resulting pair (pure x = P $ \s -> Just (s, x)), unlike pure for (,) String which is defined as pure x = ("", x).

Still, even with these difficulties, you can try to put as much as you can pure and <*> methods inside new definitions. You will notice that this can shorten you proofs.