Today, more then ever before, I/O dominates what software is about. Of course, it’s always been important but, with increasing bandwidths, I/O seems to be what most programs now spend most of their time doing. This leads to interesting questions about how, for example, to handle millions and millions of concurrent connections and we can expect this to continue unabated (for now). But, that’s not what I want to talk about. Rather, I’m interested in how this affects the object-oriented paradigm.
To understand where I’m coming from, it’s useful to revisit the principles of object-oriented programming. This is what Wikipedia says:
An object-oriented program may be viewed as a collection of interacting objects, as opposed to the conventional model, in which a program is seen as a list of tasks (subroutines) to perform. In OOP, each object is capable of receiving messages, processing data, and sending messages to other objects. Each object can be viewed as an independent “machine” with a distinct role or responsibility. Actions (or “methods”) on these objects are closely associated with the object. For example, OOP data structures tend to “carry their own operators around with them” (or at least “inherit” them from a similar object or class)—except when they must be serialized.
You might not agree with all of this but, for me, the following idea comes up a lot in conversations about object-oriented programming: each object can be viewed as an independent “machine” with a distinct role or responsibility. That is, the emphasis of the object-oriented paradigm has always been on encapsulation of both data and functionality. In essence, we send a message to an object and it reacts in a manner dependent on its particular behaviour. The machinery of the object-oriented paradigm (e.g. inheritance, dynamic dispatch, subtyping, etc) is geared towards making this simple and easy to do. And, to this end, it’s been pretty effective.
There are quite a few situations where complex hierarchies of interfaces backed with different implementations are extremely useful. Take the Collections library as a prime example. Similarly, UI widget libraries are another (and were, of course, one of the motivations behind OOP). In such situations, hiding the data and functionality of objects behind well-defined interfaces is very important. For example, we might want to change our choice of
Set implementation for performance reasons, and we don’t want this to break our program.
The issue with all this is simple: how often do we write our own collection implementation, or UI Widget? Probably, not that often … and yet our languages are geared specifically towards making this task easy to do. Of course, we still want to able to e.g. choose different collection implementations. It’s just that this does not (perhaps surprisingly) account for a large portion of what our code does. That’s because our code spends a lot of time handling I/O and moving data from one source to another.
Typically data reaches our code in the form of XML, JSON, or a plethora of other formats. Such data is not encapsulated per se because, to read it off the wire, we need to know exactly what’s in it. Such data also does not typically include functionality — it’s just raw, open data that we can manipulate as we see fit. For me, this is the very antithesis of the object-oriented paradigm.
Perhaps one reason why dynamic languages have been popular is their traditional emphasis on I/O over other aspects of language design (think e.g. Perl). Similarly, the C language remains strong and, again, perhaps one reason for this is the clear separation of data from functionality. And, of course, functional languages have always had a clean separation of these things. In contrast, with a language like Java, I’m often hitting my head against a wall. For example, I typically want many different and diverse kinds of subtyping between objects (because, viewed purely in terms of the raw underlying data, they are subtypes), and end up with an explosion of different interfaces. Likewise, I often end up with a plethora of classes which are really just
structs and have to make the awkward decision of where to locate functionality, etc (in fact, I often end up just providing accessors and then using external functions along with
instanceof tests as necessary — which again goes against the othordoxy of the object-oriented paradigm).
So, where to from here? Well, I think there’s certainly room for new languages which put a strong emphasis on I/O. For example, languages where you can write detailed schema for data which includes not just type information, but also constraints on values (e.g. the
day field is between
7, etc). And, where subtyping relationships are implicit based on the information they contain (i.e. structural typing). Anyhow, it’ll be interesting to see what developments happen in this space…