“Should I use functional or object-oriented programming?”
A student asked me this as I closed out one of my Python Programming lectures in January. In this context, the student meant “what should I use for my upcoming homework assignment,” but I didn’t realize that at first. On most occasions when I hear this question—usually in professional circles—the unspoken subtext is “always.” Which of these two styles should I swear by as the right way to write code?
A second later, the student clarified “…on this assignment, I mean.”
Too little, too late. My gears were already turning.
The Functional/OO debate has, in my view, two big problems.
- The “all or nothing” assumption. I have a colleague who wants all his code to be functional code, full stop. I also have two former colleagues who swear by, and I quote, “lots of little objects.” I don’t fall into either camp because I think there are better questions than “which of these two tools should I use always,” with more insightful answers that have more potential to make us better programmers.
- Absolute terminological butchery. We have these two terms: “functional” and “object-oriented”, that we use interchangeably with other would-be synonyms, except that they don’t describe the same thing. And what’s with the fact that “functional” is just “functional” and “object” has “-oriented” tacked onto the end? Why isn’t it opposite “function-oriented?” We’re super imprecise about the way we discuss these ideas, and then we make new programmers feel stupid because they cannot picture the leaping triple-axle we obviously meant to perform when what we actually did was slip and fall on the ice.
This grinds my gears to nubbins: the way we teach, instruct, and describe in lazy, platitudinous, imprecise ways, and then suggest that some people just aren’t smart enough to get it.
So I got angry and tried to fix it.
Here’s a video explanation. The explanation references Python because I recorded it with my Python students top-of-mind. That said, Python also serves as a useful model language for discussing this topic.
If you don’t want to watch me scream and gesticulate while my off-kilter bowtie tragically shortens my neck for the camera, you can see a more language-agnostic version of the point I’m making here in the text below.
So first of all
Let’s talk about terminology, because there’s a massive lack of precision on this floating around the programming community, and it makes these concepts harder to understand than they have to be.
What is a Paradigm?
Let’s talk about two different paradigms: different ideas about how solving a programming problem can work.
- imperative: describes a way of thinking about a programming problem. Specifically, thinking about a programming problem in terms of how to perform tasks and how to manage state
- declarative: describes a way of thinking about a programming problem. Specifically, thinking about a programming problem in terms of what output we want, without having to know the details of how we got there.
Different programming languages adopt each of these to different degrees.
For example, in Ruby, when you want to write a web app, you inherit from classes (usually defined by a framework like Rails or Sinatra) that are specifically designed to help you keep track of state (database records, attributes on objects) and behavior (which requests are supposed to route to what actions).
By comparison, in SQL, you write a statement declaring what data you want out of the database and how you want it organized. SQL decides for you how to get the thing you want—whether to use indices, what order to do things in, et cetera—without bothering you to specify that information.
How do we implement the paradigms?
Chiefly, programming languages implement these two paradigms with object-based implementations or function-based implementations.
- object-based: describes the implementation of a solution in code. Specifically, a solution that depends on the instantiation of, use of, and inheritance from objects.
- function-based: describes the implementation of a solution in code. Specifically, a solution that depends on the definition of, use of, and passing of functions to functions.
These are not the only ways to implement the paradigms. Huge, common example: SQL is largely not a functional language. You aren’t passing functions around to functions. But it does implement the declarative paradigm. The paradigms and the implementations are not equivalent things.
What does “-oriented” mean?
When a programming language is oriented in a certain direction, it means that the constructs available in that language loan themselves better to one implementation or the other.
- object-oriented: describes a programming language. Specifically, one whose constructs make object-based solutions convenient to implement.
- functionally-oriented: describes a programming language. Specifically, one whose constructs make function-based solutions convenient to implement.
Now, it is possible (though kinda difficult) to make a language that only supports one type of solution. Haskell is pretty close to a functional language. Alloy is pretty close to an object language. However, the utility of a language drops off pretty fast if it only does one or the other because both are at least a little useful in most programming areas. So -oriented means “one is more convenient, but you can kinda do both.”
Colloquial terminology butchers this by referring to functionally oriented languages as “functional” and languages that are oriented either way as “multi paradigm” despite the fact that the paradigm is an idea, not an implementation, that a programming language does not have a paradigm, and that a language is X-oriented already denotes that it supports multiple implementation strategies.
(I firmly believe that the reason that this functional vs. OOP idea is so hard for people is that we use the same term to mean six different things, two of which are sometimes opposites.)
I found this chart to provide a visual, but it also butchered the terminology, so I fixed it:
Python is an object-oriented language.
People will argue with me on this point that Python is “in fact, dual-paradigm.” I disagree for reasons that you are now intimately familiar with. Python is object-oriented. You can do functional programming in it. It is designed, however, to prioritize object-based programming. BDFL Guido Van Rossum has said this himself on several occasions (here, straight from the horse’s mouth, don’t @ me). So far the core team has not reversed any of the major technical decisions driven by that point of view.
I would also argue that Python is not only not at all unique in its support for multiple paradigms, but also a far cry from the most graceful language at supporting multiple paradigms. This is fine: as I mentioned, the design goals of the language have never included functional support, or even grace in general (see here, we talked about this). But like, when people get on a high horse about this, please don’t be taken in.
Fine, Chelsea. Anyway, which one should I use?
I’m kidding: based on the fact that we just spent a thousand words getting clear on what we’re even talking about, you’d be right to predict that my answer to this question has a lot more nuance than that. Meanwhile, though, I try to keep things pithy and digestible around here. So we’ll call it a night and dig into the “what to use” question in another post.
If you liked this piece, you might also like:
The last time I just absolutely snapped on imprecise terminology in tech (on this occasion about “technical debt”)
The debugging category (people seem to like this and struggle to find similar content elsewhere)
The risk analysis workshop (4 out of 5 “Jimi Hendrix of [insert programming language here]”s approve!)