From OOP to Functional Style (FP) — A JavaScript Pattern Progression

Mushrooms

JavaScript is under the hood a rather different language than many other common programming languages, but it is perhaps not obvious at a first glance. With roots in the Lisp-like language Scheme — functions stand in the very center of how JavaScript operates. The function is in JS what is called a “first-rate citizen”, which means it can be assigned, returned and passed around like any other value.

All functions has always been “lambda functions” in JavaScript.

With the upgrades of the language that started in 2015, with ES6 and succeeding versions, the class syntax was added. Since it took some time for all JS engines to implement native support for the class syntax, such code could simply be transpiled into old ES5 JavaScript — because the language already had the constructor pattern. Some argued that adding the class syntax was bad rather than good because it would only widen the gap between programmers and the actual language.

Classes was added because OOP, Object Oriented Programming, still is the predominant programming paradigm. With its 3 pillars — inheritance, encapsulation and polymorphism — one could divide and conquer every system or problem, it seemed. But OOP has big intrinsic issues and has gotten more and more criticized lately, where at the same time there has been a noticeable swing towards functional programming, FP. But how do we transition from thinking object oriented into thinking pure functions, immutability and separation of functionality & data?

In this article I will evolve a simple class with both private, public, and read-only variables (OOP), through a few other object patterns and finally over to the world of functional programming — with pure functions and immutability. This is a JavaScript guide, but also a much more general guide for understanding how OOP and FP relates to each other and how to transition our thinking — and JavaScript will be the perfect vehicle to show such a transition, since it now has the class syntax and is great for FP.

For every programming pattern shown, between OOP and FP, we’ll discuss the differences to the previous ones and relate it to a few important programming/developmental measurements (sometimes called “non-functional requirements”):

  • Reuseability: How easy it is to keep things DRY and generic, in order to reuse good functionality.
  • Extendability: How easy it is to add functionality to the code.
  • Testability: How easy it is to write tests for the code.

One of the most common examples to use when teaching patterns or frameworks in the realm of the web is the “TODO app”. However, I want to use something that is more general, has some computational parts and a simple data model including read-only members as well as writable.

Circle Objects

Big metal cylinder in the woods

Let’s imagine that we want to handle round 2D objects that we call Circles. Let’s define a couple of properties and operations that fit the concept of such objects:

  • r: Radius of the circle. This can never be changed (because then it is not the same circle anymore!).
  • pos: Position in a 2D world — an array with 2 elements/coordinates. Freely changeable.
  • hits: How many times this circle has been calculated to intersect another circle. This is internal state, not freely changeable.
  • area(): Get the area of a circle.
  • intersect(): Calculate if a circle intersects another, and update both hits if they do.

Class

We start in OOP land by implementing this circle concept as a class:

First, in the constructor, we need to transfer the incoming arguments over to the class variables using this. Since we want to control the access of the different members in the class, we can use so called getters and setters. The intersect method is the most intricate one where a distance is calculated, compared to circle radii and then the hits of the two objects are increased.

Now, we need some test that uses the above class, and see if everything we hoped for holds true:

The 2 circles in the example

It seems to work as intended. Trying to set hits or r will throw errors. Thus we have contained and protected the data inside the objects created through this class so that unintended behaviors are avoided in surrounding code.

What can be said about the class pattern?

The best way to work with classes is to recognize the better alternatives and use those instead, like objects or factories.

  • Reuseability: The whole class can be used in different contexts, if it perfectly fits the purpose. The reuseability will mainly be determined by how generic of a component we made as a class. If the class is not generic and versatile, the reuseability is very limited. If there are interesting general functionality inside our class, that is not easily reused either. For instance, to reuse the mathematical function of calculating the area of a circle without having to create a class instance, we would have to import the whole Circle and then do: Circle.prototype.area.bind({ r: 4 })() Not nice.
  • Extendability: One common way of course is to grow the class. This might break the rule of doing only one thing. It will almost by definition worsen the previous point above, but also the next way that a class can be extended: by inheritance. When inheriting you can’t pick and choose, so large non-generic classes are very clumsy to work with. With class hierarchies we also risk ending up in the banana-jungle problem. It has also been recognized that it’s a bad practice in JavaScript to directly export a class from a module, since it limits how the module can evolve and be updated/refactored (the factory pattern is much better). More on this later.
  • Testability: Testing can be done by instantiating the class, running its methods, and trying to check the effects. This can turn out to be a bit tricky in cases of large classes or classes including communication or other outside dependencies. We could be forced to change the internals of the class only to be able to mock dependencies or reach inside to check tested effects.

