What the applicative? Optparse-applicative from the ground up (Part 2)

In this post we’re going to be following on from the previous entry covering some of the foundations of how applicatives work with a slightly more concrete example. We’ll be breaking down an example usage of optparse-applicative quite slowly to explore applicatives; if you’re just looking for some examples of usage, I’d refer to the already excellent documentation here.

Project Setup

Create a baseline project using stack:

$ stack new gh-cli

Once you’ve done this all we need to do is add optparse-applicative to our package.yaml file under dependencies:

dependencies:
- base >= 4.7 && < 5
- optparse-applicative

Setting up some working baseline code

Next let’s start with a very basic example that we’ve shamelessly stolen from the optparse-applicative docs and plug this into our Main.hs:

module Main (main) where

import Options.Applicative

data Sample = Sample
  { hello      :: String
  , quiet      :: Bool
  , enthusiasm :: Int }

sample :: Parser Sample
sample = Sample
      <$> strOption
          ( long "hello"
         <> metavar "TARGET"
         <> help "Target for the greeting" )
      <*> switch
          ( long "quiet"
         <> short 'q'
         <> help "Whether to be quiet" )
      <*> option auto
          ( long "enthusiasm"
         <> help "How enthusiastically to greet"
         <> showDefault
         <> value 1
         <> metavar "INT" )
          
main :: IO ()
main = greet =<< execParser opts
  where
    opts = info (sample <**> helper)
      ( fullDesc
     <> progDesc "Print a greeting for TARGET"
     <> header "hello - a test for optparse-applicative" )

greet :: Sample -> IO ()
greet (Sample h False n) = putStrLn $ "Hello, " ++ h ++ replicate n '!'
greet _ = return ()

Running using stack we can see that this is working:

$ stack exec -- gh-cli-exe
Missing: --hello TARGET

Usage: gh-cli-exe --hello TARGET [-q|--quiet] [--enthusiasm INT]

  Print a greeting for TARGET

$ stack exec -- gh-cli-exe --hello Pablo
Hello, Pablo!

NB: I had a bit of bother getting emacs to connect to the haskell lsp having not run it in a while due to some versioning issues. For me the fix was to use ghcup to get onto all the recommended versions. I had to also manually point lsp-haskell to the correct path. My init.el file now looks like this:

(use-package lsp-haskell
  :ensure t
  :config
 (setq lsp-haskell-server-path "haskell-language-server-wrapper")
 ;; gives a little preview on hover, useful for inspecting types. set to nil to remove. 
 ;; full list of options here https://emacs-lsp.github.io/lsp-mode/tutorials/how-to-turn-off/
 (setq lsp-ui-sideline-show-hover t)
 (setq lsp-haskell-server-args ())
 (setq lsp-log-io t)
)

Quick refactor before we get started

In this post we’re going to be breaking down exactly what’s going on here; specifically looking at how applicatives are used. Before we get started let’s do a quick refactor; this isn’t strictly necessary but the code in the examples is a little terse so it can be harder to tell exactly what each part is doing. Right at the start of main we’ve got:

main = greet =<< execParser opts
  where ...

=<< is just syntactic sugar for bind but with the arguments reversed (ie (a -> m b) -> m a -> m b) which is elegant, but let’s just refactor this to good old do notation in the interests of clarity:

main = do
  parsedOpts <- getInput
  greet parsedOpts
  where
    getInput = execParser opts
    opts = ...

Lets take this a step further and do a bit more of a tidy up, improve some variable names, add some type hints, and run fourmolu set the formatting:

data InputOptions = InputOptions
    { helloTarget :: String
    , isQuiet :: Bool
    , repeatN :: Int
    }

inputOptions :: Parser InputOptions
inputOptions =
    InputOptions
        <$> strOption
            ( long "helloTarget"
                <> metavar "TARGET"
                <> help "Target for the greeting"
            )
        <*> switch
            ( long "quiet"
                <> short 'q'
                <> help "Whether to be quiet"
            )
        <*> option
            auto
            ( long "enthusiasm"
                <> help "How enthusiastically to greet"
                <> showDefault
                <> value 1
                <> metavar "INT"
            )

