CHATTBLOG

Functional Programming with Javascript

Software Design
image
One of the great things about Javascript is that several programming paradigmes can be used with the language. Unlike most languages that limit themselves to use either Object Oriented programming or Functional programming, with JS both can be done. The OOP paradigm is quite popular and widely used leading to programmers usually believing that functional programming is hard to do or more complex. In this article, lets have a look at the basic definition, concepts and advantages of functional programming techniques.

Declaration vs Imperative programming

The fundamental idea that drives functional programming is the concept of declarative programming. This is the type of programming in which the code conveys what it is trying to do instead of what it is doing in detail. So any code that wraps all the complications into abstractions and executes those abstractions one by one to tell a story. Imperetive style on the other hand goes into implementation details in a verbose manner.
  // Imperetive
  let a = 1;
  let increment = 10;
  for(let c = 0; c < increment; c++) {
         a++;
  }
 
  // Declarative
  function increment(v, i) {
         let value = v;
         for(let c = 0; c < i; c++) {
                 value++;
         }
 
         return value;
  }
 
  const a = increment(1, 10)

Charactaristics of Functional programming

Pure functions

These functions have two main properties. First, they always produce the same output for same arguments irrespective of anything else. Secondly, they have no side-effects i.e. they do not modify any arguments or local/global variables or input/output streams. The pure function are deterministic. Programs done using functional programming are easy to debug because pure functions have no side effects or hidden I/O. Pure functions also make it easier to write parallel/concurrent applications. When the code is written in this style, a smart compiler can do many things – it can parallelize the instructions, wait to evaluate results when needing them, and memorize the results since the results never change as long as the input doesn't change.

Referential transparency

Referential transparency is a property of a function that allows it to be replaced by its equivalent output. In simpler terms, if you call the function a second time with the same arguments, you're guaranteed to get the same returning value.

Functions are First-Class and can be Higher-Order

First-class functions are treated as first-class variable. The first class variables can be passed to functions as parameter, can be returned from functions or stored in data structures. Higher order functions are the functions that take other functions as arguments and they can also return functions. The advantage of the functional programming approach is that it clearly separates the data (i.e., vehicles) from the logic (i.e., the functions filter, map, and reduce). Contrast that with the object-oriented code that blends data and functions in the form of objects with methods.

Variables are Immutable

In functional programming, we can't modify a variable after it has been initialized. We can create new variables – but we can't modify existing variables, and this really helps to maintain state throughout the runtime of a program. Once we create a variable and set its value, we can have full confidence knowing that the value of that variable will never change.
What you cant do:

Loops

while,do...while,for,for...of,for...in,

Variables

var,let,Object mutation

Array mutator methods

fill,pop,push,shift,sort,splice,delete, etc

Map mutator methods

clear,delete,set,shift,sort,splice,delete, etc

Some Functional Concepts

Partial and Currying

Currying: Converts a function with multiple arguments into a sequence of nesting functions, each taking exactly one argument.
Partial Application: Involves creating a function with a smaller number of parameters by pre-filling some of the arguments.
  // example of currying
  function giveMe3(item1, item2, item3) {
         return '
             1: ${item1}
             2: ${item2}
             3: ${item3}
         ';
     }
    
     const giveMe2 = giveMe3.bind(null, 'rock');
     const giveMe1 = giveMe2.bind(null, 'paper');
     const result = giveMe1('scissors');
    
     console.log(result);
Use cases of these concepts
Event handling: Creating partially applied functions that are tailored for specific events but reuse a common handler logic.
API calls: Setting up functions with predefined arguments like API keys or user IDs that can be used repeatedly across different calls.

Function composition

Function composition is an impotant functional programmingn concept that allows several function to be combined together in a manner where the resultant code can be named and reused in a clear and declartive manner.
  const add = (x, y) => x + y;
  const square = (x) => x * x;
 
  function compose(...functions) {
     return (input) => functions.reduceRight((acc, fn) => fn(acc), input);
  }
 
  const addAndSquare = compose(square, add);
 
  console.log(addAndSquare(3, 4)); // 49

Memoization

Memoization is an optimization technique used in functional programming to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. It is particularly useful in JavaScript for optimizing performance in applications involving heavy computational tasks.
Efficiency: Reduces the number of computations needed for repeated function calls with the same arguments.
Performance: Improves application responsiveness by caching results of time-consuming operations.
Scalability: Helps manage larger datasets or more complex algorithms by minimizing the computational overhead.
  function memoize(fn) {
         const cache = {};
         return function(...args) {
                 const key = args.toString();
                 if (!cache[key]) {
                         cache[key] = fn.apply(this, args);
                 }
                 return cache[key];
         };
  }
 
  const factorial = memoize(function(x) {
         if (x === 0) {
                 return 1;
         } else {
                 return x * factorial(x - 1);
         }
  });
 
  console.log(factorial(5)); // Calculates and caches the result
  console.log(factorial(5)); // Returns the cached result

Monads

Monads are a type of abstract data type used in functional programming to handle side effects while maintaining pure functional principles. They encapsulate behavior and logic in a flexible, chainable structure, allowing for sequential operations while keeping functions pure.
Monads are a more advanced topic in functional programming, but they provide a structured way to handle side effects while adhering to functional principles. Promises are a familiar example of monads in JavaScript. They allow you to work with asynchronous operations in a functional way, chaining operations without directly dealing with callbacks or managing state.
A monad is a way of composing functions that require context in addition to the return value, such as computation, branching, or I/O. Monads type lift, flatten and map so that the types line up for lifting functions a => M(b), making them composable. It's a mapping from some type a to some type b along with some computational context, hidden in the implementation details of lift, flatten, and map.
  function fetchData(url) {
         return fetch(url).then((response) => response.json());
  }
    
  fetchData('https://api.example.com/data')
         .then((data) => {
                 // Process data
                 return data.map(item => item.name);
         })
         .then((processedData) => {
                 // Use processed data
                 console.log(processedData);
         })
         .catch((error) => {
                 console.error(error);
         });
Charactaristis of Monads
Identity: Applying a function directly or passing it through the monad should yield the same result.
Associativity: The order in which operations are performed (chained) does not affect the result.
Unit: A value must be able to be lifted into a monad without altering its behavior.

Pros and Cons

Pros

Pure functions are easier to understand because they don't change any states and depend only on the input given to them. Whatever output they produce is the return value they give. Their function signature gives all the information about them i.e. their return type and their arguments.
The ability of functional programming languages to treat functions as values and pass them to functions as parameters make the code more readable and easily understandable.
Testing and debugging is easier. Since pure functions take only arguments and produce output, they don't produce any changes don't take input or produce some hidden output. They use immutable values, so it becomes easier to check some problems in programs written uses pure functions.
It is used to implement concurrency/parallelism because pure functions don't change variables or any other data outside of it.

Cons

Sometimes writing pure functions can reduce the readability of code.
Writing programs in recursive style instead of using loops can be bit intimidating. Writing pure functions are easy but combining them with the rest of the application and I/O operations is a difficult task.
Immutable values and recursion can lead to decrease in performance.
author image

Sujoy Chatterjee

Author