Objects have been taking flak. One objection I’ve heard a few times is to the idea that “everything is an object”. I couldn’t find a way of interpreting the phrase that rendered it simultaneously accurate and objectionable, so I figured it was a fuss over nothing. But the discipline of trying to express an idea in more than 140 characters has forced me to rethink a bit. (By the way, here is a recent attempt to say what objects are.)
Let’s dispense up front with one possible (but silly) interpretation of “everything is an object”: that an OO program is made entirely of objects. This is silly because there are no OO programs in which everything is an object. Objects have methods; in some languages they have attributes (as distinct from methods); in others, classes. These are aspects of an OO system that do computational work, but are not objects. Clearly this is not the intended meaning of “everything is an object”, then, because it’s trivially false.
[Remark: In a reflective enough language, you might be able to get hold of an object representing, say, a method. One might say then that the method “is” an object, in that it can be described by an object. The meta-object representing that method will itself have methods; those methods will also be “objects”, in that they can be described by meta-meta-objects, and so on. So, with full reflection, we might be able to salvage a true variant of the above interpretation of “everything is an object”, but again, I suspect, not the intended one.]
Here’s a more plausible interpretation of “everything is an object”: in OO designs, everything ends up being modelled as an object. The intended criticism here might be that OO invites designs that suffer from “object overkill”. There are Iterators and Adapters and Visitors and Policies and Strategies and Handles and References and DataItems and RecordSets. Objects everywhere you look, regardless of whether they seem to be describing the application domain. Apparently no part of speech is safe. But turning something into an object isn’t like climbing a mountain. You shouldn’t do it just because you can.
Everything is a value. But is every value an object?
I agree with this. But if this is the criticism, it’s not a very compelling one. I mean, it’s a compelling criticism of poor practice, but it’s not a criticism of “OO”. Of course it doesn’t make sense to turn all aspects of a system into objects. But no diligent OO programmer would.
Moreover, representing things as objects is how OO programmers model the domain features that they want to make “first-class”: the things, or qualities, or interactions that they want to configure and pass around at runtime. Values are what we use to do modelling; at runtime, it is values that represent the domain features the application is “about”. In OO, those values happen to be objects. What’s the biggie?
At least, that’s where my thinking was until I started writing this down. But now I start to feel the force of what was perhaps the original criticism. If every value is an object, then it follows for example that numbers are “objects”. But hang on. Is it really helpful to model stateless quantities like “the natural number 3” as a stateful agent interacting with other natural numbers by “sending them messages”? That’s not what natural numbers do. In fact natural numbers don’t do. Natural numbers are. They’re data, not codata.
In other words, perhaps the real objection is not that OO programming methodology has a tendency to make first-class things that don’t need to be made first-class, but rather that there are ways of making things first-class other than representing them as objects. Objects are behaviours, or branching streams of states; and if domain features only enjoy fixed — rather than interactive, transformational — relationships with each other, then maybe it simply isn’t appropriate to model them as objects. (Perhaps this is what David Barbour meant by his observation that some abstractions are about relationships without well-defined interactions.) So while it is right and proper that a programming language provide a system of values for encoding domain models, the problem with OO, in its purest form, is that the only kinds of values are co-values. If this is the objection, then I think I agree. There are other kinds of values than objects.
Record and replay
I’m not really clear on what this implies in practice. Languages with both data and codata treated properly would be a start. In Haskell you can have data types that are also codata types, but in a way I’ve never found particularly satisfying (although I can’t really say why). In existing OO languages, there are things that look like codata, but are also data. (Codata is defined solely by its elimination forms, whereas in practice you usually say how to construct an object too.) But they too don’t feel right. It’s all a bit hacky. So there’s probably room for a much cleaner foundation.
This is probably a banal observation, but maybe we can also connect the data and codata perspectives together. Recently I’ve been reading a lot about tracing debuggers, execution monitoring, algebraic steppers, and other ways of getting a tangible handle on execution. What’s struck me is how frequently we want to convert between two different perspectives on execution. On the one hand, we often start with a notion of execution as a transition system, whose evolution we then want to record into a some kind of data structure or trace that captures what happened. It’s helpful if that structure is inductively defined because it make it easier to reason about. This is fine because we’re only ever going to do this for a finite prefix of the execution, even if would keep running for ever given the chance. Conversely, given such an execution trace, we then often want to replay it, so that we can animate it, or step through it, or debug it with some notion of a moving focus, as in algebraic debugging.
Bret Victor, in Ladder of Abstraction, shows how it can be useful to take the dynamic evolution of a system and “trace” it into a static value. The system being traced might be an object, but the trace itself is not: it’s a data value all there at once. Equally, in LambdaCalc, the objects of study are “executions”. But executions are reified as values, not objects: we can talk about them, manipulate them, write functions as folds over them; but not directly “observe” them. Observing a static trace means replaying it.
These might already be common patterns in datatype-generic programming. “Observing” (visiting?) data, so that it looks like finite codata; and “tracing” codata, so that some finite prefix of it looks like data. When the data and the codata have the same “shape” (= inhabit the initial algebra and terminal coalgebra respectively for the same functor), maybe this interconversion is always possible, and something we could integrate more deeply into a language.
[Remark: here’s another reason we might want to be able to convert between “data-oriented” and “codata-oriented” views.]