Comparing Programming Paradigms
There’s a lot of confusion and disagreement in discussions of the relative benefits of object oriented programming (OOP) and functional programming (FP). I noticed that some people say the two are opposed while others (like Martin Odersky, creator of Scala) say they’re orthogonal. Mike noticed that OOP really has two meanings, one that people usually focus on and one that Alan Kay (creator of Smalltalk) thinks is more important. I think both of these issues are clarified if we realize that the distinction between object oriented and functional programming are along two dimensions.
I’m going to let the linguist in me come out as I write these the way we write phonological binary features.
[side effects] The difference between [+side effects] and [-side effects] goes by many names:
impure vs. purely functional
message passing vs. returning values
statements vs. expressions
The Python list method sort has the side effect of sorting the list and returns None, while the Python function sorted has no side effects (doesn’t change the list), but returns a new sorted list.
my_list = [2,3,1] print my_list.sort() > None print my_list > [1,2,3] another_list = [2,3,1] print sorted(another_list) > [1,2,3] print another_list > [2,3,1]
Message passing, I should note, is not simply the opposite of returning values. I’m going to define it as code that causes a side effect and is sent from one encapsulated actor to another. I think that to maximize the effectiveness of message passing it shouldn’t return a value, but that’s not critical to the definition; Ruby is all about message passing but always returns values.
[inheritance] [+inheritance] languages allow you to create a new data type that by default has all the properties of another data type. This goes hand in hand with what “object oriented" most obviously means, which is that decisions about how actions apply to data types are made by the data types, not by the actions. [-inheritance] languages let the actions (functions) make these decisions, so inheritance doesn’t make sense for them.
By crossing the possible values of these features, we get a typology of four types of programming.
[+side effects] [-side effects] [+inheritance] OOPFOOP [-inheritance]ImperativeFP
Imperative programming doesn’t have inheritance because it doesn’t have objects, and it tends to use statements (hence “imperative"), creating side effects.
Functional programming, in my mind, is defined by avoiding side effects as much as possible. It’s also characterized by the use of higher order functions, but somehow I don’t think that’s what all the fuss is about when people are comparing paradigms. FP is not associated with objects and inheritance; it’s the functions that decide how actions apply to data types. But if you think it could have objects, that’s ok. My point in making this table is that what “functional programming" means is unimportant; which features you want to use is what matters.
Object oriented programming is sometimes seen as being all about message-passing, sometimes seen as being all about inheritance, and sometimes seen as requiring both. In order to fill out the table, I put OOP in the cell that’s both [+side effects] and [+inheritance]. In the [-side effects] [+inheritance] cell, I wrote FOOP, for functional object oriented programming. This is where I’d put Scala, for instance — its standard data types are immutable and it encourages [-side effect] code, but it does make use of objects and inheritance.
So if that’s what’s out there, what should we use?
Side Effects Are Unavoidable
If you want your program to do anything useful, it has to have side effects on the world outside of it, by printing to your terminal, updating your database, changing your webpage, writing to your file, or making your robot play soccer.
Side effects internal to your program are more avoidable, but some data structures, like hash tables, rely on updating existing values to achieve their efficiency.
When you change variable values mid-program, you make your program harder to understand, and that increases the risk of bugs and the difficulty of fixing bugs. After Mike primed me to think about side effects, I realized that the bugs that made me the craziest, because they took so long to figure out, where due to unintentional side effects. So I think the best way to program is to limit your side effects — and constructs that make side effects possible (mutable data types, generators) — as much as you can, resulting in a practical functional style.
Haskell is a purely functional language except for its I/O Monad, which encapsulates all the code that has side effects on the outside world. This results in the purest possible program that still does useful things. Compare the idea of using message passing at the top level of a program and FP underneath.
In languages that lack support for some functional programming constructs, or when you’re using a mutable data structure for efficiency, you can have impure programming inside of functions, and make sure that your function doesn’t have side effects outside of it, on global variables. This means you only have to keep track of side effects within a small function, not within your program as a whole.
I do think I should read more of what Alan Kay has to say though, since he thinks there’s really something good in the idea of message passing. (In fact, Mike and I just had a conversation that brought up a benefit of message passing, so I’ll post about that sometime soon.)
Inheritance Has a Specific Purpose
Part of the confusion out there is that people often say OOP is good because it makes programs more modular, which enables more code reuse and makes code easier to understand.
OOP does enable modularity, but to say that’s what makes OOP good implies that other paradigms don’t enable modularity, and that’s just not true. Imperative and functional programs have modules, which separate parts of programs, hide implementations, and enable code reuse.
[+inheritance] languages encapsulate objects while [-inheritance] languages encapsulate functions. Both can encapsulate modules.
The thing that’s special about inheritance is that it makes it easier to add new data types (if they’re related to old ones) than modules and functions do. But there’s also something special about orienting your program around functions instead of objects — that makes it easier to add new functions to your program than objects do.
Grossman, Dan. OOP Versus Functional Decomposition. Programming Languages, Section 8. Coursera.
Odersky, Martin. Programming Paradigms. Functional Programming Principles in Scala, Week 1. Coursera.