Introduction to Functional Programming at an Advanced Level
\n\nFunctional programming (FP) is a paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. In JavaScript, especially in the Node.js environment, FP is becoming increasingly popular due to its predictability, testability, and parallelism.
\n\nIf you have already mastered the basics of FP (pure functions, higher-order functions, map, filter, reduce), it's time to move to the next level. In this article, we will cover advanced concepts: currying, composition, monads, and working with immutable data.
1. Currying and Partial Application
\n\nWhat is Currying?
\nCurrying is the process of transforming a function that takes multiple arguments into a sequence of functions, each taking a single argument. This allows you to create specialized functions based on general ones.
\n\n// Regular function\nconst add = (a, b, c) => a + b + c;\nconsole.log(add(1, 2, 3)); // 6\n\n// Curried version\nconst curriedAdd = a => b => c => a + b + c;\nconsole.log(curriedAdd(1)(2)(3)); // 6\n\n// Universal currying function\nconst curry = (fn) => {\n const arity = fn.length;\n const resolver = (...args) => {\n if (args.length < arity) {\n return (...nextArgs) => resolver(...args, ...nextArgs);\n }\n return fn(...args);\n };\n return resolver;\n};\n\nconst multiply = (a, b, c) => a * b * c;\nconst curriedMultiply = curry(multiply);\nconsole.log(curriedMultiply(2)(3)(4)); // 24\nconsole.log(curriedMultiply(2, 3)(4)); // 24\n\nPartial Application
\nPartial application is fixing a portion of a function's arguments, creating a new function with fewer arguments. Unlike currying, partial application does not require a strict sequence of calls.
\n\nconst partial = (fn, ...presetArgs) => (...laterArgs) => fn(...presetArgs, ...laterArgs);\n\nconst greet = (greeting, name) => `${greeting}, ${name}!`;\nconst sayHello = partial(greet, 'Hello');\nconst sayHi = partial(greet, 'Hi');\n\nconsole.log(sayHello('Alice')); // Hello, Alice!\nconsole.log(sayHi('Bob')); // Hi, Bob!\n\n2. Function Composition
\n\nAdvanced Composition
\nComposition is the process of combining several functions to create a new function. In advanced FP, we use right-to-left composition (compose) and left-to-right composition (pipe).
// Simple composition (right to left)\nconst compose = (...fns) => (value) => fns.reduceRight((acc, fn) => fn(acc), value);\n\n// Left-to-right composition (pipe)\nconst pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);\n\n// Example: string processing\nconst trim = (str) => str.trim();\nconst toLowerCase = (str) => str.toLowerCase();\nconst capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);\nconst removeSpaces = (str) => str.replace(/\\s+/g, '-');\n\nconst slugify = compose(capitalize, toLowerCase, trim, removeSpaces);\n// Equivalent: removeSpaces(trim(toLowerCase(capitalize(" Hello World "))))\n\nconsole.log(slugify(' Hello World ')); // Hello-world\n\nTransducers
\nTransducers are compositions of transformation functions that work with any iterable data structure. They allow combining multiple operations (e.g., map and filter) into a single pass.
const transducer = (mapper, predicate) => (reducer) => (acc, value) => {\n const mapped = mapper(value);\n return predicate(mapped) ? reducer(acc, mapped) : acc;\n};\n\nconst double = (x) => x * 2;\nconst isEven = (x) => x % 2 === 0;\n\nconst processNumbers = transducer(double, isEven);\nconst sumReduc