main :: IO ()
main = do
    parsedInput <- getInput
    handleInput parsedInput
  where
    infoMod :: InfoMod a
    infoMod =
        fullDesc
            <> progDesc "Print a greeting for TARGET"
            <> header "hello - a test for optparse-applicative"

    parser :: Parser InputOptions
    parser = inputOptions <**> helper

    opts :: ParserInfo InputOptions
    opts = info parser infoMod

    getInput :: IO InputOptions
    getInput = execParser opts

handleInput :: InputOptions -> IO ()
handleInput (InputOptions h False n) = putStrLn $ "Hello, " ++ h ++ replicate n '!'
handleInput _ = return ()

We’ve gone a little overboard with extracting some of our variables here and we’ve added some redundant type declarations we don’t actually need, but this hopefully helps clarify some of the types.

What’s actually going on here?

Let’s break down what’s going on into chunks so we can see exactly how this works; for the sake of completeness we’ll first break down our main function. The first thing we do is set up our input type opts:

    infoMod :: InfoMod a
    infoMod = fullDesc
     <> progDesc "Print a greeting for TARGET"
     <> header "hello - a test for optparse-applicative"

    parser :: Parser InputOptions
    parser = inputOptions <**> helper

    opts :: ParserInfo InputOptions
    opts = info parser infoMod

The key function here (info) is a function from optparse-applicative; it’s type is

info :: Parser a -> InfoMod a -> ParserInfo aSource

All this is doing is creating a ParserInfo object from Parser (in our case of type Parser InputOptions) and an InfoMod (which in this case we’re just defining inline). In our case the Parser is inputOptions and is defined above; we’ll talk about this in some more detail below.

Next we’re calling execParser (execParser :: ParserInfo a -> IO a) from optparse-applicative with our ParserInfo to tell it how to handle our input:

    getInput :: IO InputOptions
    getInput = execParser opts

Once we’ve got our input, we simply pass this onto our handler function and we’re good to go:

main = do
  parsedInput <- getInput
  handleInput parsedInput
  where 
    -- etc etc

How do applicatives come into this?

The optparse-applicative library — unsurprisingly —makes use of applicative-style quite widely; let’s use our instance of inputOptions as a specific example. For reference, the definition is here:

inputOptions :: Parser InputOptions
inputOptions = InputOptions
      <$> strOption
          ( long "helloTarget"
         <> metavar "TARGET"
         <> help "Target for the greeting" )
      <*> switch
          ( long "quiet"
         <> short 'q'
         <> help "Whether to be quiet" )
      <*> option auto
          ( long "enthusiasm"
         <> help "How enthusiastically to greet"
         <> showDefault
         <> value 1
         <> metavar "INT" )

If we remember from our last post, <$> is just an infix fmap operation; for the purposes of illustration we can start off by turning this:

InputOptions
      <$> strOption
          ( long "helloTarget"
         <> metavar "TARGET"
         <> help "Target for the greeting" )

into something thats maybe a bit more declarative like this:

partialInput :: Parser (Bool -> Int -> InputOptions)
partialInput = fmap InputOptions $ strOption $ long "helloTarget" <> metavar "TARGET" <> help "Target for the greeting" 

Here, we’ve pulled out the first part of our Parser, that so far just “wraps” a function with the remaining parameters for our InputOptions object. If we remember from our last post, the signature for <*> is

(<*>) :: Applicative f => f (a -> b) -> f a -> f b)

