What actually is Object Oriented Programming?Sun 22 January 2017
And where does Rust fit in?
When I first learnt to write computer programs, the focus was entirely imperative: that is, programs were focussed on logic and actions. My code was organised into subroutines, but I didn't have much notion of structure beyond that.
Later, like many people, I learnt about Object Oriented Programming (let's call it OOP and rhyme it with "gloop" to add pizazz*). I felt I was evolving, that I was learning a more sophisticated form of programming.
But now I am older and wiser. I have built OOP software I am proud of, but increasingly I see classes abused, making code unnecessarily complicated, instead of organising and streamlining it. I started hearing more and more about functional programming, and was interested. I began questioning what I knew about the world. In the midst of this turmoil came Rust, my new true love. I love programming in Rust – it feels clean, clever, and fun. Rust is multi-paradigm with a lot of influence from functional; crucially though, Rust doesn't have classes as such, and doesn't support inheritance the way a lot of OO programmers might be used to. I wondered: "have I outgrown OOP? Am I now truly a superior being?"
In order to answer these pressing questions, I wanted to understand more about how Rust related to other languages I am familiar with, which got me thinking about what OOP actually is. My understanding of OOP was largely based around examples of its implementation, rather than an understanding of its essence. I had a notion of code structured around "objects" and of classes, of inheritance and encapsulation, but didn't feel this was sufficient understanding. Below are my findings after doing a little reading, as well as some thoughts about how Rust relates to OOP.
* and also to make it sound like the mistake it is
The origins of OOP
Smalltalk is an OOP language from the days of yore. Many modern OOP languages are heavily influenced by it, and one of its creators, Alan Kay, is credited with coining the term OOP. This seems like a nice place to start our journey.
Being object-oriented, Smalltalk code is structured around objects. An object is able to do exactly three things:
- hold state, i.e. references to other objects
- receive a message from itself or another object
- in the course of processing a message, send messages to itself or another object
Alan Kay describes his original conception of OOP:
"OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things."
This focus on messaging was interesting to me. I had previously mostly heard the term message passing in a Machine Learning context, e.g. in belief propagation, and it didn't sound like something I associated specifically with OOP. In a programming context, message passing involves an object sending a message to another (or itself) and the recipient processing this message. The recipient is responsible for selecting the appropriate code to run on receipt of the message.
So: how does this idea of message passing fit into other OO-supporting
languages like C++, Java, Ruby and Python? I spent too long thinking about
this, and I am confident that it mostly comes down to this: Message passing is
a way of conceptualising method calls, e.g.
foo.bar() is sending the
message to object
foo. I spent some time considering whether message passing
was one way of doing method call resolution; indeed, in Ruby classes have a
send method that takes a method name string. This is clearly very closely
modelled on the concept of message passing. On the other hand, many languages
use vtables, where pointers to methods are used for resolution. This feels
further away from the pure concept of message passing, but I don't think this
means such languages are not object-oriented. Message passing, like objects,
is a high level concept; vtables and such are implementation details.
What about the "four fundamental concepts of OOP"? My precious inheritance!
These four concepts are often stated as the "fundamentals" of OOP:
- Encapsulation (the interpretation of which is contentious, see Python's lack of privacy modifiers)
I think it is worth pointing out that these seem to come from research into what people consider OOP, and a longer list includes classes. I don't want to position a lowly internetter like myself above a legit researcher; I think we had different goals in mind: If by "fundamental" they mean that to learn OOP in practice you must understand these concepts, then I agree; if they instead mean that these are necessary and of core importance to OOP itself, then I disagree.
Let me explain. My goal here was to understand the essence of OOP – perhaps a lofty and pointlessly academic goal, but nonetheless, I think this distinction between essence and incidence is helpful and important. When I read Alan Kay's original conception of OOP, it feels essential to me: software built around objects which encapsulate state, and interact by talking to each other. I think encapsulation is essential – without it, the meaning and purpose of objects is diminished. In short, I have spent hours reading and contemplating OOP only to come to a somewhat ridiculously tautological and obvious conclusion.
But I do see things more clearly now: I don't think OOP languages necessarily have to support, for example, inheritance and composition. These are natural extensions of a framework based on objects, and a lack of support for any kind of relationship between object types would be severely limiting. As an engineer I care a lot about understanding the reality of OOP's implementations, and don't think that these things being incidental means they are of little importance. But if you don't understand the essential elements of something, how can you hope to improve it? How can you compare it fairly with other systems without understanding exactly what you are comparing?
I think it's also worth pointing out that concepts such as polymorphism are in no way exclusive to OOP; OOP supports polymorphism using subtyping, but polymorphism can be achieved in other ways, most notably through generic programming.
Where does Rust fit in?
Ah yes, back to the important question of how superior I can truly feel by dissing OOP like a wannabe functional hipster. I want to understand where my love of Rust positions me in the world of software. How do I communicate what it is I love about it, in comparison to other languages?
This isn't the place for me to go into detail about Rust's design, though I have a post if you'd like an introduction. I will say that one of the most important things to me about Rust is its use of parametric polymorphism. OOP uses subtyping to achieve polymorphism, which I really don't like and am going to blog about in another thrilling installment. Rust's design also heavily favours composition over inheritance, a stated principle of good OOP design that many seem to ignore. In these respects, Rust is very different to what I would classically consider OOP languages.
However, considering my new understanding of the essentials of OOP, I would say that Rust does support some level of OOP. While there are no classes, there are structs and associated implementations (basically methods). Importantly, structs don't support inheritance. As I have already said, I don't think this is an essential aspect of OOP at all, and indeed, composition is favoured by many. However, inheritance hierarchies are such a classic part of OOP that it would feel disingenuous or unhelpful to describe Rust as OOP without at least making this clear.
More important, I think, is Rust's achieving polymorphism without subtyping. This is fundamentally different to how OOP does it, and is such a huge part of Rust.
Finally, while instances of Rust structs could very validly be interpreted as objects, Rust code is not built solely around object interactions. In this sense, it is not very object-oriented. Compare with Java where everything must be encapsulated by a class.
So in conclusion.../tl;dr
At an essential level, OOP is about writing code that is organised conceptually around objects which talk to each other. Who knew! Encapsulation is an important part of what makes these objects objects.
Inheritance, composition &c. are natural extensions of this framework, but not essential.
Instances of Rust structs can validly be interpreted as objects, but Rust's use of parametric polymorphism over subtyping is much more salient, and sets it apart from OOP languages.
While I don't think calling Rust OOP is particularly helpful, it is not wrong to say that it has some support for it. This support looks very different from how many OO programmers might expect, without support for inheritance, instead strongly favouring composition.
I hope this was enjoyable even if it was entirely unenlightening. I enjoyed getting to the bottom of a concept that I was lacking clarity on, and am pleased to announce that I do indeed feel like a superior being, and am very excited to make a post about the benefits of parametric polymorphism over subtyping. I hope you will fight me on this because I love to learn and to make friends :)