Abstractions are something that isn't obvious, not easy to understand. Probably because it's not the first thing we encounter in most cases. In computing abstractions are kinda the opposite, it makes difficult things easy to understand more obvious. We can watch videos and think about about it in terms of playback, time, rewind. We operate a computer in ways that make sense to us, but in reality it's a bunch of instructions acting on data seated in memory or from input devices.
Any concept in computing is inevitably an abstraction of a more complicated concept underneath. If you think about when you're using a computer you're clicking widgets, buttons, typing along text fields. For most people that's where their understanding of computing begins and ends. But obviously when i push submit, that's prompts the computer to go away and do something. Which is what a user needs and expects. How does it turn that input into some electronic stimuli. Keeping in mind that a computer is an electronic device, just a more useful and complicated at the same time to make it do what we want. Computer programmers are the bridge between satisfying a particular necessity and having a computer perform the intended action. What they have to work with is specifying step by step instructions that something underneath will be able to interpret. For example, in recent years, crafting human readable text prompts that an intelligent system can perform subsequent work, e.g. like generating birthday invitation card.
To some abstractions are the reason why software scales, while some say it's why software is difficult to understand. For some reasons too both are right or none is right. Abstractions exist because software is complex and our heads can only hold so many details at once. But it also need to scale.
From a reasonable point of view let's consider an instruction set architecture, the abstract model and interface that defines how computer software communicates with a processor's hardware.
A simple one line in Python:
print("hello, world.")
It's assembly might look something like:
mov rax, 1 ; syscall number for write
mov rdi, 1 ; fd 1 = stdout
mov rsi, buf ; pointer to "hello. world.\n"
mov rdx, 12 ; length
syscall
At this point we can question what if we have print() an integer rather than a string, would it's assembly look different. Probably, because the integer would be referenced by a memory location rather than a pointer to a buffer containing the string. It's a good thing that abstractions give us independence, that each layer only depends on the interface below it. Meaning we can change or replace one side without touching the other. It's useful that we can keep calling the same API endpoint in newer updates of resource with the implementation of the resource change. It's a better world with powerful libraries and frameworks that let's us do less and get even much than we expected.
Abstractions doesn't hide or remove the work or complexity, it relocates it. If you don't know where the complexity is, you will eventually run into it, because sometimes the details leak. We call it abstractions leak, and the cost is usually performance, reliability and/or debugging. Ignoring details is optional but with a cost. Each part of the code thinks about one thing. But do you know where the devil starts screaming? When we treat abstractions as walls rather than compressions. Nothing is concrete, nothing is obvious, you're debugging concepts not code. The good thing is that the fails are usually in a predictable way.
The newest layer in the stack is the one most of us touch in plain English. You ask an AI for a birthday invitation card, and it feels like it simply understands you. It’s a bit like talking to a very well-read friend: we say what we want and a card comes back. Underneath, it isn’t understanding the way a person does. For developers we just have to give a little more details. The LLM is designed to speculate and give us as much relevant response. If we give it less relevance it's going to either widen or narrow the scope. Your words are broken into small pieces and turned into numbers, and the system works with those numbers to predict, piece by piece, what should come next, until a card appears. We don’t need to know any of that to use it, which is the whole point. And because we don’t depend on how it works inside, it can be replaced by a better one and we keep asking in the same way. Like every other abstraction, it leaks. Ask it to count the letters in a word and it can get it wrong, because it never saw the word as letters, only as pieces. But the latter was several years ago, now and in future we expect computers to be more intelligent.
The problem with abstractions is that you don't realize they exist in a particular stack until you come across them or a certain level of details pushes you. Which is why it wouldn't help if I came to this realization earlier. This is not that one of those things i wish i knew before starting. Like I said, the stack is huge, and creating or embracing flawless abstractions is the remedy. And since they are unsparingly everywhere, we just have to understand enough to solve the problem at hand. Now isolating the problem is a different skill, less relevant here. I might not be completely right about how I view abstractions in software, but we keep encountering more of them, new ones, we may even incorporate some in our designs, and that's how we grow. That's it for today. May your builds always pass, your deploys never fail, and your rollbacks be graceful.
fn bless(you: &mut Developer) {
you.bugs = nil
you.uptime = 99.999
defer you.rollback()
}
Top comments (0)