I love OOP so I’m going to stand up for it even if it makes me look like a doofus and it’s only kind of relevant in this thread. Somebody’s just gotta after comments like these. I don’t know why everyone wants to rag on it all the time…although I do suspect Java is somewhat to blame.
Of course, part of what I think makes conversations like these challenging is that the phrase “object-oriented programming” means something a little (or a lot) different to everyone. I don’t think it helps that “functional programming” is the same way. Sometimes I think we should throw out all of this programming paradigm jargon and start over with more precise terms.
But, anyway, I think one thing that ties together most language features that get called “object-oriented,” at the very least, is that they allow you a wide syntactic design space when you’re creating an interface to some set of data. That’s really what I like about them more than anything. An object gives you a “syntax scope,” where you can pretty freely choose what the messages to the object mean character-by-character without affecting the rest of the codebase. I think people often don’t focus enough on syntax when they’re considering OOP—when it’s done well it gives you a lot to lean on in pursuit of “representing your ideas directly in the code,” that vaunted ideal.
To give an example of what I mean, say we have a SQLite database table storing 4D vectors:
sqlite> SELECT * FROM vectors;
id x y z w
-- ----- ----- ----- ----
0 58.93 21.75 97.94 0.24
1 79.09 49.08 17.31 0.49
2 88.41 17.85 16.24 0.37
Let’s say we want to get the X, Y, and Z components of each vector divided by their Ws. I think we can all agree that, for a given vector v
, this would be a nice way to express the operation we want to perform in code:
v.xyz / v.w
SQLite’s syntax doesn’t really allow you anything quite that elegant:
sqlite> SELECT x / w, y / w, z / w FROM vectors;
x / w y / w z / w
---------------- ---------------- ----------------
245.541666666667 90.625 408.083333333333
161.408163265306 100.163265306122 35.3265306122449
238.945945945946 48.2432432432432 43.8918918918919
This is okay, and it makes sense if you’re familiar with SQL, but I don’t think it’s the first thing you would come up with, exactly—it makes certain concessions to the “databaseness” of the environment.
Now, let’s say we’ve loaded this data into four-component Ruby Vector
s, and we’ve extended Vector
with my Ruby swizzling mixin that I posted the other week, in this manner:
class Vector
include Swizzleable
def swizz_vals
(0...[4, size].max).map { self[_1] }
end
# if there's only one component, return it as a scalar; otherwise, return a
# Vector
def post_swizz(comps)
if comps.size == 1
comps[0]
else
self.class[*comps]
end
end
end
Now if we have all the Vector
s in an enumerable called vecs
, we can do:
vecs.map {|v| v.xyz / v.w }
#=> Vector[245.54166666666669, 90.625, 408.0833333333333]
# Vector[161.40816326530614, 100.16326530612244, 35.326530612244895]
# Vector[238.94594594594594, 48.24324324324325, 43.89189189189189]
To me, this is a great example of the power of OO (with a bit of help from dynamic typing). You probably wouldn’t want to define functions at the top level with names like w
or x
, but in the interface of a particular type, the meanings of strings like that have a much more constrained range of possibilities generally speaking, so having methods/member functions with those kinds of names can be obvious and unproblematic. Also, mature OO languages often give you a wide variety of ways to express how you want the interface to a given type to work, even very abstractly (Swizzleable
uses Ruby’s metaprogramming features to express what it wants in only a short bit of code, calling things on the including class like define_method
and public_method_defined?
at include time).
In a simple procedural language like C, we would probably be stuck with a syntax like
vec_scalar_div(vec_swizz(v, vec_xyz), vec_scalar(v, vec_w))
or something, and we would probably end up generating the definition of the vec_
swizzling enum as part of the build process instead of being able to express it abstractly in the language itself. Even in a powerful functional language like Haskell, although we could define the mapping between “swizzling strings” like xyz
and actual component accesses abstractly in our code, I think we would probably still end up with code like
swizz v "xyz" / comp v "w"
because of the namespacing issues that would otherwise arise from defining functions with names like w
at the top level (the Haskell community seems to have spent years and years trying to come up with a nice solution to this issue and although there have been a variety of proposed extensions and things, I don’t think anything has caught on to the point of being truly mainstream, although a more seasoned Haskeller is welcome to correct me if I’m wrong). Of course, even if we did define define functions like that at the top level, we would end up with
xyz v / w v
which is at least terse, but I don’t think it’s quite as obvious as the v.xyz
-style syntax (even mathematicians would use subscripts, after all).
Also, I think it’s worth noting that OO languages generally make it easy to share “bits of an interface” between different types, however it makes sense to do that. Swizzleable
, as a mixin, is a nice example of this; you can use it to add swizzling to the interface of any type for which you think it makes sense (Ruby supports mixins via modules, C++ does it with abstract classes, Java/C# with interfaces, etc.). In the more narrow cases where you want to provide an implementation along with the interface, you can do that too, via what people generally mean by “inheritance” in OO. This gives you a nice, fine-grained way to make things convenient for the users of your “bit of an interface,” depending on whether it would be nicer for them to design the implementation for it or use a ready-made one (or both! languages like Ruby and C++ let you pick between things like this conditionally, C++ at compile time via templates). Languages without OO, and even languages with OO but without “classical” OO, don’t necessarily give you so many options for composing interfaces piecemeal like this; Haskell’s type classes give you something kind of similar at least, but even then you don’t have as much flexibility for the syntax of the resulting interface.
Zooming out a bit, I think the basic “message passing” idea popularized by Smalltalk is a nicely intuitive way to think about information moving around in a computer program, especially one with complex patterns of interaction between its different parts. It’s easy for us to understand the idea that both a kangaroo and a CPU might understand the message “jump,” but in different ways. Also, once we’ve established that we have a kangaroo and a CPU responding to messages, it’s a hop, skip, and jump to the idea of having a conductor in the program waving their baton around and sending the message “hop” to the kangaroo and “jump” to the CPU at different times and so on. I think it’s nice to remember that SIMULA, the language which is often considered to have founded OOP, was designed for writing simulation programs, and a computer game is basically a kind of simulation program.