How JavaScript Works: Functional Style and Comparison

2021-06-106 min read

How JavaScript Works: Functional Style and Comparison

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. In this article, we'll explore functional programming in JavaScript and compare it with other programming paradigms.

Core Concepts of Functional Programming

1. Pure Functions

javascript
// Impure function
let total = 0;
function addToTotal(amount) {
  total += amount;
  return total;
}

// Pure function
function add(a, b) {
  return a + b;
}

2. Immutability

javascript
// Mutable approach
const user = { name: 'John', age: 30 };
user.age = 31;

// Immutable approach
const updatedUser = { ...user, age: 31 };

3. Function Composition

javascript
const compose = (...fns) => 
  fns.reduce((f, g) => (...args) => f(g(...args)));

const addOne = x => x + 1;
const double = x => x * 2;
const addOneAndDouble = compose(double, addOne);

console.log(addOneAndDouble(3)); // 8

Functional Programming Techniques

1. Higher-Order Functions

javascript
// Map
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(x => x * 2);

// Filter
const evenNumbers = numbers.filter(x => x % 2 === 0);

// Reduce
const sum = numbers.reduce((acc, curr) => acc + curr, 0);

2. Currying

javascript
const curry = (fn) => {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return (...moreArgs) => curried.apply(this, args.concat(moreArgs));
  };
};

const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6

3. Function Pipelines

javascript
const pipe = (...fns) => 
  fns.reduce((f, g) => (...args) => g(f(...args)));

const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const transform = pipe(addOne, double, square);
console.log(transform(3)); // 64

Comparison with Other Paradigms

1. Imperative Programming

javascript
// Imperative
function sumArray(arr) {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  return sum;
}

// Functional
const sumArray = arr => arr.reduce((sum, num) => sum + num, 0);

2. Object-Oriented Programming

javascript
// Object-Oriented
class Calculator {
  constructor() {
    this.value = 0;
  }

  add(x) {
    this.value += x;
    return this;
  }

  subtract(x) {
    this.value -= x;
    return this;
  }
}

// Functional
const calculator = {
  add: x => y => x + y,
  subtract: x => y => x - y
};

Practical Applications

1. Data Transformation

javascript
const users = [
  { name: 'John', age: 30 },
  { name: 'Jane', age: 25 },
  { name: 'Bob', age: 35 }
];

// Functional approach
const getAdultNames = users =>
  users
    .filter(user => user.age >= 18)
    .map(user => user.name)
    .sort();

2. Event Handling

javascript
// Functional event handling
const handleClick = compose(
  logEvent,
  updateState,
  preventDefault
);

button.addEventListener('click', handleClick);

3. State Management

javascript
// Functional state management
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

Best Practices

1. Use Pure Functions

javascript
// Bad: Impure function
function calculateTotal(items) {
  let total = 0;
  items.forEach(item => {
    total += item.price;
  });
  return total;
}

// Good: Pure function
const calculateTotal = items =>
  items.reduce((total, item) => total + item.price, 0);

2. Avoid Side Effects

javascript
// Bad: Side effects
function updateUser(user) {
  user.lastUpdated = new Date();
  return user;
}

// Good: No side effects
const updateUser = user => ({
  ...user,
  lastUpdated: new Date()
});

3. Use Function Composition

javascript
// Bad: Nested function calls
const result = processData(transformData(filterData(data)));

// Good: Function composition
const process = compose(processData, transformData, filterData);
const result = process(data);

Performance Considerations

1. Memoization

javascript
const memoize = (fn) => {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
};

const expensiveOperation = memoize(x => {
  // Expensive computation
  return x * x;
});

2. Lazy Evaluation

javascript
const lazyMap = function* (fn, iterable) {
  for (const item of iterable) {
    yield fn(item);
  }
};

const numbers = [1, 2, 3, 4];
const doubled = lazyMap(x => x * 2, numbers);

Conclusion

Functional programming in JavaScript offers several benefits:

  • More predictable code
  • Easier testing
  • Better code reuse
  • Improved maintainability

However, it's important to:

  • Choose the right paradigm for your use case
  • Balance functional and imperative approaches
  • Consider performance implications
  • Use appropriate tools and libraries

By understanding and applying functional programming concepts, you can write more maintainable and reliable JavaScript code.