When someone initially looks at all the bespoke interfaces on an OS that could easily be provided by a single API, one’s first and natural response is to wonder whether all this complexity has a non-historical justification for it. On Linux/BSD, I would argue that these choices are mostly historical and do not originate from some theoretical model of how an OS should be structured.

But let’s say we sit down and write a new OS: should the API for sockets be the same as for local files? Should we be able to write and read from processes by catting to and from some synthetic process file on disk? Should I be able to mount the internet on a directory and interact with sites be ls’ing their directory? Maybe I should be able to mount remote cpus and pin tasks to them? We can even take this further: registers as files, memory addresses as files, pixels and windows as files, etc etc.

All of these things sound super nice, and in many ways, they are. The everything is a file concept can be taken further than even Plan9. And fundamentally, this is what the post is arguing for. Except instead of everything is a file on a file system, everything is a table on a database.

The advantages of this approach are pretty obvious, we provide a simple and consistent interface: read/write for files, or select/insert/delete for tables. This allows a development surface that appears very simple and straightforward.

The problem is that this simplicity is an illusion and just a black box abstraction over what’s really going on. In many ways, it actually makes things more complicated as read/write become incredibly polymorphic. Maybe the API stays the same (syntax) for everything but the actual semantics still remain complex, maybe even more complicated than distinct API’s.

Even when the complexity of the semantics between the two approaches are similar, there are other problems. There’s arguments to made the heterogeneous resources should not all be provided by a homogeneous interface. For example a call to a data structure that has O(n) probably shouldn’t have the same interface as O(n^2). It makes it very easy for a developer to write incredibly inefficient code.

At its core, this dichotomy is best epitomized by Richard Gabriel’s “Worse is Better.” In the essay he talks about the difference between the New Jersey school and the MIT School. One of the differences is how the two schools think about APIs. The MIT approach is to design opaque and complete interfaces that solve the problem correctly at the expense of underlying complexity. The New Jersey or Unix approach is to value simplicity of the system at the expense of a more complicated API.

You can see an example of this in the read() system call. read() in Unix is hard to use and annoying as hell. There’s many bugs that stem from it’s misuse. The system call can (and does!) return less information that asked for even if it didn’t hit an EOF. Making read() always fill the buffer except for an EOF is a very hard problem and would have created a lot of extra complexity in the system. The MIT approach would be to implement this complexity as a simple interface is more important.

As you can probably imagine, there’s pros and cons to both approaches. Maybe the MIT way is better because many more people are going to be using read() than actually hacking on the OS. Or maybe the Unix way is better because the underlying simplicity of a system allows developers to attain a mental model of what’s going on.

If someone is interested, look at the OpenGenera source code (MIT school) vs say Plan9. OpenGenera is undoubtably a sublime and beautiful system but the code required to do it is just absurd. Plan9 is maybe less sublime, but the code itself is dead simple. Also you could compare the GNU userland vs the OpenBSD one.

tl;dr: There are costs to homogenous APIs for heterogeneous things: complexity, hard to form a mental model, easy for a programmer to misuse unintentionally. There are benefits to: consistency, beauty, easy programming.

Personally I like to take a balanced approach and decide on a case by case basis on how to trade off simplicity and correctness. Being a dogmatic programmer isn’t a good thing and it certainly doesn’t help your employers.