The second best way to work with classes is to try to only work with composition and to try to keep as much code as possible outside classes — all functions should reference (pure) functions outside the class. The best way to work with classes is to recognize the better alternatives and use those instead, like objects or factories.

Constructor

Red submarine

In JavaScript, we have already had the ability to create objects with the new keyword, just like with classes, even before the introduction of the class syntax.

This is how the Circle class could look using the constructor pattern:

But now you say:

That is not correspondent to the class example! We need to use the prototype, and we could still use getters and setters!

Yes, but this guide is about patterns and a direction, and each version will not always equal the other. The main reason to even include the constructor pattern is that it is an interesting bridge to the factory.

The constructor is just a function — where inside, you could attach values and methods to this. this is the function object that gets instantiated when you call it with the new keyword. By simply keeping private variables (let or const) in the function scope, we easily create and handle private unexposed constants and states, and we can do so because of JavaScript’s closure (not in the scope of this article). By creating our own getters and setters as methods, we expose an API that will define what’s private, read-only, writable etc.

The small difference in the usage of objects created with this constructor, compared to the class, is that we will have to call our API functions instead of directly accessing and setting properties:

Because of that the new class syntax has gotten extra features, this old equivalent pattern does not give the exact same API of using the instantiated objects. But it is much closer to the core language, and it is an interesting demonstration of how closure works in JavaScript. The difference regarding our developmental measurements is not that significant, but there might be some comments worth giving:

  • Reuseability: Our code is still ensnared inside a construct, a function body, but it is perhaps a bit more apparent that just attaching arrow functions to properties of an object opens up the possibility of using external generic functions to define the guts of the object constructor. If we would lift out interesting parts of the code into reusable generic utilities (namely think more in line with functional programming), we would get a higher score here. It is of course possible to do this in the class pattern as well, but the class syntax somewhat hides rather than highlights this.
  • Extendability: We are still forcing external code to use new which blocks patching in certain updates or refactoring our module and instead making such changes breaking.
  • Testability: Not essentially different from the class pattern, although adding testing purpose adaptations inside the module might look different.

So, looking at the fact that what the constructor is doing is just tie functions and values to its own properties, through this — what if we replace this with just another object?

Factory

Factory buildings on a waterfall

Below is one way to make our circle objects with a factory.

The first thing to notice here is that this is just a function, not any class, constructor, interface or other “object-descriptive” abstract construct (indicated by the lowercase first letter of makeCirle). The function, when called, will simply create a regular object with a set of properties (const obj = {...}) and return it. We are still using closure to keep hits & r private. To clarify: new could not and should not be used when using a factory.

Factories in JavaScript can sometimes take another object to be set as the new object’s prototype (using Object.create), called prototypal inheritance. But regarding trying to use the prototype as much as possible, like that or using classes, the factual penalty of creating new objects with own properties is getting smaller and smaller with increased device performance and more optimized JS engines. It is indeed nice that it is nowadays recommended to simply create a basic object every time we need one, and optimize later should you actually find your object creation being a bottle-neck. This way we can actually skip using prototypal inheritance, or any prototype juggling for that matter. It is good to know about it, to read and work on others code, but we can build great JavaScript software without ever having to take any prototype into account explicitly.

  • Reuseability: We explicitly use JavaScript’s closure instead of implicit this which even more opens up for defining and building objects in our factory using external reused code. More on this below (pun intended). But we are still not doing this yet, so our potentially generic code is still trapped.
  • Extendability: The major difference here is getting rid of new for callers. This allows us to create and return things more freely. Examples would be the possibility of keeping objects in some kind of pool or cache. Or to return a tuple/pair (array) with not only the object but also perhaps a callback or any other related construct or value (compare for example with the React hook useState). On that note, we could consider returning an error-first pair if the object creation could go wrong. We also no longer pretend to give any type guarantee, helping surrounding code avoid the use of any unreliable type checking, like the “broken” instanceof. As we inside the factory simply add more and more properties to an object, there would be nothing stopping us from continuing to attach functionality to the object after we have returned it from the factory. This idea would point in the direction of functional mixins which is a powerful higher order pattern, but a subject outside the scope of this article. A more straightforward general approach is again to work with composition (when something is made up of other parts, like in React), and also — since we should strive to mainly use pure functionsfunctional composition (e.g. using compose in Ramda).
  • Testability: A rather significant difference has again to do with unlocking the shackle we were chained with through new. If we need to provide something extra behind the scenes other than the actual object, for testing purposes — in a factory we are free to do such things.

