DEV Community

Caio Borghi
Caio Borghi

Posted on • Edited on

The Philosophy of OOP and FP with JavaScript

I'm not a philosopher, but I sometimes enjoy understanding the whys more than the hows, especially when it comes to theoretical programming.

OOP: The "What Entity" paradigm

Looking at the world through the lens of entities or, to be more precise, objects.

What is OOP?

Object-Oriented Programming (OOP) is structured around objects—data structures that encapsulate properties and behavior.

Properties are self-explanatory (you'll understand better with the example), while the behavior of the program is described by methods.

OOP communicates in a clear, imperative manner - each method is a directive, an action to perform something.

The first task in OOP is identifying the objects.

These objects have properties that act like drawers; they store sockets which, in this context, refer to data.

In JavaScript, this data can be anything: socks, t-shirts, hats, gloves, television cables, televisions, desks, chairs, houses, cities, planets, creatures, and any tangible or fictional objects.

All these items can be stored as variables, or in the terminology of OOP, classes.

For instance, consider this Drawer object in JavaScript:

class Drawer {
  // Furniture!
  // Properties of the Drawer are declared within the constructor.
  constructor() {
    this.socks = []; 
    // It is also possible to store multiple properties, or perhaps, a reference to another class.
  }

  addSock(sock) {
    // Class properties are accessed using the keyword `this`
    this.socks.push(sock);
  }

  countSocks() {
    return this.socks.length;
  }
}

Enter fullscreen mode Exit fullscreen mode

The Drawer represents an entity, binding related properties (socks) and behaviors (methods addSock and countSocks) together.

How does it work?

In OOP, you create an instance of your class (instantiate an object) and interact with it using the defined methods.

Here's how you'd use the Drawer:

  const myDrawer = new Drawer();
  myDrawer.addSock('red');
  myDrawer.addSock('blue');
  console.log(myDrawer.countSocks()); // Output: 2

Enter fullscreen mode Exit fullscreen mode

Why use it?

The structure OOP provides makes it really easy and beginner-friendly to represent objects inside the code.

You figure out real-world entities, define what they do (through methods), and then write it into your program.

With this real-world mirroring, OOP helps you apply your everyday knowledge to your programming. You deal with objects all the time in real life.

Using them in your code just feels natural!

Functional Programming: "The Workflow" Paradigm

The beauty of working with Functional Programming (FP) is that you're less focused on entities. Instead, you want to describe the workflow/process.

What is it?

FP is about breaking each process into small steps—the shorter, the better—that combined, solve your problem.

FP views a program as a series of transformations applied to data. It values pure functions with single responsibilities (no side effects), immutability, and first-class functions.

In FP, steps are seen as data transformations.

Let's see how we could handle our 'socks in drawer' problem with FP:

let drawer = [];

const addSock = (drawer, sock) => [...drawer, sock];

const countSocks = (drawer) => drawer.length;

drawer = addSock(drawer, 'red');
drawer = addSock(drawer, 'blue');
console.log(countSocks(drawer)); // Output: 2
Enter fullscreen mode Exit fullscreen mode

These steps show how we can transform our data (drawer and socks) to solve the problem. We start with an empty drawer, add socks to it, and then count them. Each step is a small transformation that brings us closer to the solution.

How does it work?

In FP, data and actions are separate. It's like cooking:

  • Your raw ingredients are your data 🥚🥛🌾
  • Your cooking process represents the actions 🍳
  • You feed your raw ingredients (data) into your cooking process (functions)
  • These functions output a new dish (new data), but your raw ingredients (data) are still raw. They're not modified!
let rawIngredients = ['eggs', 'flour', 'milk'];
let cookPancakes = (ingredients) => { /*...process...*/ return 'pancakes'; }
let pancakes = cookPancakes(rawIngredients);
// You now have pancakes, but your rawIngredients are still ['eggs', 'flour', 'milk']
Enter fullscreen mode Exit fullscreen mode

Why use it?

It makes your code predictable and easy to test, by working with deterministic functions, there's less room for unexpected behavior.

By keeping your functions small, you guarantee that each method solves one single problem and the solution is achieved by chaining multiple steps.

Other benefits of using are:

  • Predictability: FP is like a well-written recipe. You can predict the outcome, given the same set of ingredients (data) every time. It eliminates surprises in your code 📖

  • Ease of testing: In FP, you can test each function (or step) separately, just like testing how well your egg-beating technique works before you proceed to the next cooking step. This isolation makes your code easy to test and debug 🥚➡️🍳

  • Reliability: For projects where high reliability is required, FP shines. It reduces the chances of unexpected bugs, making it a dependable choice for crucial projects 👍

  • Suitability for modern problems: Like using the right utensil for the right dish, FP provides the right tools to handle data-centric issues in programming. By focusing on the workflow, data transformations (not the data itself), you ensure your code is more concise and less propense to bugs.

JS Flexibility

With JavaScript, both approaches are valid, although I would recommend FP over OOP, as I think it suits more with the language's characteristics.

Why I recommend FP for JavaScript

Don't get me wrong, I absolutely appreciate the OOP's mind model, SOLID principles, hierarchy and polymorphism.

In fact, throughout my career, I've primarily written OOP code in in C#, .NET, Flutter, Xamarin Native, C and Java.

However, as I dove deeper into the world of JavaScript, I found myself increasingly drawn towards Functional Programming.

This transition wasn't motivated by hype.

Instead, it originated from a genuine sense of joy while crafting Functional Programming code.

Yet, this wasn't solely driven by personal satisfaction. finding more joy in writing Functional code compared to OOP, there are three concrete technical points that solidified my preference.

State Management

Usually in a JavaScript web application you need to handle local or global state.

Relying solely on OOP principles to handle state may introduce some challenges, specially when it comes to tracking changes.

FP advocates for immutability, the concept of data not being changed, but always replaced, once created.

This eliminates bugs arising from unexpected data mutation, leading to a more robust and predictable application.

Furthermore, with FP's preference for pure functions, tracking changes becomes transparent and intuitive, as each function's output is solely dependent on its input.

// Instead of mutating state directly,
// FP creates a new copy with desired changes
const newState = Object.assign({}, oldState, {updatedProp: newValue});

// A pure function in FP - given the same input, the output is always the same
const square = num => num * num;
Enter fullscreen mode Exit fullscreen mode

Data Transformations

JavaScript often manipulates DOM or AJAX responses. FP excels here, treating these operations as a series of data transformations, which can be very handy when you're working with some Reactive Framework.

// Mapping data to DOM elements
const elements = data.map(item => `<span key={item.id}>${item.name}</span>`);
Enter fullscreen mode Exit fullscreen mode

The map function is a pure, deterministic function.

It receives a callback function (often an arrow function, as used here) as its parameter and consistently returns a new array, leaving the original array unchanged, applying the FP concept of Immutability.

Reactive Frameworks

Reactive frameworks like React, Vue, or Svelte, embrace the reactive programming philosophy.

At the heart of this philosophy is writing code that springs into action upon state changes - like having a bingo card and looking for all selected numbers.

Whenever the selected numbers list changes, you'll "execute" a "reactive" behavior, that is:

  • Analyze your card
  • If the new selected number is present on your card, circle it
  • If not, do nothing

This behavior will react to the state update of the selected numbers of the bingo.

Summarizing

Why objects?

Because it provides a clear and intuitive way to represent real-world behaviors and relationships by encapsulating them within recognizable objects.

By creating these virtual representations of real-world entities, we can easily conceptualize, organize, and manipulate our code in a manner that reflects reality.

Why steps?

The core philosophy of Functional Programming is to simplify complex problems by breaking them down into manageable pieces.

If you're struggling to solve a problem, break it into smaller tasks.

If the problem persists, break it down further until you have a specific question that can be answered through online searches or tools like ChatGPT.

This approach empowers beginners and experienced programmers to tackle complex problems by progressively addressing smaller, more comprehensible components.

By breaking down challenges into manageable tasks, you gain a clearer understanding of the overall problem and find solutions more easily while still keeping code readability.

At the end of the day, you'll end up creating solution pipelines that guide the data hand-in-hand through the problems and requirements of your application.

Top comments (5)

Collapse
 
bias profile image
Tobias Nickel

i think the drawer class need a comment that it is furniture and not an artist.

Collapse
 
ocodista profile image
Caio Borghi

Thank you Tobias, you're right.

As a non-native English speaker I let that one pass, adding it now.

Collapse
 
efpage profile image
Eckehard

In my impression, Classes work like a "mini-program" in a context, that is insulated from the rest of the code. This makes it easier to reuse this part of your code in another context (or another program).

So, why not follow the principles of FP inside a class?

Collapse
 
ocodista profile image
Caio Borghi

Indeed, classes in object-oriented programming (OOP) act as a means of bundling data and behavior, which facilitates code reuse. Functional programming (FP), on the other hand, focuses on immutability and stateless functions.

However, applying FP principles strictly within an OOP context might be inefficient. For instance, if you followed FP principles and returned a new object with each operation, memory allocation would be a major concern. It could result in performance decreases and increased memory usage, especially for frequent operations or large objects.

Furthermore, the concept of object identity in OOP is vital. If you create a new object each time state changes, it could lead to confusion regarding object identity. Therefore, while incorporating some FP principles into OOP can be beneficial, it's important to consider the specific problem context and requirements.

On the other hand, FP excels at creating reusable, composable, and testable functions, especially when data transformations are prevalent. Using higher-order functions, code can be more easily modularized and reused.

Collapse
 
efpage profile image
Info Comment hidden by post author - thread only accessible via permalink
Eckehard

What you describe is using objects inside FP, which is surely not useful. Objects are all about encapsulating states AND behavoir, so creating new objects with each operation makes objects completely useless.

But if you think of a class like a little, independent application, each object works like a "microservice". Inside a class you often use good old procedural spaghetti code, featuring class-global variables. So, why not apply the principles of FP INSIDE a class, using pure functions and and immutable states? Even OOP classes could benefit from being built from testable functions instead of unreliable spaghetti.

Some comments have been hidden by the post's author - find out more