September 11, 2026

Haskell for Hobos -- Applicatives

Applicative is short for "applicative functor". You would be well served to have digested the discussion of functors before diving in here.

In review, functors are really a generalization of "map". Functors are also a typeclass, so many things can be functors. Map is really a specialized functor restricted to operating on lists.

The usual functor operator is "fmap", but there is also an infix operator (a synonym for fmap) that is written <$>. I mention this right up front, because applicatives have an infix operator <*> and I am always getting these confused.

<$> - plain old functor
<*> - applicative functor
You may be interested to know that applicatives were a late addition to Haskell and they came along significantly later than Monads. You can view them as beefed up functors or watered down monads.

Just so you know, you often will have a choice of whether to deal with types as applicatives or as monads. It is up to you in many cases. Monads however allow you to use "do notation".

The Applicative typeclass

If you understand the Haskell type system, here is all you need to know (just kidding).
class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
We see the <*> applicative operator here. We also see "pure" which is a constructor we can use to "wrap" some other type (a) into an applicative (f). Usually whatever type is acting as an applicative already has a type constructor all its own to do this, so I'm not yet clear when "pure" would actually get used.

The cool <*> operator (which you could pronounce "apply") has an interesting type. It will take 2 arguments. The first is f (a -> b) which is a function wrapped as an applicative. The second object is the type "a" wrapped as an applicative. It yields "b" wrapped as an applicative.

This is exactly the same as map or fmap, except that for those, the function was "out in the open", while here the function is wrapped as an applicative. You are ahead of the game here if you can see the analogy and/or generalization of map/fmap here.

All this talk is pretty abstract. So lets use the Maybe type as a concrete example.

value = Just (*10) <*> Just 13
This yields "Just 130" as a result.

Here the first Maybe holds the function (*10) which is looking to multiply something by 10. The second Maybe holds the value 13. The applicative extracts the function, and also extracts the value 13, then applies the function to 13 then wraps it back up as a Maybe.

Don't ask me when or where this would be useful, but you have now seen an applicative in action.
We could write the above example like this and get the same result:

value = pure (*10) <*> Just 13
This is what we might do if we had a value in a Maybe and we knew we wanted to multiply it by 10.
Of course we could just use fmap (i.e. <$>) in that situation:
value = (*10) <$> Just 13
I remember my early days of Haskell when I called some function (probably "find") that returned a Maybe value. I was at my wits end wanting to work with it, and I suspected that Haskell had machinery for doing it. Now I know.

A second example is perhaps more interesting. We already know that we can use map on lists. And we know that lists are functors, so we can use fmap (or <$>) also, which will act the same as map and maybe confuse people. Lists are applicatives, and they way <*> is implemented for lists is interesting. Consider this:

newlist = [ (*10), (*2) ] <*> [7, 9]
This gives us: [70,90,14,18]

Notice that the way the apply operator is defined for lists, we get a cartesian product, all the possible combinations of functions and values. It didn't have to be defined this way, but it is.

You don't get to do this:

value = Just (*10) <*> [ 1, 2 ]
While both Maybe and [] are both Applicatives (the same typeclass), the types are different and this is a no-go.

Applying functions with two (or more) arguments

Notice that we are now talking about "applying" rather than "mapping".
Look at this:
pure (+) <*> Just 3 <*> Just 9
Here we could write "Just (+)" rather than using "pure". Take this from left to right (the above is left associative), we could write:
(pure (+) <*> Just 3) <*> Just 9
The first apply yields "Just (+3)", then we have this to evaluate:
Just +3 <*> Just 9
And we end up with Just 12.

The key idea is to apply the binary (+) operator to two wrapped values. To be able to use it, we have to "lift" it into the Maybe realm.
We can also do it with plain old fmap (<$>) like this:

(+) <$> Just 3 <*> Just 9
There are also explicit functions to do lifting, so we could write:
liftA2 (+) (Just 3) (Just 2)

What good is all of this?

More or less this boils down to a generalization of map. Functors and "fmap" generalize map to things other than lists. However fmap (and map) can only utilize a function that has one thing in and one thing out. Applicative functors eliminate that restriction because they can hold functions themselves inside the applicative. This in turn allows them to work with functions that take more than one argument, as we just demonstrated above.

Here is another example

val1 = Just "dog"
val2 = Just "fish"
joinem a b = a ++ " ... " ++ b
value = joinem <$> val1 <*> val2

main = putStrLn $ show value
This yields Just "dog ... fish"
Notice that the first operator must be <$> not <*>. Why? Because <*> expects a "wrapped" item on both sides and "joinem" is a plain old unwrapped function. But good old fmap (aka <$>) expects a plain function that it maps over a wrapped thing, thus "lifting" it.

Here is a 20 page paper on this whole business if you want to see what the academic types say about all this:

Sequencing

Along with <*> there are two other operators worth mentioning.
*> - sequence two actions, keep the second result, discard the first
<* - sequence two actions, keep the first result, discard the second
Here is an example of the first of these. Suppose we are doing IO and for some reason don't want to set up "do notation". We could do this.
main = putStrLn "Hello" *> putStrLn "World"
We would get each on a separate line.
Hello
World
An example of the second is this:
parseStatement = parseInteger <* parseChar ';'
This is from a hypothetical parser. We want the value from the integer, but we just want to discard the semicolon that follows.


Have any comments? Questions? Drop me a line!

Tom's software pages / tom@mmto.org