An Example of Functors and Applicatives in Haskell


For the past few months, I have been reading through Haskell Programming from First Principles. It is a wonderful book, worth reading carefully with many opportunities to develop a well-grounded understanding of Haskell, a challenging language with a wealth of powerful ideas.

This post will not cover any basics of installing Haskell and so on. Instead, look here for more on getting the language running on your machine. Also, if you're entirely new to Haskell and prefer working with free materials, see here for a great way to get started learning the language.

Recently, while reading the chapter on applicatives I encountered a practical example of how one might use applicatives with functors. While it might sound too abstract, the example below is actually readily understandable, especially if we proceed by thinking first and foremost about types and type signatures.

As a disclaimer, the example we will discuss comes straight out of Haskell Programming. This post is an attempt at reinforcing my own understanding of the example. Needless to say, an interested reader will read the chapters on functors and applicatives for the full context. In addition, there is also this incredible post on functors and applicatives which includes plenty of helpful visual guides as well as a nice introduction to the concepts.

Now for the example. Let's assume we have a type which represents a Person. The Person type includes a Name and an Address:

data Person =
    Person Name Address
    deriving (Eq, Show)

The Name and Address types are both wrappers around the String type:

newtype Name =
    Name String
    deriving (Eq, Show)

newtype Address =
    Address String
    deriving (Eq, Show)

Before creating a Name or Address type, we will ensure the provided String type satisfies a validation check: for names we will require no more than twenty-five characters. For addresses, we will require no more than 100 characters.

To handle validation, we will use a function which accepts a character limit (the Int type) and a string (the String type) which represents the name or the address.

validateLength :: Int -> String -> Maybe String
validateLength maxLen s =
  if (length s) > maxLen
  then Nothing
  else Just s

What is interesting about this validation function is that it returns a Maybe type. In other words, the validation can fail and when it does, its return value will be a Nothing. Successful validation will result in the value wrapped in a Just type. Both Nothing and Just are the two data constructors of the Maybe data type.

Because validateLength alternatively returns a Just String or a Nothing, we must handle the cases of both valid and invalid lengths when making a Name or Address. To do that, we will use fmap, a function of the Functor type class.

First, let's start with the two make functions:

mkName :: String -> Maybe Name
mkName s = fmap Name $ validateLength 25 s

mkAddress :: String -> Maybe Address
mkAddress a = fmap Address $ validateLength 100 a

Both the data contructors Name and Address take a String and return a Name or Address data type. What then is this fmap and how does it work with the Maybe type?

Let's look at the fmap function on its own for a moment starting with its type signature.

fmap :: Functor f => (a -> b) -> f a -> f b

The fmap function applies a function to a value within a Functor and returns a new Functor with the transformed value.

Now, if we consider the types involved in mkName for example, we'll see that fmap does exactly what we need:

-- assuming some input
fmap Name               (validateLength 25 "some-input")

-- we have these types
fmap (String -> Name)   (Maybe String)

-- which match the signature of fmap
fmap (a -> b)        -> f a

From the types above, we can see that fmap will apply a function to the value within a Functor (Maybe in the case here) and then return a new Functor (again a Maybe type) with the transformed value within it (a Maybe Name type). In Haskell parlance, we are lifting a function over structure.

So now that we have two mk functions and understand their workings, we have a problem: how will we use mkName and mkAddress with the Person data constructor, since it expects a Name and an Address object?

If we write a mk function for our Person type, it will need two string arguments:

mkPerson :: String -> String -> Maybe Person
mkPerson = undefined -- <- what goes here?

Both mkName and mkAddress return a Maybe Name or a Maybe Address. How can we use all three mk functions together?

The answer is to use an applicative. To understand why, let's look at the type signature for the applicative operator <*>:

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

The type signature for <*> looks similar to fmap above with the exception that the first argument -- a function -- is itself contained within an applicative structure. That wrapped function is then applied to the second argument -- an applicative wrapping a type which matches the function's input type -- and finally the output is an applicative wrapping a type of the function's return value.

Let's construct an example matching the types to help make this clear. What if we could use the <*> operator with the following types? Would the types satisfy the type signature of <*>?

Maybe (Address -> Person) <*> Maybe Address

We start with a Maybe type wrapping a function which takes an Address and returns a Person. The right side of the <*> operator is a Maybe Address. Putting this next to the type signature of <*>, we see the types line up perfectly:

f     (a       -> b     ) <*> f     a       -- returns f     b
Maybe (Address -> Person) <*> Maybe Address -- returns Maybe Person

Needless to say, Maybe is a member of the Applicative type class.

We know that mkAddress takes a String and returns a Maybe Address, which means we can use it as part of our mkPerson function. How then do we get a Maybe (Address -> Person)?

We can use fmap to produce exactly this function:

fmap Person (mkName String)

If we expand the types of the function above, we have:

fmap (Name -> Address -> Person) (Maybe Name)

-- which becomes
Maybe (Address -> Person)

We have used fmap to apply the result of mkName to our Person data constructor, leaving a partially applied function which now takes an Address and returns a Person, all inside a Maybe structure.

Putting all the pieces together, we have

mkPerson :: String -> String -> Maybe Person
mkPerson n a = (fmap Person (mkName n)) <*> mkAddress a

Or, swapping fmap with its infix operator <$>:

mkPerson :: String -> String -> Maybe Person
mkPerson n a = Person <$> mkName n <*> mkAddress a

And now we have a practical example of using applicatives with functors.