Applicative functors by Mark Seemann
An applicative functor is a useful abstraction. While typically associated with functional programming, applicative functors can be conjured into existence in C# as well.
This article series is part of a larger series of articles about functors, applicatives, and other mappable containers.
In a former article series, you learned about functors, and how they also exist in object-oriented design. Perhaps the utility of this still eludes you, although, if you've ever had experience with LINQ in C#, you should realise that the abstraction is invaluable. Functors are abundant; applicative functors not quite so much. On the other hand, applicative functors enable you to do more.
I find it helpful to think of applicative functors as an abstraction that enable you to express combinations of things.
In the functor article series, I mostly focused on the mechanics of implementation. In this article series, I think that you'll find it helpful to slightly change the perspective. In these articles, I'll show you various motivating examples of how applicative functors are useful.
- Full deck
- An applicative password list
- Applicative combinations of functions
- The Maybe applicative functor
- Applicative validation
- The Test Data Generator applicative functor
- Danish CPR numbers in F#
- The Lazy applicative functor
- Applicative monoids
A Haskell perspective #
A normal functor maps objects in an 'elevated world' (like C#'s IEnumerable<T>
or IObservable<T>
) using a function in the 'normal world'. As a variation, an applicative functor maps objects in an 'elevated world' using functions from the same 'elevated world'.
In Haskell, an applicative functor is defined like this:
class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
This is a simplification; there's more to the Applicative
typeclass than this, but this should highlight the essence. What it says is that an applicative functor must already be a Functor
. It could be any sort of Functor
, like []
(linked list), Maybe
, Either
, and so on. Since Functor
is an abstraction, it's called f
.
The definition furthermore says that in order for a functor to be applicative, two functions must exist: pure
and <*>
('apply').
pure
is easy to understand. It simply 'elevates' a 'normal' value to a functor value. For example, you can elevate the number 42
to a list value by putting it in a list with a single element: [42]
. Or you can elevate "foo"
to Maybe
by containing it in the Just
case: Just "foo"
. That is, literally, what pure
does for []
(list) and Maybe
.
The <*>
operator applies an 'elevated' function to an 'elevated' value. When f
is []
, this literally means that you have a list of functions that you have to apply to a list of values. Perhaps you can already see what I meant by combinations of things.
This sounds abstract, but follow the above list of links in order to see several examples.
An F# perspective #
Applicative functors aren't explicitly modelled in F#, but they're easy enough to add if you need them. F# doesn't have typeclasses, so implementing applicative functors tend to be more on a case-by-case basis.
If you need list
to be applicative, pure
should have the type 'a -> 'a list
, and <*>
should have the type ('a -> 'b) list -> 'a list -> 'b list
. At this point, you already run into the problem that pure
is a reserved keyword in F#, so you'll have to find another name, or simply ignore that function.
If you need option
to be applicative, <*>
should have the type ('a -> 'b) option -> 'a option -> 'b option
. Now you run into your second problem, because which function is <*>
? The one for list
, or the one for option
? It can't be both, so you'll have to resort to all sorts of hygiene to prevent these two versions of the same operator from clashing. This somewhat limits its usefulness.
Again, refer to the above list of linked articles for concrete examples.
A C# perspective #
Applicative functors push the limits of what you can express in C#, but the equivalent to <*>
would be a method with this signature:
public static Functor<TResult> Apply<T, TResult>( this Functor<Func<T, TResult>> selector, Functor<T> source)
Here, the class Functor<T>
is a place-holder for a proper functor class. A concrete example could be for IEnumerable<T>
:
public static IEnumerable<TResult> Apply<T, TResult>( this IEnumerable<Func<T, TResult>> selectors, IEnumerable<T> source)
As you can see, here you somehow have to figure out how to combine a sequence of functions with a sequence of values.
In some of the examples in the above list of linked articles, you'll see how this will stretch the capability of C#.
Summary #
This article only attempts to provide an overview of applicative functors, while examples are given in linked articles. I find it helpful to think of applicative functors as an abstraction that enables you to model arbitrary combinations of objects. This is a core feature in Haskell, occasionally useful in F#, and somewhat alien to C#.
Next: Full deck.