Functional Programming — Writing Code Declaratively

January 9, 2025

☕️ Support Us
Your support will help us to continue to provide quality content.👉 Buy Me a Coffee

Functional programming is a widely popular programming approach in both frontend and backend development in recent years. For example, React, the most popular frontend library in the community, makes extensive use of functional programming concepts. Therefore, whether you are a frontend or backend engineer, understanding functional programming will improve your comprehension of writing code.

Consequently, we will have several articles discussing functional programming to help readers understand this paradigm and how to apply it at work. In this article, we'll start by exploring some fundamental concepts of functional programming.

Writing Code Declaratively

Functional programming, as its name suggests, is writing programs using functions (in contrast to object-oriented programming, which centers on objects). One distinctive feature of function-oriented programming is that it allows developers to write code declaratively.

What does this mean? Let's explain with a concrete example.

Suppose we want to multiply each number in an array by 2 and put it into a new array. Generally, we might write it like this. In the code below, we have a for loop that iterates over the original numbers array, multiplies each element by 2, and adds it to the doubled array.

const doubled = [];
for (let i = 0; i < numbers.length; i++) {
  doubled.push(numbers[i] * 2);
}

But if we use a function-oriented approach, we can use the map method that most programming languages have built-in and rewrite it like this:

const doubled = numbers.map((x) => x * 2);

The two programming approaches above are called imperative and declarative, respectively.

The first imperative approach using for is like giving different instructions, focusing on how to execute, so it details every step and detail. The benefit of this is that the programmer has control over every detail. However, someone reading the code needs to go through it completely to understand what it does.

The declarative approach using map, on the other hand, focuses on what you want and hides implementation details, making the code more concise and readable. With map, the implementation details of iterating through the array are hidden, allowing the reader to focus on the core logic (in the above case, x * 2).

With a more complex example, we can clearly see the difference between the two approaches. Suppose we have the following e-commerce shopping cart data:

const cartItems = [
  { id: 1, name: "iPhone", price: 30000, quantity: 1, inStock: true },
  { id: 2, name: "AirPods", price: 5000, quantity: 2, inStock: true },
  { id: 3, name: "Charger", price: 900, quantity: 3, inStock: false },
  { id: 4, name: "Phone Case", price: 1500, quantity: 1, inStock: true },
];

The following code might not be immediately clear about what it does:

const processCartImperative = (items) => {
  const result = {
    items: [],
    totalQuantity: 0,
    totalAmount: 0,
  };

  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    if (item.inStock) {
      const subtotal = item.price * item.quantity;

      result.items.push({
        name: item.name,
        subtotal: subtotal,
        quantity: item.quantity,
      });

      result.totalQuantity += item.quantity;
      result.totalAmount += subtotal;
    }
  }
  return result;
};

But if we write the same code using functional programming, it becomes immediately clear. You can see that we first filter items in stock, then calculate the subtotal for each item, and finally output the cart items, total quantity, and total amount:

const processCartFunctional = (items) => {
  const inStockItems = items
    .filter((item) => item.inStock)
    .map((item) => ({
      name: item.name,
      subtotal: item.price * item.quantity,
      quantity: item.quantity,
    }));

  return {
    items: inStockItems,
    totalQuantity: inStockItems.reduce((sum, item) => sum + item.quantity, 0),
    totalAmount: inStockItems.reduce((sum, item) => sum + item.subtotal, 0),
  };
};

Through the examples above, we hope you can see the benefits of functional programming's declarative approach for code readability and maintainability. Of course, declarative style isn't always appropriate. For example, in scenarios where precise control over memory or performance is needed, imperative code might be more suitable. But when focusing on business logic, this approach is particularly fitting.

☕️ Support Us
Your support will help us to continue to provide quality content.👉 Buy Me a Coffee