A thing worth expressing explicitly is that we no longer need the keyword this in the factory pattern. The workings of that keyword can be rather confusing in JavaScript at times and therefore a source of bugs. It is actually a good rule to never use this, it will help in moving over to the functional paradigm. Removing this will namely also remove classes, constructors, binding, and juggling around different this in some that or self as seen here and there.

Functional Style

A very blue bedroom

It is a really good general idea in programming to always start with the simplest implementation first and move to more complex patterns only as needed. This means pure functions as the first approach, and a class would actually be the last resort (if you really ever need one).

If you’re new to the concept of pure functions, they are basically defined by (1) having no side effects and (2) their return value only depends on the given input arguments.

Using mainly pure functions leads to the separation of operations and data — the perhaps most apparent structural aspect of Functional Programming that differs heavily from Object Oriented Programming where those 2 are tightly coupled.

Another consequence of pure functions is that we will not be mutating any data — everything in FP land is going to be immutable (at least treated as). (There are exceptions, but being firm with this rule in the beginning is going to help in shifting our way of thinking into functional style, and reap the benefits).

Armed with the concept and understanding of pure functions, let’s start over and try to code our Circle objects again from scratch. We first write a pair of helpers for doing immutable updates followed by our circle computations — all pure:

Since this looks so different from the previous patterns — some general comments about functional style:

  • As a general rule, we separate computations from data.
  • Instead of functions most often being bound inside objects, we make sets of self standing functions that can operate on different objects or data.
  • The functions do not mutate any data or have any other side effects (1st rule of pure functions).
  • The functions don’t have any outside scope or context, but instead operate only on the incoming arguments (2nd rule of pure functions).
  • Since we want our data to get updated in certain ways, though being treated as immutable, we include in our set of pure functions a couple of helpers for making those updates, using ES6 spread operator (incHits & setPos above).
  • Always using arrow functions can be a good habit since all simple functions then become more declarative (no imperative body) and it also makes it easier to refactor a function to point-free style (e.g. made through composition).
  • ES6 destructuring provides a way to make a form of pattern matching on the incoming argument(s), unpacking only exactly what we need.

Notice how our intersect function now returns new updated copies of the incoming objects.

Using the above code directly in practice means holding and managing the data, since that is now outside of the above functionality. And so far we don’t have any way of enforcing immutability, so mutating our circles is unfortunately possible (c1.hits = 4; below). But if we by convention (or by some immutability enforcement, discussed later) never mutate our state/circles then shared state will not mutate(!), demonstrated by the c2ref below.

The state, our circles, are just serializable objects — nothing fancy, just pure data. And they need to be replaced by new updated clones at every step that should alter any state. If we store and use a reference to a state, it will be the state at the point in time when it was taken as a reference, that object will never change. This means that we have eliminated shared state mutation, which is a good thing.

