Fantas, Eel, and Specification 9: Applicative

I asked my German friend whether any of this series’ posts particularly stood out. They said 9, so I’d better make this a good one! I told you we were doing jokes now, right? Moving on… Today, we’re going to finish up a topic we started last week and move from our Apply types to Applicative. If you understood the Apply post, this one is hopefully going to be pretty intuitive. Hooray!

Applicative types are Apply types with one extra function, which we define in Fantasy Land as of:

of :: Applicative f => a -> f a

With of, we can take a value, and lift it into the given Applicative. That’s it! In the wild, most Apply types you practically use will also be Applicative, but we’ll go through a counter-example later on! Anyway, wouldn’t you know, there are a few laws to go with it, tying ap and of together:

// For some applicative A...

// Identity.
v.ap(A.of(x => x)) === v

// Homomorphism
A.of(x).ap(A.of(f)) === A.of(f(x))

// Interchange
A.of(y).ap(u) === u.ap(A.of(f => f(y)))

With identity, we’ve lifted the identity function (x => x) into our context, applied it to the inner value, and, surprise, nothing happened.

The homomorphism law says that we can lift a function and its argument separately and then combine them, or combine them and then lift the result. Either way, we’ll end up with the same answer! The of function can do nothing but put the value into the Applicative. No tricks. No side effects.

Interchange is a little more complex. We can lift y and apply it to the function in u, or we can lift f => f(y), and apply u to that. I think of this one as, “Nothing special happens to one particular side of ap - it just applies the value in the right side to the value in the left”.


So, why is this of thing useful? Well, the most exciting reason is that we can write functions generic to any applicative. Let’s write a function that takes a list of applicative-wrapped values, and returns an applicative-wrapped list of values. Note that T is a TypeRep variable - we’ve seen these before in the monoid post. We need it in case the list is empty:

// append :: a -> [a] -> [a]
const append = y => xs => xs.concat([y])

// There's that sneaky lift2 again!
// lift2 :: Applicative f
//       => (a -> b -> c, f a, f b)
//       -> f c
const lift2 = (f, a, b) => b.ap(a.map(f))

// insideOut :: Applicative f
//           => [f a] -> f [a]
const insideOut = (T, xs) => xs.reduce(
  (acc, x) => lift2(append, x, acc),
  T.of([])) // To start us off!

// For example...

// Just [2, 10, 3]
insideOut(Maybe, [ Just(2)
                 , Just(10)
                 , Just(3) ])

// Nothing
insideOut(Maybe, [ Just(2)
                 , Nothing
                 , Just(3) ])

First of all, we lift an empty list into the applicative context. Then, value by value, we combine the contexts with a function to append the value to that inner list. Neat, right? We’ll see in a few weeks that this insideOut can be generalised further to become a super-helpful function called sequenceA that works on a lot more than just lists!

This is pretty useful for AJAX, too. We can use our data.task applicative to create a list of requests - a [Task e a] - and then combine them into a single Task containing the eventual results - a Task e [a] - using insideOut!

Notice that we only need an Applicative because the list could be empty. Otherwise, we could just map over the first with x => [x] and use that one as the accumulator - we’d only need Apply! Anyone else having flashbacks to monoids? They’re all very strongly-connected:

  • concat can combine any non-zero number of values (of the same type) into one.

  • ap can combine any non-zero number of contexts (of the same type) into one.

  • If we might need to handle zero values, we will need to use empty.

  • If we might need to handle zero contexts, we will need to use of.

Wizardry. For our next trick, let’s notice that you can turn any Applicative into a valid Monoid if the inner type is a Monoid:

// Note: we need a TypeRep to get empty!
const MyApplicative = T => {
  // Whatever your instance is...
  // const MyApp = daggy.tagged('MyApp', ['x'])
  // Put your map/ap/of here...

  // concat :: Semigroup a => MyApp a
  //                       ~> MyApp a
  //                       -> MyApp a
  MyApp.prototype.concat =
    function (that) {
      return lift2((x, y) => x.concat(y),
                   this, that)
    }

  // empty :: Monoid a => () -> MyApp a
  MyApp.prototype.empty =
    () => MyApp.of(T.empty())

  return MyApp
}

