Functional Programming with Functors, Monads, and Promises
Published: 2024-04-18 21:51:25

Introduction:
Functional programming is a paradigm that emphasizes the use of immutable data and pure functions. It provides a set of powerful tools and concepts for writing clean, concise, and composable code. In this article, we will explore three fundamental concepts in functional programming: functors, monads, and promises. We will explain what they are and provide code examples for each of them.

Functors:
Functors are objects or data types on which mapping can be applied. They allow us to apply a function to the values they contain while maintaining the structure. In functional programming, the map function is commonly associated with functors.
Code Example:

```
// Array Functor
const products = [
  { name: 'T-shirt', price: 20 },
  { name: 'Pants', price: 50 },
  { name: 'Shoes', price: 80 }
];

const prices = products.map((product) => product.price);
const totalPrice = prices.reduce((total, price) => total + price, 0);
console.log(totalPrice);
// Output: 150
```
In this example, we use the Array functor and the map function to apply a function that extracts the price of each product in the list. Then, we use the reduce function to sum up all the prices and obtain the total price. The result is 150, which is the sum of the prices of all products in the list.


Monads:
Monads are a higher-level abstraction that extends the concept of functors. They allow us to chain operations while handling possible side effects. Monads provide methods like flatMap or bind to compose computations sequentially.
Code Example:

```
// Maybe Monad
const Maybe = (value) => ({
  map: (fn) => (value ? Maybe(fn(value)) : Maybe(null)),
  flatMap: (fn) => (value ? fn(value) : Maybe(null)),
  value,
});

// Function to get a user from the database
const getUser = (id) => {
  // Simulating user retrieval
  const database = {
    1: { id: 1, name: 'John Doe', age: 30 },
    2: { id: 2, name: 'Jane Smith', age: 25 },
  };

  const user = database[id];
  return Maybe(user);
};

const user = getUser(1);
const uppercasedName = user
  .flatMap((u) => Maybe(u.name))
  .map((name) => name.toUpperCase())
  .value;

console.log(uppercasedName);
// Output: "JOHN DOE"
```
In this example, we use the Maybe monad to handle possible null values when retrieving a user from the database. The getUser function returns a Maybe monad that contains the user if it exists or a null value if it doesn't.

Then, we chain multiple operations using the flatMap method to get the user's name and the map method to convert it to uppercase. The beauty of using monads is that we can safely chain these operations without worrying about null value cases.

In the end, we get the user's name in uppercase as a result. If the user doesn't exist in the database, the result would be null.


Promises:
Promises are a mechanism for handling asynchronous operations in a functional manner. They represent the eventual completion (or failure) of an asynchronous operation and provide a way to chain asynchronous actions.
Code Example:

```
// Function simulating an asynchronous operation
const asyncOperation = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = Math.random() >= 0.5; // Simulating success or failure
      if (result) {
        resolve('Operation successful');
      } else {
        reject('Error: Operation failed');
      }
    }, 2000);
  });
};

// Calling the asynchronous operation
asyncOperation()
  .then((message) => {
    console.log(message);
  })
  .catch((error) => {
    console.error(error);
  });
```

In this example, the asyncOperation function simulates an operation that completes after 2 seconds. The operation has a random result: a 50% chance of success and a 50% chance of failure.

We use a promise to wrap the asynchronous operation. Inside the promise, we resolve the promise with a success message if the result is true, or reject it with an error message if the result is false.

Then, we call the asynchronous operation using the then and catch syntax of promises. If the operation is successful, then will execute and print the success message to the console. If the operation fails, catch will execute and print the error message to the console.


Conclusion:
Functors, monads, and promises are essential concepts in functional programming. Functors allow us to apply functions to values while maintaining their structure. Monads extend functors by providing a way to chain computations and handle side effects. Promises allow us to handle asynchronous operations in a functional manner. By understanding these concepts and using them in our code, we can write more expressive, modular, and maintainable functional programs.