The important thing to remember here is that the function in our Parser, of type Bool -> Int -> InputOptions, can be thought of as having type a -> b -> c or a -> b where b is simply a function of type b -> c. This allows us to use <*> to compose in our Bool parameter using switch and our Int parameter using option auto respectively (NB: ). In these cases <*> will effectively “unwrap” our Bool, and Int parameters (remember switch returns a Parser Bool and option auto is in our case returning a Parser Int) for us to build up our InputOptions and leave us with a Parser InputOptions when we’re done. If we wanted to do this in the classic monadic style we’d need to do something like this:

withoutApp :: Parser InputOptions
withoutApp = do
  helloTarget <-  strOption $ long "helloTarget" <> metavar "TARGET" <> help "Target for the greeting"
  booleanParam <- switch
          ( long "quiet"
         <> short 'q'
         <> help "Whether to be quiet" )
  intParam <- option auto
          ( long "enthusiasm"
         <> help "How enthusiastically to greet"
         <> showDefault
         <> value 1
         <> metavar "INT" )
  SomeParserConstructorThatDoesntExist $ InputOptions helloTarget booleanParam intParam

This is not only much less readable but also there is no public constructor for Parser so wouldn’t work even if we wanted it to.

If you’re not used to reading applicative-style code, I think it can be a little confusing to work out how the composition fits together however once the penny drops it’s actually a very readable pattern. I have a suspicion this post will prove to be more of an exercise in rubber-ducking and the actual writing of it more useful than anything else, but hopefully the breakdown is a helpful illustration of how applicatives can be used in a more real-world scenario.

What the applicative? Optparse-applicative from the ground up (Part 1)

Functors and monads are both (relatively) well understood topics these days, even if they’re not always labeled as these directly. Functors are everywhere, and many of the basic structures that imperative programmers use on a day-to-day basis form mondad’s and can be composed accordingly. As such, once the penny drops on what a monad actually is, it’s trivial to understand their application and widespread importance. Additionally, many languages have support specifically for this style of programming, making the leap very natural. Applicative functors on the other hand, are not widely used by Johnny-Java-Developer and because of this there’s a temptation to view them as little less intuitive and it’s harder see their applications immediately. In reality applicatives are certainly no more complex than many functional concepts and can be used to facilitate some really nice patterns.

In this short series, I’m going to cover the basics of what applicatives are, and go over an example in a little more detail to show them in action (using the haskell’s optparse-applicative library).

What are applicative functors?

As with many functional concepts, applicative functors are fairly simple to describe; the definition for applicatives looks something like:

class (Functor f) => Applicative f where
    pure  :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

For reference the standard functor is below:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

-- note, we'll mainly be using the infix operator <$> in this article, described here:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) = fmap

The first change in the applicative functor is the pure function, we’re not going to go into this in too much detail here, for the time being just consider this as a “wrapper” function — think Just in Maybe.

The second function in applicative is more interesting: <*> (pronounced “app”). From the signature we can see that it’s a function, that takes a function (of type a->b) wrapped in a functor, and a functor of type a, then returns a functor of type b — the only real difference from the standard fmap being that the initial function we’re passing in is wrapped in a functor of the same kind. At this point, if you come from an imperative background it’s tempting of thinking of a use-case that looks something like this:

someFunc :: Num a => a -> a
someFunc = \x -> x + 1

someMaybe = Just 1

result = someFunc <$> someMaybe

-- result == Just 2

Which we could write using the applicative functions as:

someFunc :: Num a => a -> a
someFunc = \x -> x + 1

someMaybe = Just 1

result = Just someFunc <*> someMaybe

result == Just 2

Personally, I struggled to see the point of this at first. In many languages it’s simply unlikely that you’d often write something like this — if we’re concerned about someMaybe being Nothing, we can still just pass in someFunc to fmap and this is handled just fine.

In all likelihood, if we had more parameters in someFunc we’d probably compose this monadically:

someFunc::(Num a ) =>a -> a -> -> a
someFunc a b c = a + b + c

someResult::Maybe Int
someResult = do
  a <- Just 1
  b <- Just 2
  c <- Just 3
  Just $ someFunc a b c

Again this feels very familiar. The power of applicatives however really shines through though once you’re working in a language with first class support for partial application.