The above will always be valid. Notice that it doesn’t care about the shape of our Applicative at all - we can write these implementations using just the interface (map, ap, and of) for any applicative. Of course, Applicative types can use different implementations for Monoid. For example, look at Maybe:

// Usual implementation:
Just([2]).concat(Just([3])) // Just([2, 3])
Just([2]).concat(Nothing)   // Just([2])
Nothing.concat(Just([3]))   // Just([3])
Nothing.concat(Nothing)     // Nothing

Maybe.empty = () => Nothing

// With the above implementation:
Just([2]).concat(Just([3])) // Just([2, 3])
Just([2]).concat(Nothing)   // Nothing
Nothing.concat(Just([3]))   // Nothing
Nothing.concat(Nothing)     // Nothing

Maybe.empty = () => Just(
  MyInnerType.empty())

A type might have more than one implementation for any given typeclass (such as Semigroup); the choice is up to the implementer and the users! As we can see, Maybe’s usual implementation probably works better. Particularly the empty bit.

Still, if we have an Applicative type, we know for sure that have at least one valid Monoid definition! Magical, right?


All the Apply types we’ve seen so far have, coincidentally, been Applicative. We can see it’s pretty easy in many cases to make an of:

Array.of = x => [x]
Either.of = x => Right(x)
Function.of = x => _ => x
Maybe.of = x => Just(x)
Task.of = x => new Task((_, res) => res(x))

We’re really just constructing the simplest possible value within a type that can hold a value for us. No tricks, nothing fancy. Even Task, if you remember our Promise analogy, is about as routine as we could make it. However, let’s talk about pairs:

// Pair :: (l, r) -> Pair l r
const Pair = daggy.tagged('Pair', ['x', 'y'])

// Map over the RIGHT side. The functor
// is `Pair l` and `r` is the inner type.
// map :: Pair l r ~> (r -> s)
//                 -> Pair l s
Pair.prototype.map = function (f) {
  return Pair(this.x, f(this.y))
}

// Apply this to that, retain this' left.
// ap :: Pair l r ~> Pair l (r -> s)
//                -> Pair l s
Pair.prototype.ap = function (that) {
  return Pair(this.x, that.y(this.y))
}

// But wait...
// of :: r -> Pair l r
Pair.of = function (x) {
  return Pair({WHAT GOES HERE}, x)
}

Ah. Look at the signature for of - there’s a magical l value that just appears! We don’t know what type it is, so we don’t know what it can do, which means we can’t find a value to fill this gap. Sure, we can ap - because we have two l values to choose from! - but we can’t of.

How do we solve this? With the same magical pattern that has been going on throughout this post: we require l to be a Monoid. If l is a Monoid, we know we can call l.empty() to get a value for that gap!

// TypeRep!
const Pair = T => {
  Pair_ = daggy.tagged('Pair', ['x', 'y'])

  // And now we're fine! Hooray!
  Pair_.of = x => Pair_(T.empty(), x)

  return Pair_
}

Array.empty = () => []

// SUCCESS!
const MyPair = Pair(Array)
MyPair.of(2) // Pair([], 2)

So, an Apply is an Applicative without of. An Applicative without ap also has a name: Pointed. However, there are no laws attached to it independently, so it’s not particularly useful on its own - just a bit of trivia!


That, good people of The Internet, is all there is to Applicative. Most of this was covered in the last post, so there are hopefully no great surprises. The important take away is that the relationship between Semigroup and Monoid is very similar to that of Apply and Applicative. This isn’t the last time we’ll see such a relationship, either! Isn’t it weird how everything’s connected? Baffles me, at least.

Anyway, I hope this has been useful, and, as always, I’m available on twitter to answer any questions you might have. Next time, we’ll be tackling Alt. Don’t worry: it’s going to be pretty familiar-looking if you’ve been through all the posts in this series. Until then, though, go forth and Apply!

Thank you so much for reading, and take care ♥