Fantas, Eel, and Specification 10: Alt, Plus, and Alternative

We’re in double digits! Isn’t this exciting? It also means that, by my estimations, we’re well over half way! Before we get too excited by Profunctor and Comonad, though, might I tempt you with an… Alternative?

Today, we’re going to bundle together three very well-related entries in the spec, starting with Alt. This little typeclass has one function:

alt :: Alt f => f a ~> f a -> f a

As we know, with great algebraic structure, come great laws:

// Associativity
a.alt(b).alt(c) === a.alt(b.alt(c))

// Distributivity
a.alt(b).map(f) === a.map(f).alt(b.map(f))

The associativity law is exactly what we saw in the Semigroup post with concat! As we said back then, think of it as, “Keeping left-to-right order the same, you can combine the elements however you like”.

Distributivity gives us another clue that we might be looking at something a bit Semigroup-flavoured. We can map first over the elements and then alt or alt first and then map over the result, and we’ll end up at the same value. Either way, some kind of “combination” definitely seems to be going on here.

So, what is it? Well… it’s like a semigroup for functors. It’s a way of combining values of a functor type without the requirement that the inner type be a Semigroup. Why, you ask? Well, we get a little hint from the name.

Alt allows us to provide some alternative value as a “fallback” when the first “fails”. Of course, this is particularly relevant to types with some notion of failure:

Maybe.prototype.alt = function (that) {
  this.cata({
    Just: _ => this,
    Nothing: _ => that
  })
}

If we have a Just on the left, we return it. Otherwise, we fall back to the second value! Naturally, you can chain as many of these as you like:

// Just(3) - note the "Nothing"s are
// usually the result of some functions.
Nothing.alt(Nothing).alt(Just(3))

It turns out there are loads of use cases for alt, which isn’t too surprising if you look at it as a functor-level if/else. You can do database connection failover, API/resource routing, and, most magically of all, text parsing. Those last two were in PureScript and Haskell respectively, but don’t worry: in these languages, alt has an operator, written as <|>.

The key thing all these cases have in common is that you want to try something with a contingency plan for failure. That’s all there is to Alt!


If Alt will be our functor-level Semigroup, what’s our functor-level Monoid? In comes Plus, which extends Alt with one more function called zero:

zero :: Plus f => () -> f a

Looks a bit like Monoid’s empty, right? Note that there’s no restriction on the a, so this zero value must work for any type. This one has three laws, but the first two will look really familiar to readers of the Monoid post:

// Right identity - zero on the right
x.alt(A.zero()) === x

// Guess what this one's called?
A.zero().alt(x) === x

// The new one: annihilation
A.zero().map(f) === A.zero()

The left and right identity laws just say, “zero makes no difference to the other value, regardless of which side of alt you put it”. Annihilation gives us a stronger idea of what zero does: nothing! Plus types must be functors; for a map call to do nothing in all cases, the type must have the ability to be empty, whatever that means.

Think of our Maybe type: what can we map over with any function and not change the value? Nothing! In fact, () => Nothing is the only valid implementation of zero for Maybe.

What about Array? Well, map transforms every value in the array, so the only array that wouldn’t be changed is the empty one. () => [] is the only valid implementation of zero for Array.

We didn’t cover Array as an Alt because it’s a bit of a funny one. Back when we discussed functors, we saw that Array extends our language to allow us to represent several values at once. This can be thought of as non-determinism if we see an Array as the set of possible values. Thus, the alt implementation for Array is the same as concat - all we’re doing is combining the two sets of possibilities!

So, Plus adds to Alt what Monoid adds to Semigroup, and, in fact, what Applicative adds to Apply: an identity. Are we bored of this pattern yet? I hope not, because we’re still not done with it! Incidentally, we can write custom Semigroup and Monoid types to encapsulate this behaviour so we can reuse the functions we talked about in their posts:

// The value MUST be an Alt-implementer.
const Alt = daggy.tagged('Alt', ['value'])

// Alt is a valid semigroup!
Alt.prototype.concat = function (that) {
  return Alt(this.value.alt(that.value))
}

// The value MUST be a Plus-implementer.
// And, as usual, we need a TypeRep...
const Plus = T => {
  const Plus_ = daggy.tagged('Plus', ['value'])

  // Plus is a valid semigroup...
  Plus_.prototype.concat =
    function (that) {
      return Plus(
        this.value.alt(
          that.value))
    }

  // ... and a valid monoid!
  Plus_.empty = () => Plus_(T.zero())
}

Monoids are everywhere, I tell you. Stare at something long enough and it’ll start to look like a monoid.


The final boss level on this Alt quest is Alternative. There are no special functions for this one, as it is simply the name for a structure that implements both Plus and Applicative. Still, I know how much you love laws:

// Distributivity
x.ap(f.alt(g)) === x.ap(f).alt(x.ap(g))

// Annihilation
x.ap(A.zero()) === A.zero()

Distributivity is exactly as the same law that we saw with Alt and map at the beginning of all this, but now for ap. We can either alt first and then ap the result to x, or we can ap first to both separately, and then alt. Either way, we end up in the same place.

Annihilation is a really scary word for a not-so-scary idea, if you think back to the zero values we discussed earlier. You couldn’t apply a value to Nothing, right? Or an empty list of functions? The annihilation law defines this behaviour: if you try to do something with nothing, you get nothing. Whatever you were doing is considered a failure, and zero is returned.

You’ll often hear Alternative types described as monoid-shaped applicatives, and this is a good intuition. We talked about of as being the identity of Applicative, but this is only at context-level. For an Alternative type, zero is the identity value at context- and value-level.


Maybe, Array, Task, Either: we’ve seen a lot of types that can very naturally implement Alternative. You could even make Function an Alternative if you knew the output would be of a Plus-implementing type. With that, you could then write a function whose body can do extra computation depending on the result; who needs if/else?

That’s about all there is to it! Alt, Plus, and Alternative are under-appreciated typeclasses, particularly within functional JavaScript. Take some time to look through your code, glare at the if/else, try/catch, and switch blocks, and see whether they’re really just alts in disguise!

Next time, we’ll be looking into your new favourite typeclasses: Foldable and Traversable. Try to contain your excitement until then!

Take care ♥