Abstraction

💡
My thinking around this topic is influenced by Eric Normand's Lispcast episode on abstraction. Check it out, and his podcast.

Abstraction is a common topic in programming discussions. You hear a lot about the right abstraction, the wrong abstraction; this level of abstraction or that level; leaky ones, bad ones, too many of them, not enough of them. For every abstraction there are many opinions about whether its good or bad - or perhaps even whether its too anstract or not enough. I believe that we tend to conflate the term to mean other things, and in doing so we lose precision and risk sounding overly complicated.

Abstraction is so common, so fundamental a concept that it is used everywhere. It's like ink in a pen or letters in words - but to be a bystander in a discussion about abstraction, one would be tempted to think it's something very specific and cerebral.

One man's abstraction is another man's bread and butter.
- Charles C. Pinter

Abstraction is simply dealing with something that hides the details of how it works. Consider abstraction in Lambda Calculus by looking at the identity function. This function takes an argument and returns it.

λx.x

In Lambda calculus, the left side of the . is called the abstraction, and the right side is the application. The abstraction is a name given to the parameter, also called a bound variable. The right side is the returned value. So here in the identity function, the abstraction is named x and the value given to that is returned from the function - no matter what that value is. It could be a number, a function, a character, anything. So from Lambda Calculus, we learn that abstraction is as simple as representing a value with a name.

More broadly, this applies to any name which represents something else. A few examples of abstractions:

var name = 'Ben';
let age = 33
class Person {}
interface Person {}

In dealing with each line above - the variable name, an object that is a type of Person, the interface for Person, etc. - the details are removed and hidden within each of those things. This is abstraction also.

Not only names, but symbols that we use constantly are abstractions. Arithamitic and equality operators like + and =, as well as keywords such as async, match, and function - there are endless details hidden under even single characters. The more syntactic sugar a language has, the higher the quantity of abstractions in nearly every keystroke.

Too Abstract? Bad Abstraction?

Is a variable more abstract than an interface? Is an interface more abstract than a class? Is + more abstract than a function named add()? Can one abstraction be better or worse than another?

When we talk about interfaces and classes, we talk about abstraction and concretion. But the concretion still has members whose details are hidden and vary from one instance to the next. The concretion may have members whose details may change because of dependencies. In these cases a concretion is no more or less abstract than the abstraction. If we ask "are the details hidden in one but not the other" then answer is "no" - both have details that are hidden.

If we stop to think about abstraction in its simplicity, it starts to feel odd to describe an abstraction as good or bad. If you take the details from one spot, and conceal them within something else that allows you to not have to think about those details, the act of abstracting is neutral. Say you have a string and it's value does not matter to you in the current context: you can conceal it under a variable, a function that returns a string, a class that has a method that returns a string (which in turn has an interface), you can even hit an API and have the string returned. Each of these are equally abstract and one is not better or worse than the other. In each the details are equally concealed to the current context.

When we speak of abstraction in this way, I think we tend to mean more specific ideas than simply the fundamental idea abstraction. In a lot of instances, I think we mean patterns. One pattern can be preferrable to another in certain situations. The pattern is no more or less abstract than another pattern, but perhaps it is more practical or maintainable. In this case I think it is more helpful to say that "factory pattern is the right pattern," or "reader monad" is the right pattern than "factory pattern is the right abstraction".

Other times we may refer to abstraction being good or bad in the sense that it's too specific to a few cases and should be more reusable. But an abstraction being specific to a few use cases does not change the fact that the consumer of that abstraction does not know the details of what's happening on the other side. If a date formatting function does or does not allow the user to specify a locality, it doesn't change the level of abstraction, it changes the generality of the function. In this case, one implementation of the function would be more geared to general consumers, while the other would be geared for specific consumers - in both the consumer is equally ignorant of the details within the abstraction.

Conclusion

We refer to abstraction a lot in programming - and that's fine. It's a fundamental concept and is the core of everything we do. But often when we talk about abstraction we use the term as if it means something much more specific than what it is. Every line of code we write is saturated in abstraction, whether intentional or unintentional.

Usually when we use this term, we mean something more specific. We mean "pattern", or "generality", or "reusability", or "modularity." In most of these instances, using the more precise term would be beneficial in our communication as developers. First of all because we can get to the heart of the discussion faster, but also because  "abstraction" is a general enough term to where participants in the conversation may be offput because the subject matter is too...well, abstract.