RSSAmplifier

Index on Blog · Sep 30, 2023

Don't worry, your Parser is a functor

0
Sign in to vote or save

blog.ubavic.rs

Back to Index

If you start learning about 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.

Read the original on blog.ubavic.rs

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.