Let’s take a really simple data class

type Name = String
type Age = Int
type StreetName = String
type PostCode = String

data SimplePerson = SimplePerson {firstName:: Name} deriving (Show)

-- which we can create as 
sampleSimplePerson = SimplePerson "Harry"

Like all constructors SimplePerson is just a function, which in this case consumes a Name and returns a SimplePerson. If we have some function that generates a Maybe firstName for us, we can use our old friend fmap to generate a Maybe SimplePerson:

ghci> :t SimplePerson
SimplePerson :: String -> SimplePerson

ghci> fmap SimplePerson fetchName
Just (SimplePerson {firstName = "Harry"})

So far so good, but let’s take a type with a little more information, say:

data Person = Person
    { name :: Name
    , age :: Age
    , postCode :: PostCode
    , streetName :: StreetName
    }
    deriving (Show)
ghci> :t Person
Person :: Name -> Age -> PostCode -> StreetName -> Person

we can see that fmap isn’t going to cut it — it doesn’t take enough parameters. To allow us to use the same fmap style we used above for SimplePerson we’d really need something like:

magicMap:: (Name -> Age -> PostCode -> StreetName -> Person) -> Maybe Name -> Maybe Age -> Maybe PostCode -> Maybe StreetName -> Maybe Person

For obvious reasons this isn’t going to work for us on a practical, what we really need is a generalised way of applying this kind of mapping — enter <*>. The critical thing to note is that working with a language like Haskell, we get partial application out the box, so if we don’t apply all the parameters required to a function that’s ok, we’ll just get back another function that consumes the rest. That means we can write this:

ghci> :t Person “Harry”
Person :: Age -> PostCode -> StreetName -> Person

Or like this:

ghci> :t fmap Person $ Just "Harry"
fmap Person $ Just "Harry"
  :: Maybe (Age -> PostCode -> StreetName -> Person)

Now we’re getting somewhere: what we’ve ended up with is a function wrapped in some context (a Maybe) that takes some parameters and returns our completed Person. In turn, this function could be called with any number of the remaining parameters, and we’ll slowly work towards the end Person. If we stub out some extra functions to generate our required parameters wrapped in the same context we could use these to build our example outputs:

fetchName :: Maybe Name
fetchName = Just "Harry"

fetchAge :: Maybe Age
fetchAge = Just 21

fetchPostCode :: Maybe PostCode
fetchPostCode = Just "SUR1"

fetchStreetName :: Maybe StreetName
fetchStreetName = Just "Privet Lane"

Obviously we could put these together monadically:

personUsingDoNotation :: Maybe Person
personUsingDoNotation = do
    name <- fetchName
    age <- fetchAge
    postCode <- fetchPostCode
    streetName <- fetchStreetName
    Just $ Person name age postCode streetName

-- or using bind

personWithoutUsingDoNotation::Maybe Person
personWithoutUsingDoNotation = fetchName >>= (\name -> fetchAge >>= (\age -> fetchPostCode >>= (\postCode -> fetchStreetName >>= (\streetName -> Just $ Person name age postCode streetName))))

Using do-notation feels a tad overkill here and using bind directly is borderline unreadable, even in this simple case. Using applicative style instead however, allows us to put these together by stringing <$> and <*> as follows:

personUsingApp :: Maybe Person
personUsingApp = Person <$> fetchName <*> fetchAge <*> fetchPostCode <*> fetchStreetName

This is much nicer, hooray! Again, if we only pass in some of the parameters, we just get back a function that we can <*> away to as we go

ghci> :t Person <$> fetchName <*> fetchAge <*> fetchPostCode -- ie were missing `<*> fetchStreetName`
Person <$> fetchName <*> fetchAge <*> fetchPostCode
  :: Maybe (StreetName -> Person)

In the next section of this we’ll be taking a look at Haskells optparse-applicative library, which makes extensive use of applicative style to build command line tools.