J.S. Cruz

Abstraction is Restriction is Composability

Abstraction and generalization go hand-in-hand. The more abstract something is, the more general it is, i.e., it is applicable in more situations.

I dislike the word “general” here because its antonym is “specific” (i.e., restricted), which I think is a much better description for abstract functions. The more abstract a function is, the more restricted it is, since it must do less.

The canonical example is the identity function. In Haskell:

id :: a -> a

This function is extremely abstract: it receives any value of any type and returns a value of that same type. Since the types are so abstract, we cannot make any promises about the returned value. We cannot say that it’s “one more than the argument” (because that would imply a wouldn’t be any type, but a number); we cannot say it’s a “filtered list” (because that would imply a is a list of values of comparable type). Really just about the only implementation possible is to return the argument, untouched, because we have no idea what we would even do to it.

id x = x

Another example is std::optional, in C++. If instead of an std::optional<T> that can wrap any type T, we had an optional_int that just wrapped integers, we could define some of its functions like so:

int optional_int::value_or(int x) { return this->value() - x; }

This function is obviously very wrong, but the point is that there are as many possible implementations of optional_int::value_or as we can imagine ways of combining integers.

If, instead, the optional takes an abstract type T: template <typename T> T optional::value_or(T x);, we only have access to two values of type T to play with: this->value() or x. Of course, this doesn’t prevent us from having a wrong implementation still:

template <typename T> T optional::value_or(T x) { return x; }

but it reduces the quantity of implementations we have available (e.g., we couldn’t return x + 1 because that assumes that T is addable), so it makes it easier to find the correct one.

The more abstract our function types are, the fewer their possible implementations and the narrower their responsibility.

That’s why it’s the functional languages — where we see this habit of abstracting everything to its core, sometimes to the point of some silliness — which lend themselves the best to composability.

Or, if you prefer another metaphor, the smaller our Lego blocks, the less of the overall structure each block fills, but the easier it is to stack them on top of each other, and the more control we have over the final structure.

Tags: #programming