What's the relationship between abstraction and generality?
This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.
Do abstract and general mean the same thing? I don't think so. I've actually stopped using the term 'abstraction' because it's so laden with semantic baggage. We explore what they do mean in different contexts, and why abstract is not a relative term.
Eric Normand: What is the relationship between abstraction and generality?
Hello. My name is Eric Normand. This is my podcast. Welcome.
We use this term a lot in programming circles, software engineering, we say abstraction. I created an abstraction. Oh, this is a nice abstraction. We use this term a lot. I have started myself to try to shy away from it because it is too fraught with multiple interpretations. Let's talk about that.
I've gone over this before in another episode, where I try to find real definitions of abstraction and I've found two sources that have defined it.
The first one is in structure and interpretation of computer programs. They call abstraction naming some compound thing to refer to later, in a language-like scheme, which is what the book is about.
You build a new function. You write this function, it's built out of the parts that are already existing. You have this new function, now you give it a name and that naming is an abstraction. That's what they call it. That process of naming it is called abstraction.
The nice thing about a name is that you can give it some human level meaning because it's an English word, or whatever language you speak, you name it in that language. It has some human level meaning, and then you can refer to it later.
There's a layer of indirection there. You can refer to it and then change the implementation of the function. The things that call that function don't have to know that you changed how it was written.
There's an indirection. You could imagine just putting that same function inline everywhere, but that's not what we do. We want to name it so that it's easier to work with on a human level. That's one definition of abstraction.
The other one is from Barbara Liskov. She was instrumental in defining what we think of as modularity these days. We take it all for granted. She has a definition — I'll paraphrase — it's treating two different things as if they were the same.
This is a more general definition. It's anytime you have two things that are different in some detail, you ignore those details, and you treat them as if they are the same.
We can see all sorts of different kinds of abstraction. Polymorphism is a kind of abstraction. If I have an array list and a linked list, there's some common set of API, and I can ignore the difference. I don't have to know which one I have, I'll just treat them the same. I'll just treat it like a list, and things should work out.
The more I think about these, the definitions are super related. I think the one from structure and interpretation is more specific. It's specifically about naming, and Barbara Liskov's is more general. It's not about naming. It's about any type of treating two different things as if they are the same.
I want to focus on this idea of ignoring details. When you name something, you're giving yourself a handle to call that thing later, to use that thing later. You're ignoring other stuff like where it's defined, how it's defined.
If it's a function, what's inside the body of that function? You don't know. You're ignoring that because you have this human level name that lets you ignore those specifics.
Now, what I want to explore in this episode, is this idea that we use the term abstraction — incorrectly — we typically mean we're making something more general. We confuse the idea of abstract and general.
This has to do with the English lay person understanding of the word abstraction or abstract. In a conversation, I might say, "Well, that's a really abstract idea. I want to talk about real world specifics."
When someone's talking about that, they're doing the same thing. They're ignoring details. They're trying to speak at this more general level instead of talking about some specific thing. You might say, "Well, that person is very mean." It's like, "Well, what specific thing did they do that was mean?"
You're abstracting this one instance into a whole personality trait or something like that. You can tell when someone's speaking more abstractly than they should for the given context.
Typically, when we speak like that in English, when we use a term abstraction like that, abstract, we mean you're speaking very generally. It's hard for me to understand what you're getting at. I would like you to be more specific, please.
In programming, when we create a new abstraction, we create a new named function, that function is more specific than the things that it's made of. We're making something more specific. We have this confusion where we call it an abstraction, but it's actually less general.
As an example, the function map is at a certain level of generality. It can do any element-wise transform between lists. It's general, works on any list and any kind of transform.
Now, if I wrote a function that did something inside the body, it had one argument, it took a list, and then it did map, square, to multiply a number by itself of that list, now, it's much more specific. This new function I wrote can only operate on numbers, a list of numbers. It will do a specific thing, which is square all the numbers in the list.
Likewise, I could have another thing that it takes another function that takes the operation, the function, but operates on a specific list, let's say, the integers or the letters of the alphabet. It's another list.
Now, you can pass any function you want to and so map (f) of the alphabet. Again, this is more specific. It only operates on the alphabet instead of on any list.
All right, so I hope that that shows usually when we're building a new thing, we are building a thing that is more specific, it's less generally applicable than the things it's built out of.
We call that an abstraction, and it is, in these definitions. It is ignoring details. Because of that name, you're able to ignore the actual implementation. That's the kind of abstraction, and you are able to treat things differently. You're still treating this function that's an argument like it's a different function each time. We don't have to know inside that function.
We don't have to know what the function is. That's where we're ignoring details. It's more specific. There's a confusion. We're confusing this specific definition of abstraction that comes from software engineering with the lay definition of abstraction, which means general and ungrounded. We're confusing that with specific in general.
I try to avoid using this term now, abstraction. If I'm writing a new function, I don't say, "I'm making a new abstraction." I say, "I'm writing a new function," because I think that this causes a lot of confusion.
Here's another thing. People talk about stuff like, "At the top of the stack is super abstract. The stuff at the bottom of the stack, the low level stuff that's less abstract." It's totally not true. There's no more and less abstract. You can't compare the details that this layer is ignoring with the details that this layer is ignoring.
For instance, let's go all the way down to the most general stuff. Not the most, most general stuff, but very general, all the way down to NAND gates. NAND gates, it's very general. If you have enough of them, you can build a whole computer out of it.
Is that more or less abstract than the plus (+) function, for instance? It's a ridiculous question when you think about it. How do you compare the two? The plus (+) function is definitely more specific. I suppose you could make a NAND gate out of the plus (+) function. Maybe they're technically at the same level of generality. Not sure, I have to think about that.
The plus (+) function that we know is built on top of NAND gates. Somehow it gets translated into some NAND gates in that circuit, that computer, being activated and used. Let's stick with that. We don't want to get into too philosophical of a discussion here.
They're both abstract in their own ways. They both ignore details. What does the NAND gate ignore? It doesn't know what you're doing. It doesn't know what circuit it is part of. The NAND gate takes two inputs and has an output. It doesn't know if you are saving the world, curing cancer, or launching nuclear missiles and starting Doomsday.
It does not know what you're doing. It doesn't need to know. It ignores everything besides those two signals coming in. Likewise, the plus (+) function, it can operate on any numbers.
Let's say integers to be specific. If it's the integer plus (+), it ignores what formula it's part of. It doesn't know. It doesn't need to know. It doesn't know what numbers you're going to give it and what those numbers mean.
Everything ignores details, and it's very hard to compare a set of details that it ignores. You can't say that this is more or less abstract. What I argue you can do is find general and specific. Now, of course, this whole idea of Turing universality throws a monkey wrench in this idea of a hierarchy like that, of general at the bottom and specific at the top.
If you find a Turing complete system, you can create the whole stack again on top of it. That's weird. [laughs] That does mess this up. In general, when we're developing applications, were making stuff more specific.
For instance, we make a certain data structure. The idea of general data structures is very general. Like a C struct, the idea is general, you can make any data structure, write any set of data elements in that structure. Then you make a specific one that has a certain set of data elements. You're making it something more specific.
Anyway, I feel this is one of those things that messes us up in thinking and in communication. I see it all the time where, "Oh, people talk about low-level stuff as being less abstract." It bugs me. I can see all the mistakes that they're making in their logic because they have this faulty idea.
Like I said, I try to stick with the term specific in general, and avoid using the term abstract. It's not a relative term, it is simply an objective fixed. It means this ignores some details. It treats two different things as if they were the same.
There's no way to compare them. There's no spectrum of concrete to abstract. Everything is ignoring details and is ignoring something. It's almost a useless term, unless you're using it in the very specific sense of like, in lambda calculus, making a new function is called abstraction.
Then, again, it's so easily confused with the English language term, that lay general term, and the way we use it in software engineering. I suggest you avoid it. I'm avoiding it myself. Let's end it here.
My name is Eric Normand. This has been another episode of my podcast. Thank you for listening. As always, rock on.