When I was in college at Oberlin, one of the early classes that was required for the CS major was called something like “Programming Abstractions”. I did a quick check, and yup, it’s still offered. This was a class that teaches the programming language Scheme, which for many people is an eye opener. Scheme can be viewed from a number of different levels and it is both an appalling language and an awesome language.
I had a lot of issues with the language, most of which was that I was trying to figure out how it was implemented. How the hell did everything work under the hood? I interrupted the lecture routinely because I wanted to know to really understand what was going on under the hood. Many years later, the head of the CS department told me that I was one of the most stubborn son-of-a-bitches he’d had because I was never satisfied with a short answer. I get it – he had to go through the syllabus and reach as many of the students as possible, not teach a seminar in the efficient implementation of Scheme.
Still, I recall that he did one class where he covered the implementation of a bank account management system in Scheme. Scheme does everything in lists and if you wanted a data structure in Scheme, you would use make a list of the elements and then write accessor functions to get at each element and factory functions to build them. It was painful, but the point was that you could aggregate the accessors and factories and make something akin to object properties and constructors found in other languages. If I recall, the lecture also showed how you could change the behaviors and have different functional views on the same data, so you could have people who could, for example, view the data but couldn’t withdraw funds.
And this is the essence of abstractions in programming. It’s the ability to describe the valid operations on a data structure in such a way that you can change or have different representations of the data without needing to change the code that consumes the interfacing.
Not too long afterward, I was working at Bellcore on a project called SuperBook. Superbook was an experimental hypertext system that was built to make it easier to read and find information in a text. It was a cool system that was way ahead of its time. It initially ran on a Sun workstation running the MGR window system. I had done a port of MGR to the Macintosh and MGR would run under that system, but the people in my department wanted SuperBook to feel more like a Macintosh application, while still keeping all the advantages of the application.
Joel Remde had made an API for the application and I could work with it over a serial connection. I made hooks for it in the Mac application that implemented the API through a serial protocol. If you were logged into a UNIX system through the Mac’s serial port, you could run SuperBook on the Mac. The best data rate available to me at the time was 19200 baud, which was pretty appalling compared to the native application.
Right around that time, Apple started getting on board with ethernet and I got my hands on an ethernet card that I could plug into the Macintosh II that I used for development of SuperBook. I was tasked with adding ethernet support to the app. As I worked with it, I saw that both the serial and ethernet protocols shared a great deal in common with each other in terms of what I needed to do, although the implementation details were very different.
I was able to distill it down into a few actions:
- data available
- read data
- write data
And from this, I implemented an object in C that was very similar in many respects to the lecture in Scheme a few years earlier. I had created an interface and had two implementations that met it and now I had an app that could select its communications channel on the fly and the app didn’t care which it used.
This is much of practical engineering: looking at a problem and not only solving it, but deciding if there should be levels of abstraction in the solution and how many are appropriate.
Sometime later I did a private study course at Oberlin wherein I implemented FORTH for the Macintosh. It was a nifty little system that was JIT compiled. At the end of the semester I gave a talk about my project called “What Does DOES> Do and Other FORTH Do’s and Don’ts” which covered the process of creating closures in FORTH and how they were implemented under the hood with a comparison to Scheme. One of the conclusions I had was that Scheme was abstract and FORTH was concrete – the reason being that in Scheme, you didn’t really know how any particular thing was implemented (which is a good thing and a bad thing) whereas in FORTH, you could do very much the same things and you knew exactly how they were implemented (which is also a good thing and a bad thing).
The point being that abstraction is neither good nor bad, but it is a tool to be used in judicious measure.