Skip to content

Repository files navigation

Functional Programming

Table of Contents

  1. What is Functional Programming?
  2. Core Concepts
  3. Quick Reference

What is Functional Programming?

Functional programming is a style of writing code where programs are built by composing functions that:

  • Take inputs and produce outputs without modifying external state (pure functions).
  • Treat functions as first-class citizens, meaning they can be passed as arguments, returned from other functions, or assigned to variables (higher-order functions).
  • Avoid side effects, such as mutating variables or external data, to ensure predictability (immutability).
  • Focus on what to compute rather than how to compute it, making code more declarative.

1. Arity

Arity refers to the number of arguments a function accepts. Understanding arity is crucial for function composition and currying.

Example (Arity.js)

functionunary(x){return"Value: "+x;// Arity of 1 (unary)}functionbinary(x,y){returnx+y;// Arity of 2 (binary)}console.log(unary(5));// Output: Value: 5console.log(binary(2,3));// Output: 5
  • Why it matters: Functions with clear arity (e.g., unary or binary) are easier to compose or curry. Unary functions are particularly useful in FP for mapping over data structures.

2. Curried Functions

Currying transforms a function with multiple arguments into a sequence of single-argument functions. This enables partial application, where we can fix some arguments and reuse the function with others.

Example (Curried.js)

functioncalculate(sign){returnfunction(amount){returnsign+amount;};}vartotalUS=calculate("$");console.log(totalUS(100));// Output: $100console.log(totalUS(400));// Output: $400vartotalNP=calculate("₹");console.log(totalNP(50));// Output: ₹50
  • Contrast with non-curried (WithoutCurried.js):
    functioncalculateTotal(amount,sign){returnsign+amount;}console.log(calculateTotal(100,"$"));// Output: $100
  • Why it matters: Currying allows us to create specialized functions (e.g., totalUS) from a general one, improving reusability and reducing repetitive code.

3. Function Composition

Function composition combines two or more functions so that the output of one becomes the input of the next, creating a new function.

Example (WithComposition.js)

functiontrimName(name){returnname.trim();}functiontoLowerCaseName(name){returnname.toLowerCase();}functioncompose(f,g){returnfunction(x){returnf(g(x));};}varcleanName=compose(toLowerCaseName,trimName);console.log(cleanName(" Ashish "));// Output: ashish
  • Contrast with non-composed (WithoutComposition.js):
    vartrimmed=trimName(" Ashish ");varcleanName=toLowerCaseName(trimmed);console.log(cleanName);// Output: ashish
  • Why it matters: Composition creates concise, reusable pipelines, reducing intermediate variables and improving readability.

4. Higher-Order Functions

Higher-Order Functions (HOFs) are functions that take other functions as arguments or return functions as results.

Example (WithHOF.js)

functionarea(radius){returnMath.PI*radius*radius;}functioncalculate(logicFn,radius){varoutput=[];for(vari=0;i<radius.length;i++){output.push(logicFn(radius[i]));}returnoutput;}varradius=[2,4,6];console.log(calculate(area,radius));// Output: [12.566..., 50.265..., 113.097...]functiongreet(message){returnfunction(user){returnmessage+", "+user;};}varsayHello=greet("Hello");console.log(sayHello("Yeju"));// Output: Hello, Yeju
  • Contrast with non-HOF (WithoutHOF.js):
    functioncalculateArea(radius){varoutput=[];for(vari=0;i<radius.length;i++){output.push(Math.PI*radius[i]*radius[i]);}returnoutput;}console.log(calculateArea(radius));
  • Why it matters: HOFs reduce code duplication by abstracting common patterns (e.g., looping) and enable dynamic behavior (e.g., returning specialized functions).

5. Immutability

Immutability ensures data isn’t modified after creation, preventing side effects and making code predictable.

Example (Immutability.js)

functionpreviewNameChangeImmutable(user,newName){return{ ...user,name: newName};// Creates new object}varuserProfile={name: "Ashish",bio: "Developer"};varpreview=previewNameChangeImmutable(userProfile,"Bob");console.log(preview.name);// Output: Bobconsole.log(userProfile.name);// Output: Ashish (unchanged)
  • Contrast with mutable (Mutability.js):
    functionpreviewNameChange(user,newName){user.name=newName;// Modifies originalreturnuser;}varuserProfile={name: "Ashish",bio: "Developer"};varpreview=previewNameChange(userProfile,"Bob");console.log(preview.name);// Output: Bobconsole.log(userProfile.name);// Output: Bob (modified)
  • Why it matters: Immutability prevents unintended changes, making code safer and easier to reason about, especially in complex applications.

6. Pure Functions

Pure functions always produce the same output for the same input and have no side effects (e.g., no mutation, no randomness).

Example (PureFunction.js)

functiongreet(name){return"Namaste, I'm "+name;}console.log(greet("Ashish"));// Output: Namaste, I'm Ashishconsole.log(greet("Ashish"));// Output: Namaste, I'm Ashish (same input, same output)functionsliceArray(arr){returnarr.slice(0,3);// Doesn’t modify original}varevenNums=[2,4,6,8,10];console.log(sliceArray(evenNums));// Output: [2, 4, 6]console.log(evenNums);// Output: [2, 4, 6, 8, 10] (unchanged)
  • Contrast with impure (ImpureFunction.js):

    varname="Ashish";functiongreet(){console.log("Namaste, I'm "+name);// Depends on external state}greet();// Output: Namaste, I'm Ashishname="Yeju";greet();// Output: Namaste, I'm Yeju (different output)varevenNums=[2,4,6,8,10];console.log(evenNums.splice(0,3));// Output: [2, 4, 6]console.log(evenNums);// Output: [8, 10] (modified)
  • Why it matters: Pure functions are predictable, testable, and safe, reducing bugs caused by unexpected changes.

7. Functors

A functor is a data structure that holds a value and provides a map method to transform the value while preserving the structure of container.

Example (Functors.js)

functionBox(value){return{map: function(fn){returnBox(fn(value));},valueOf: function(){returnvalue;},};}varbox=Box("hello");varupperCased=box.map(function(str){returnstr.toUpperCase();});console.log(upperCased.valueOf());// Output: HELLO
  • Other functors:
    • Array: var nums = [1, 2, 3]; console.log(nums.map(function(x) { return x * 2; })); // [2, 4, 6]
    • Promise: Promise.resolve(100).then(function(x) { return x / 2; }).then(function(x) { console.log(x); }); // 50
  • Why it matters: Functors enable predictable transformations (e.g., mapping over arrays or promises) without altering the container’s structure, promoting reusable code.

Quick Reference

ConceptPurposeKey Benefit
ArityNumber of function argumentsEnables composition and currying
CurryingTransform multi-arg function into single-arg functionsPartial application and reusability
CompositionCombine functions into pipelinesCode reuse and clarity
Higher-Order FunctionsFunctions that work with other functionsAbstraction and flexibility
ImmutabilityData that doesn't changePredictability and safety
Pure FunctionsNo side effects, deterministicTestability and reliability
FunctorsContainers with map methodSafe transformations

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages