FP vs OOP. In search of truth

Roman Krivtsov
5 min readSep 25, 2018

--

To compare such different approaches we need to find some basis for both of them. So let’s take the most common programming principles https://en.m.wikipedia.org/wiki/Category:Programming_principles and consider them in terms of OOP and FP. Since there are a lot of them, I’d group them to:
1) Solving complexity with modularity
Encapsulation, Cohesion, coupling, separation of concerns, single responsibility, Open/Close, KISS
2) Preventing inattentive mistakes
DRY, Single source of truth, Code re-usage (Inheritance, Polymorphism)

The magic of these principles is that they come directly from our life. Human’s brain can’t operate with more than 7 objects at the same time, so we build abstractions (which are in fact objects from Category theory, which is a base of type theory for functional languages. But let’s slow things down for now) over real objects to be able to think and make conclusions. We also like when things at home are in order, clothes with clothes and dishes with dishes. Without modularity, it’s not possible to produce any kind of complicated product from movies to cars. Moreover software. In real life, we can be inattentive to details, but with building of big systems, it’s not acceptable. So we protect ourselves with different technics and automatizations.

Modularity

Usually, systems become complex when they consist of one or multiple large parts (monolith). Also when these parts are coupled with each other, which doesn’t make sense for them to be actually separated. Unfortunately, none of the approaches help you separate concerns properly, but they suggest different ways to build modules.

I would include encapsulation to modularity since it reduces scope and in fact, makes code isolated. FP has encapsulation on the level of functions and modules and OOP on the level of classes and methods. Every class contains properties and methods which belong to this domain, encapsulating internals and exposing public behavior. In order to create nice classes (which provide nice API) and then maintain and extend them, there have been created principles like SOLID, GRASP etc.
FP programmer would laugh at OO modularity. Comparing to pure functions, classes are huge and dirty. It’s true: usually, classes are big, they do share a context, not even between its methods, but in the hierarchy of children classes as well. Well designed classes may be really granular, but they’re far not that simple, elegant and powerful than pure functions. Immutability doesn’t allow to modify anything, reducing possible unwanted side effects.
However, it needs to notice, that this ultra-modularity of FP in couple with immutability leads to some real practical problems. For example storing state in purely functional languages is a quite tricky task (requiring advanced technics like State Monads), because all the functions are considered to be pure, there is no global state, furthermore, you cannot even reassign value to a variable. In this case, imperative languages give a lot more simplicity. Besides that, mutability shows better performance in most of the cases since it’s a nature of computing systems on the low level.

Code re-usage

Inheritance

Inheritance is one of the basic principles of OOP. Everyone uses it, but not everyone understands why. When we’re inheriting a parent class we want to get a child with all the properties of the parent, having some overloaded or new properties. Basically, in the end, we want to be type-safe and make sure that the calling method does exist + trick with reusing code across classes. Haskell programmer is always type-safe, he literally lives in a world of types, and code re-usage always goes through functions composition. When your functions are small and granular, you can build any big and robust abstraction out of them. Sometimes it’s tough to shift the mindset from thinking object-oriented to functions composition, but any actual domain model can be built with any of these approaches just with one difference. Building a really good abstraction with composition you must clearly understand the domain, there is a big risk to get an unordered pile of bad functions. With classes, following poor domain design, you’re going to have just a couple of awkward classes which still will look like some kind of a system.

Polymorphism

Polymorphism is a very powerful way to write general code still being type-safe. So it solves two problems: code re-usage and avoiding of innatantive mistakes.
Polymorphism has many kinds, but the most useful one is probably parametric polymorphism. In Haskell there is “type variables” which are used in type notations and in most of OOP languages it’s called generics. But besides that, Haskell supports also ad-hoc polymorphism, which is in fact Type Classes (please notice that they have absolutely nothing to do with OO classes).
At the same time, there are some kinds of polymorphism that Haskell doesn’t support, or at least not natively, e.g. inclusion polymorphism and subtyping, common in OO languages, where values of one type can act as values of another type. That is just considered as not safe. For example the famous Java unsafety with subtyping in Arrays.

Conclusion

Few words about OO dynamically typed languages. By historical reasons (I guess trying to repeat Java success and improve development speed and flexibility) we have many mainstream languages like this: PHP, Python, Ruby, Perl etc. (I didn’t mention JavaScript as it just emulates OOP, being a functional language by nature).

So from OOP they have code re-usage and classes encapsulation, which, as we found out, is more expressive and powerful in FP. At the same time, they don’t have beauty and safety of interfaces and generics.

It’s going better with functional dynamically typed languages like Erlang, Elixir or Closure, but lack of static types lead them to less safety.

We could conclude that language that would fit for our principles from the beginning of the article at most should be statically typed, functional, but allow to mutate data (since strong immutability causes growing of complexity). However, mainstream languages try to sit on the fence and hand over the responsibility of paradigm choice to programmer’s shoulders supporting both approaches (Rust, Kotlin, Swift. Also many of them introduced lambdas: Ruby, PHP, Python and even Java). However, there are still some languages, which tend to be functional, safe and easy to use: Scala, F#, TypeScript. So I think they deserve respect.

Find me in Twitter

--

--

Roman Krivtsov
Roman Krivtsov

No responses yet