The false dichotomy of functional versus object oriented programming

Unless you’ve taken up a life of asceticism, you will have witnessed the rise of functional programming as the new “silver bullet” in councils of software engineers across the world. Object Oriented Programming, the previous paradigm that was promised, has begun to sustain heavy fire from some of the more warlike elements within the functional faction. Are these salvos valid? Do the arrows of the attackers fly true, or do they misunderstand the true nature of their perceived enemy? Let’s find out.

We’ll start by examining a quote from a Reddit user, tehroflknife:

“It seems like using DDD [author's note: here Domain Driven Design is synonymous with good Object Oriented Design principles] is completely contrary to the idea of having some sort of immutable business object. I like the idea of having completely immutable types that are passed into stateless services with clear inputs and outputs. To me, an anemic model is a good way to approach this.”

Here, good sir tehroflknife is utilising functional programming without objects. First, he takes an immutable data structure, with no behaviour of its own. Then, he recruits the aid of a collection of stateless functions, brought together under the united banner of a service for organisational benefits. The immutable data is given over to the champing function. The function, despite its best efforts, can do nothing to alter the data it has been given. Instead, a wholly new bag of lifeless data is presented to tehroflknife for later use.

data class SomethingData(val someValue: Int, val anotherValue: Int)

class SomethingService {
    fun someOperation(something: SomethingData, operand: Int): SomethingData {
        return something.copy(someValue = something.someValue * operand)
    }
}

Listen now! The battle cries of the anti-OOP outfit in the distance: “Throw off the shackles of side effects! Embrace the power of immutability! Death to OOP!” They make a strong case. Side effects do make code hard to test, harder to reason about. Immutability does provide safety, especially in a multithreaded environment. But, under all of this, is the assumption that following the virtues of Object Oriented Programming would lead one astray from these twin paths to righteousness. Does this assumption hold? Take a look at this sacred scroll, and tell me what you see:

data class SomethingClass(private val someValue: Int, private val anotherValue: Int) {
    fun someOperation(operand: Int): SomethingClass {
        return this.copy(someValue = someValue * operand)
    }
}

This is exactly equivalent to the service plus immutable value approach admired above by tehroflknife, but now the data and the functions which act on them have been encapsulated, following object oriented principles. An immutable domain object is born. The two pillars of functional programming remain upright and proud. The reality is that the two faiths are not in conflict, and can be followed in harmony, reaping the benefits of both paradigms.

The confusion here may stem from the fact that object oriented languages almost universally do allow mutations of state and side effects (unlike Haskell for example, which prevents either of these accidentally occurring). Of course, this does not mean it is impossible to write functional code in object oriented languages, simply that it takes a little more discipline. There is also no reason why an object oriented language could not apply the same constraints as Haskell.

We have now established that the forces of functional and the order of the object can live together in harmony. When then, should you call on the support of each? Part two of this article will detail where each technique is better suited. Peace in our time is in sight!