TL blog

Combine functional and object oriented programming

July 28, 2021

Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …[Therefore,] making it easy to read makes it easier to write.

— Robert C. Martin

There are many languages that support both functional and object-oriented programming (OOP) such as Javascript, C#, Scala… In my case, learning functional programming (FP) from OOP experience creates some confusion on how to best use functional and OOP together. Scala language has a few built-in restrictions that helps developers handle functions and objects more easily. On the contrary, Javascript is very flexible, which is why it would be more difficult to write clean and bug-free code.

Functions for logic and objects for modularity

One of the main advantages of FP is providing a clean and concise way to represent business logic. Pure functions are easily tested and organized. Thus in the programming languages that support both FP and OOP, the main mean to tackle business puzzles should primarily be functions.

When the complexity of software grows to a certain level, it is absolutely crucial to structure your functions in a way that most developers can read and understand the codebase with ease. In my opinion, the best tool to do that is OOP. Using OOP with thorought domain knowledge will help you modularize functions effectively and enhance the transparency of business logic.

To better modularize your application, I would argue that functions should mainly be placed in objects (singleton or class-based) especially when they are responsible for domain logic. This simple fact is not quite obvious in Javascript (JS) due to the syntax of export and import of ES6 module. By way of example, in JS one could write:

import defaultExport from "module-name";
import * as name from "module-name";

As we can see, JS not only allows functions to import single functions and objects, but also can import all into namespace object e.g name in the example. This flexibility often makes it harder to modularize functions because developers do not need to think very hard on the suitable files to put their functions. As a consequence, some functions that handle business logic could end up residing in random places. Based on my experience, to tackle the above issue in JS, developers have to learn and understand domain knowledge in details, then they may be able to organize functions in appropriate objects.

Separating domain models and business logic

One feature I am very fond of while working in Scala language is the use of case class. In my view, this feature is the culmination of years of experience in software development. The simple of act of separating domains models from business objects help simplify implementation vastly. Scala’s case class provides simple, yet powerful built-in functions, e.g. copy, equals, apply … for handling and manipulating domain data. Scala developers also get a better sense of case class objects when reading code as they immediately know what those objects are for.

After isolating domain models, what left in business objects are mostly pure functions. This benefits testing greatly as pure functions have less dependencies and can be independently tested. In OOP, it is not unusual to have objects that includes data and methods. However, to make the best use of programming languages like Scala, developers should adhere to the principle of dividing business and pure domain objects.

Javascript, on the other hand, imposes a greater challenge for developers in designing their code. Frontend developers using libraries like React, Vue or Angular for website development, probably have bigger difficulty. The main reason is due to the fact that these libraries heavily make use of components. Those components encapsulate its owns state and to provide public APIs for developers to handle UI component logic. As a result frontend developers tend to mix business logic inside components. This could increase the growth of complexity in large website applications as business logic get scattered in different places. One of the remedies for this situation is separating pure UI components from business components, or in other words, dumb components and smart components. The pure UI components are shared and reused across projects because they are only responsible for rendering and displaying correct UI representations. All logic related to handling data, validations, mappings should be done in business components.

Immutability in pure functions

An important principal in FP that we must strictly follow is immutability of objects. Pure functions ensure the returned value or output is deterministic. Functions without side effects also increase testability of the application.

Unsurprisingly, Scala case class has this principle built inside the language. Properties in case class objects are immutable, i.e developers are unable to change property values of existed objects, they can only create new one.

The reason functions need to be pure and values should be immutable is because functions can be composed and chained together, an example in JS:

const numbers = [2,5,6,8];
const plusOne = (a) => a + 2;
const sum = (acc, current) => acc + current
// first operation
console.log(numbers.map(plusOne).reduce(sum,0));
...
// second operation
console.log(numbers.map(minusTwo).reduce(sum,0))

We see the numbers value is unchange after the first operation which is why we can predict the result of the second operation. Javascript’s let and const are the newly added features of the language, to encourage the usage of immutable value objects.

Final note

Good code structure increases development velocity, reduces the amount of bugs and prevent application growth of complexity. Understanding all aspects of business domain is a prerequisite for well-organized codebase. Thus investing time to learn about the domain you are working on will certainly bring great benefits.

Happy learning :)


Written by Thang Le, who likes to learn new things.