But what the heck is this kind of syntax: [hit, c1, c2] = intersect(c1, c2);? — It is just destructuring again but for an array and into existing variables created with let. We are just matching the output of intersect and directly using it in assignments.

  • Reuseability: We are now in a completely new world regarding this. We can easily see that area() and circlesIntersect() are so general we could probably find them in a common library instead of defining them ourselves. Otherwise we now have the start of our own general geometry module, to use in any future project that needs to handle circular objects (game engine?).
  • Extendability: Extending our code is now simply a matter of importing precisely the functions we need and use them in any library, class, factory, object, prototype or any other construct. If we have tree-shaking in our module/build system, we won’t even get the functions we don’t import into our final code.
  • Testability: There is basically nothing more simple to test than pure functions. Depending on what you give the function, it returns something based on that. With no side effects, we won’t have any problem examining any result of calling such functions, and we don’t have to mock anything.

Functional Style Factory

A view near the sea

I have been repeating in the previous patterns that functions and calculation should be extracted out into generic utilities, striving to make our code in general more reusable, DRY and testable. Well, now we come from the complete opposite direction and could instead easily wrap the keeping of a state together with the use of our pure functions from above, into a factory:

We have kind of gone full circle now, haven’t we? We have a factory which only job is to be stateful (keep a state over time), and otherwise “tunnel” calls to external utilities. Using this factory is almost exactly the same as with the previous factory pattern above, so I won’t repeat that code (the only difference is c1.getState().hits instead of c1.getHits() although we could of course make a getHits function in this factory as well).

Regarding reuseability, extendability and testability — this factory has all the benefits of the previous factory example together with some of the benefits of the above functional style pattern.

What can be noticed however is that we introduce some lines of logic due to the nature of intersect, namely that such operation might not be at the right level here — it needs to affect multiple circles. Could we find a more powerful way to handle state, as a collection?

It’s common in JavaScript that mutations are avoided by convention, using certain patterns (like ES6 spread), or some of the very many libraries, where some interesting examples are:

There are however ways to block mutation of state, where perhaps most notably Immer is a good example. A much simpler one is Icepick. It is still more common for immutability to be maintained by convention with structural patterns and with the help of linting (static code analysis).

Since I don’t want to mix in and favor any library in this guide , I would instead like to show you that we can stay in native JavaScript and pretty easily build a tiny bit of code to get a Redux-like immutable state handler. This below state manager is however not a suggested piece of code to use in a project. You should evaluate some of the libraries above or any other state management package with an immutable style and use that. (My personal favourite is Ramda, in combination with Redux or XState for bigger applications.)

With that said — let’s see how a small piece of native JavaScript can combine enforced immutability and a tiny Redux-like state handler. This will give us a store that updates by dispatching actions through a reducer.

Functional Style with Immutable Store

Lots of people on a small boat

First we make use of the Object.freeze method and write a helper for freezing (making immutable) complex data structures, deepFreeze. Then we also define a super-simple Redux-like store creator, createStore:

deepFreeze is a recursive function where if the incoming data is a non-primitive that is not already frozen, we deepFreeze it. Our createStore function takes an initialState and a so called reducer that can, given a state, return a new altered state according to “actions”. createStore then returns a way to dispatch actions (send action objects to our reducer) and a getState to, at any point in time, get hold of our current state (which can not be mutated due to it being frozen).

An interesting reflection on createStore is that it is actually very similar to our “Functional Style Factory” wrapper function above, but you could call this one a generalized factory wrapper. They both keep a state locally and privately, and they both return an object with methods both for retrieving state and for making state updates. Even without deepFreeze using only dispatch to alter state ensures immutability.

The reducer we need for our circles is something like this (using again our previous update helpers setPos and incHits):

An action is an object with a type that matches a defined one, and arguments in some way that fits each action (a common convention is to always use payload, but I’m free-styling a bit above). Yes, I know, usually switch statements are used in reducers, but I have a stupid personal dislike to them and often prefer alternative patterns, for example the strategy pattern.

Now we can use the above 3 new functions in combination, in order to get our immutable circles example:

With our reducer and an initialState, createStore gives us getState that we call whenever we want to use parts of our state, and dispatch that we call whenever we want to change our state. With our built in deepFreeze we block any direct attempt to mutate our state.

On the usage side of a pattern like this we haven’t reduced the lines of code, rather the opposite. But this kind of pattern is meant instead to keep things streamlined and less error prone as projects grow.

Software architect and developer with web and functional JavaScript close to his heart. Worked many years with web solutions mainly within the IoT field.