What is OO?
Reading Paul Graham’s reading of Johnathon Rees on what is ‘OO’, or object-oriented made me wonder why we still treat this as such an ‘all-or-nothing’ affair. First off, some personality clash. While I don’t know Paul Graham’s opinion of OO first hand, his implied support of Johnathon Rees’ opinion, and thus the implied support of this lovely gem seem to betray some arrogance on his part:
“This is related to Lisp being oriented to the solitary hacker and discipline-imposing languages being oriented to social packs, another point you mention. In a pack you want to restrict everyone else’s freedom as much as possible to reduce their ability to interfere with and take advantage of you, and the only way to do that is by either becoming chief (dangerous and unlikely) or by submitting to the same rules that they do. If you submit to rules, you then want the rules to be liberal so that you have a chance of doing most of what you want to do, but not so liberal that others nail you.”
Eighty percent of our time, whether corporate or open source or just our own projects, is still, in this day and age, spent on finding defects. Why spend a single second on that horrible job more than we have to? Static type checking is a freebie, and if you really want to see it help you, don’t keep setting up C++/Java strawmen. Check out Haskell and it’s insane type system and type inference. Being able to eliminate some types of concurrency bugs with types alone is neat. And before anyone mentions it, don’t forget to unit test too🙂
(Although I complain, I do have to admit when I see Scala code, ‘bondage-and-discipline’ aren’t too far from my mind.)
Anywho, the between-the-lines reading of all that is that Johnathon Rees thinks people who like anything ‘OO’ are moronic Java slingers and could never hack real code. Whatever.
Before I go off on a rant here, what is OO then? Before Rees goes off on his own rant about corporate drudges versus leet Lisp haxors, he makes a list of what he considers OO. I’ve been thinking about this too, because while I believe we’ve gotten some good things out of the OO train in the past decade, there’s still quite a bit more to squeeze out before we abandon all further development. These features appear in this language or that, but rarely appear altogether in a nice package. Some of these features appear in all languages claiming some support for object orientation, while other features seem to just appear and then disappear again like so many ideas in computer science. The point is, not every OO language incorporates all these features, and you don’t have to incorporate all, or even most of these features, to claim to have support for OO.
- Encapsulation: Private, protected, public members and scoping. But we seem to have lost faith in the encapsulation mantra, with a more modern object oriented language like Python forgoing the whole thing. In prototyping languages like Python, private members can be seen as somewhat constraining for sure. I’ll agree with the BDFL that private members shouldn’t prevent a user from mucking around with them. I’d just like a way to tell the user ‘hey, dragons be here’ without having to resort to an ugly name mangling hack. Plus, I’m tired of exploring a new library with dir() and help() and not knowing what I’m supposed to pay attention to because every library implementer has a slightly different way to separate interface from implementation. Encapsulation combines with another aspect of object orientation, interfaces, to produce an insanely helpful design technique. Why stop here, though? Encapsulation has many more rich levels to explore, similar to C++’s friend notion, or special semantics for test code (and only test code) to have access to private members. What about ‘dissallowed’ members? In C++, you make your copy constructor’s private to prevent anyone from calling them if you want your class to have reference semantics. Why not make this explicit?
- Everything is an object: This is just like saying “everything is first class”. In a truly OO language, should there ever be one, functions are first class, values are first class, types are first class, classes are first class, code blocks are first class, etc. This mindset feeds into another important aspect of the spirit of OO, higher level metaprogramming. Since everything is an object, and every object has a type, this also allows you to make an incredibly expressive and safe type system. Rees points out that Java, the quintessential OO language fails at this since it leaves primitives (doubles, ints) ‘unboxed’, and not true ‘objects’.
- Message Passing: Commercially successful OO languages like C++/Java adopted the semantics of message passing, object.method, but missed the computational model. This leaves their real implementations still relatively imperative looking. Message Passing is a subtle and powerful model, also called the Actor model, that lends incredibly well to some forms of concurrency and distribution. Message passing can only be simulated in commercial languages like Java, and instead their techniques have to be degraded into ‘best practices’ like ‘tell, don’t ask’. If anything grows in OO, this needs to. Functional programming is clearly taking over imperative programming’s turf. A new computational model can be used to supplement Functional programming where it has hiccups, like side-effects for example, which I think message passing would be great for (although this is just a hunch.)
- Interfaces: What started out as simply keeping your .h files and .cpp files seperate has become a full fledged design technique. Yes, as Rees points out, interfaces and ‘discipline’ coding helps ties teams together. But interfaces also dovetails Test-Driven-Development really well. The methods you end up testing in TDD usually are derived from some sort of interface design, and even if an interface has just one single inheritor, in some cases it’s still worth it to break down a system into the thinnest interfaces possible.
- Inheritance: Rees also aptly points out that this, as we’ve learned, is less the be-all-and-end-all technique of good OO and more or less just a neat feature that’s good in some cases. Many of it’s best uses would probably be better codified elsewhere, like what’s called traits in some languages, mixins, aspects, or the template-method pattern. In other cases, specialization on a subtype really is what you need. It’s probably overused, but inheritance still is the quintessential OO feature.
- Polymorphic Method Dispatch: The idea that the real function you are calling is decided upon by at least the real, run-time type of the first argument. Animal declares method makeNoise, while cat and dog both implement it. That means at run time, two animals calling make noise will call the methods declared by their real types – one a dog and one a cat. While very useful in it’s own right, this has been properly criticized as being an awkward place to stop on method dispatch. Notice I say ‘at least’. This means that dispatch mechanisms that dispatch on more than the first type, I still consider OO. In fact, method dispatching on the real, run-time type of any ‘polymorphic’ type, i.e., a type that inherits from some other type or interface, is OO. Whether it’s just the first argument, in C++’s case, or all arguments, in Lisp’s generic functions, doesn’t matter. In fact, I think it’s about time real OO languages began adopting more ways to dispatch on type without resorting to hacks like double-dispatch via visitors or something like that.
- Actor Model Concurrency: The other half of message passing is a type of free concurrency and distribution called the Actor model. This will NOT solve all of our concurrency problems – but it can only go to compliment the other forms of concurrency being pushed by functional crowds. Basically, the Actor model says that encapsulation doesn’t just encapsulate objects from each other, but should also encapsulate threads from each other. An object’s state should only be changed by itself, on it’s own thread of control. The ‘Active Object’ pattern also captures this. There is no reason why OO languages should still resort to imperitive means of concurrency like threads and locks when we already are half way to a completely independent and powerful concurrency model.
- Reflection: Another feature that’s only implemented in a few OO languages, in some ways its added on hackishly. Reflection is a very powerful step towards a high level metaprogramming that OO is capable of, code operating on itself. I say high level because Lisp has had low level means of acting on itself all along. Reflection is not just a way to figure out types at run-time manually, but also find out what methods are supported, what are the signatures of those methods, as well as anything else. Reflection on it’s own is like inheritance, it’s kind of neat but not too incredibly useful other than specializing on various attributes. Reflection really comes into its own when it’s combined with high level metaprogramming.
- High Level Metaprogramming: Lisp has code that operates on itself, and it is able to do so because it’s syntactical model is so simple – it’s just a list. Many of the most popular languages today, however, hard more difficult to parse since they’ve adopted Algol style syntax. Moreover, there’s more to metaprogramming than simply operating on lists. High Level Metaprogramming is like an API for metaprogramming. I could be given the raw code for a class, and step through it slowly to figure out it’s method names. Or I can rely on a built in .getMethods() method. High Level Metaprogramming uses the same style, conventions and techniques as any other OO code, except you’re operating on code. No magic, no tricks. This is something I’ve only seen glimpses of, but sadly most metaprogramming supported in OO languages today is either not OO, so it breaks the conceptual model, or a little ad hoc. I think C++ template magic is great, but it’s hardly OO. We’re just going back to lists again!
Some of these ideas have been continued to be researched, while others haven’t. While I certainly think object orientation has come a long way in encroaching on traditional procedural/imperative coding, I hope I’ve shown that there’s still much to be done. OO isn’t just some java drudges toy, it does have the potential to offer everything that the functional paradigm offers. And most importantly, the two continue to compliment each other very well. We just can’t give up on OO yet🙂
No comments yet.