What's the Point of Applicative?

Posted on March 24, 2020

Lots of Scala developers know what a Monad is. A smaller number are aware what the definition of an Applicative Functor. It’s tempting to think of Applicative as a “worse Monad”. This is not exactly wrong, but it doesn’t provide us with any insight into what Applicatives are good for.

In this blog post, I’ll attempt to answer that question by looking at an example where requiring an Applicative, rather than a Monad gives us very different capabilities.

Writing a Command-Line Argument Parser

A fairly common programming problem is writing a parser for command line arguments. That is, a function that takes a list of strings, such as --firstname Joe --surname Warren --tall and converts this into some kind of domain object, such as:

Let’s write a simple interface to a command line parser Fair warning, this is going to be simple demonstration code, rather than a fully featured parser.

We’ll start by defining an error type, because it’s more stylish clearer than littering String everywhere.

And now, let’s define an interface.

Now let’s write a “concrete” parser. We want this to be relatively simple, in this case, a boolean “flag” that will return true or false depending on whether or not the flag is present.

By implementing this as an anonymous class, we prevent the user from pattern matching on the specific implementation of a Parser. This makes our Parser type “opaque”; external code can’t look inside at the implementation details. Creating the anonymous class via the apply method on a Flag object, gives us some nice syntactic sugar, and lets us write Flag("tall") as if we were constructing an instance of a case class.

In a similar vein, let’s add support for a (mandatory) String argument. This is going to require that an argument is passed, such as --firstname Joe, and it’s going to have the type Parser[String]. Unlike the Flag Parser, it is possible for this parser to fail, since either the value, or the entire argument could be missing.

Now, we have some simple building blocks, we need a way to compose them. We’re going to do this by defining a Monad instance for our Parser type.

The world contains enough Monad tutorials, so I’m going to assume that you already know what a Monad is. But for completeness, I will include a (potentially inaccurate and definitely unhelpful) definition.

Monad is a typeclass where instances have both a pure method, which takes a value, and returns that value “within” an instance of the monad (in our case pure[A](a: A): Parser[A]), and a flatMapmethod, which takes an instance of the monad of one type, and a function from that type, to an instance of the monad of a second type, and returns an instance of the monad of the second type (in our case flatMap[A, B](fa: Parser[A])(f: A => Parser[B]): Parser[B]), and also where instances must obey the “Monad Laws”: “Left Identity”, “Right Identity” and “Associativity”.

In my defence, I did say upfront that it would be unhelpful.

We’re going to use the Cats Monad implementation.

If you’re following along at home, the required import is:

Now let’s see our parser in action, and write a parser for the Person case class mentioned above.

This is going to require another set of imports, as we’re using some cats syntactic sugar:

We can use a “for comprehension” to provide an aesthetically pleasing way to “Monadically compose” our simple parsers

And we can write some tests to validate that this works as expected:

Monadic Shortcomings

A common thing to want a command line parser to do (on top of parsing arguments) is to print a simple “usage” summary of the arguments that are expected. Let’s try to extend our Parser interface in order to support this.

If you’re following along, at this point we’re more or less starting from scratch (in a separate package). The only thing we share is the error type, ParseFailure.

The Parser interface is more or less unchanged, except for the addition of a “usage” method.

Flag and StringOption are also more or less unchanged, other than by the addition of simple usage strings.

Now, let’s try to define a Monad for our PrintingParser.

Defining pure is easy enough, a pure Parser doesn’t require any arguments, so we can return an empty usage string, and leave the rest of the implementation unchanged

However, when we try to define flatMap we run into some difficulties with the usage string

Ideally, we’d like for this to include the usage strings from both the first and second parsers.

Unfortunately:

  • We have a fa: PrintingParser[A] (the first parser); it gives us the first half of the usage string 😀
  • We don’t have a PrintingParser[B] (the second parser), just a function f: A => PrintingParser[B] 😞
  • So we need a value of type A in order to get a PrintingParser[B] 🤔
  • We can’t get an A out of our PrintingParser[A] without running it on some arguments, and we want to be able to use usage without supplying an arguments list 🤬

Fortunately:

  • This is where Applicative comes to the rescue 🦸‍♀️

Applicative is a typeclass like Monad, except instead of requiring flatMap, it requires the (weaker) ap. (“Weaker” in this case means that we can build ap out of pure and flatMap, but not the other way round. Because of this, every Monad can be used as an Applicative, but not vice versa.)

ap has the signature (when applied to Parser) def ap[A, B](ff: PrintingParser[A => B])(fa: PrintingParser[A]): PrintingParser[B]. Compared to flatMap, in ap, the function sits “inside” the Parser, so we have direct access to both Parsers when composing them, and can look at both usage strings.

Because Applicative doesn’t provide flatMap, the code to compose our parsers together looks a little different.

mapN from cats, lets us compose a tuple full of applicatives of the same kind together.

I think it’s up for debate whether or not the Applicative or Monadic style of Person parser is more readable. But one thing that I do find noteworthy, is that Scala contains a fair amount of syntactic sugar for working with Monads, in the form of for comprehensions. By comparison, Applicatives don’t have any syntactic sugar, and are build using the “standard” syntax of the Scala language, despite this, people have been able to define operators that allow for an elegant style when working with Applicatives.

The same statement could be made about Haskell, and “do notation” (Haskell syntactic sugar similar to a for comprehension) vs the <$> and <*> opperators.

Applicative Shortcomings

The downside to working with Applicative rather than Monad, is that it does limit the kinds of Parser that it’s possible to write.

With a Monadic parser, it’s possible to use a result returned early in the parse, in order to decide which of two different parsers to use. This may be best explained by example, let’s throw away our Person case class, and make a more complex Algebraic Data Type (ADT):

In this ADT, a Person always has a firstname and may have a surname. This could possibly be represented by two different styles of command line argument

  • --has-surname --firstname John --surname Doe
  • --firstname John

And we could implement a Parser for this like so:

It is possible to make optional arguments without requiring a Monad, (for example, using typeclasses such as Alternative, which I won’t cover here).

With that said, the general problem of wanting to vary the Parser used as a function of the values produced earlier on cannot be solved without a Monad.

There is a trade off between Applicative which makes it possible to define classes with more functionality (such as Parsers with usage strings), vs Monad, which gives us more freedom in how we compose our classes together.

This trade off still applies in languages without Monads, Applicatives and other Higher Kinded Types (HKTs). It’s equally possible to define a Parser such as this one in a dynamically typed language such as Python. But it would not be possible to provide both an automatic usage string, and flatMap style composition as part of the same interface.

One of my favourite things about statically typed functional programming, is that it makes this kind of trade off easy to spot.

Appendix

If you want to run the code in this blogpost, it may be helpful to see the build.sbt with the versions that were used to write it:

The code for this blogpost is available on BitBucket, here.

If you liked this blogpost, and can read Haskell, you may be interested in the optparse-applicative library. While optparse-applicative contains vastly more functionality than the parser discussed here, it did provide the inspiration for this post.