javascript

examples

examples.js
/**
 * =====================================================
 * 5.9 PURE FUNCTIONS & SIDE EFFECTS - EXAMPLES
 * =====================================================
 * Writing predictable, testable functions
 */

// =====================================================
// 1. PURE VS IMPURE - BASIC EXAMPLE
// =====================================================

console.log('--- Pure vs Impure ---');

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

console.log('Pure add(2, 3):', add(2, 3)); // Always 5
console.log('Pure add(2, 3):', add(2, 3)); // Always 5

// ❌ Impure function
let counter = 0;
function incrementCounter() {
  counter++;
  return counter;
}

console.log('Impure increment:', incrementCounter()); // 1
console.log('Impure increment:', incrementCounter()); // 2 (different!)

// =====================================================
// 2. DETERMINISTIC FUNCTIONS
// =====================================================

console.log('\n--- Deterministic Functions ---');

// ✅ Deterministic - same input, same output
function multiply(a, b) {
  return a * b;
}

console.log('multiply(4, 5):', multiply(4, 5)); // Always 20
console.log('multiply(4, 5):', multiply(4, 5)); // Always 20

// ❌ Non-deterministic
function randomValue(max) {
  return Math.floor(Math.random() * max);
}

console.log('randomValue(10):', randomValue(10)); // Random
console.log('randomValue(10):', randomValue(10)); // Different!

// =====================================================
// 3. AVOIDING MUTATION - ARRAYS
// =====================================================

console.log('\n--- Avoiding Array Mutation ---');

const original = [1, 2, 3, 4, 5];

// ❌ Impure - mutates original
function addItemImpure(arr, item) {
  arr.push(item); // Mutates!
  return arr;
}

// ✅ Pure - returns new array
function addItemPure(arr, item) {
  return [...arr, item];
}

// ❌ Impure - sort mutates
function sortImpure(arr) {
  return arr.sort((a, b) => a - b);
}

// ✅ Pure - copy then sort
function sortPure(arr) {
  return [...arr].sort((a, b) => a - b);
}

// ✅ Pure - remove item
function removeItem(arr, index) {
  return [...arr.slice(0, index), ...arr.slice(index + 1)];
}

// ✅ Pure - update item
function updateItem(arr, index, newValue) {
  return arr.map((item, i) => (i === index ? newValue : item));
}

console.log('Original:', original);
console.log('Added:', addItemPure(original, 6));
console.log('Original still:', original);
console.log('Removed index 2:', removeItem(original, 2));
console.log('Updated index 0:', updateItem(original, 0, 10));

// =====================================================
// 4. AVOIDING MUTATION - OBJECTS
// =====================================================

console.log('\n--- Avoiding Object Mutation ---');

const person = { name: 'Alice', age: 25 };

// ❌ Impure - mutates original
function updateAgeImpure(obj, newAge) {
  obj.age = newAge;
  return obj;
}

// ✅ Pure - returns new object
function updateAgePure(obj, newAge) {
  return { ...obj, age: newAge };
}

// ✅ Pure - add property
function addProperty(obj, key, value) {
  return { ...obj, [key]: value };
}

// ✅ Pure - remove property
function removeProperty(obj, key) {
  const { [key]: removed, ...rest } = obj;
  return rest;
}

console.log('Original:', person);
console.log('Updated age:', updateAgePure(person, 30));
console.log('Added email:', addProperty(person, 'email', 'alice@example.com'));
console.log('Removed age:', removeProperty(person, 'age'));
console.log('Original still:', person);

// =====================================================
// 5. NESTED OBJECT UPDATES
// =====================================================

console.log('\n--- Nested Object Updates ---');

const user = {
  name: 'Bob',
  address: {
    city: 'NYC',
    zip: '10001',
  },
  settings: {
    theme: 'dark',
    notifications: true,
  },
};

// ✅ Pure - update nested property
function updateCity(user, newCity) {
  return {
    ...user,
    address: {
      ...user.address,
      city: newCity,
    },
  };
}

// ✅ Pure - update deeply nested
function updateNestedProperty(obj, path, value) {
  const [first, ...rest] = path;

  if (rest.length === 0) {
    return { ...obj, [first]: value };
  }

  return {
    ...obj,
    [first]: updateNestedProperty(obj[first] || {}, rest, value),
  };
}

console.log('Updated city:', updateCity(user, 'LA'));
console.log(
  'Updated theme:',
  updateNestedProperty(user, ['settings', 'theme'], 'light')
);

// =====================================================
// 6. PURE STRING OPERATIONS
// =====================================================

console.log('\n--- Pure String Operations ---');

// Strings are immutable in JS - all operations are pure
function formatName(firstName, lastName) {
  return `${firstName.trim()} ${lastName.trim()}`.toUpperCase();
}

function slugify(text) {
  return text
    .toLowerCase()
    .trim()
    .replace(/[^\w\s-]/g, '')
    .replace(/[\s_-]+/g, '-');
}

function truncate(str, maxLength) {
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength - 3) + '...';
}

console.log('formatName:', formatName('  alice  ', '  smith  '));
console.log('slugify:', slugify('Hello World! This is a Test'));
console.log('truncate:', truncate('This is a long string', 15));

// =====================================================
// 7. PURE MATHEMATICAL FUNCTIONS
// =====================================================

console.log('\n--- Pure Math Functions ---');

function square(x) {
  return x * x;
}

function average(numbers) {
  if (numbers.length === 0) return 0;
  return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}

function percentage(value, total) {
  if (total === 0) return 0;
  return (value / total) * 100;
}

function roundTo(value, decimals) {
  const factor = Math.pow(10, decimals);
  return Math.round(value * factor) / factor;
}

console.log('square(5):', square(5));
console.log('average([1,2,3,4,5]):', average([1, 2, 3, 4, 5]));
console.log('clamp(15, 0, 10):', clamp(15, 0, 10));
console.log('percentage(25, 100):', percentage(25, 100));
console.log('roundTo(3.14159, 2):', roundTo(3.14159, 2));

// =====================================================
// 8. MEMOIZATION (CACHING PURE FUNCTIONS)
// =====================================================

console.log('\n--- Memoization ---');

function memoize(fn) {
  const cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      console.log('  Cache hit for', key);
      return cache.get(key);
    }

    console.log('  Computing for', key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

// Only works with pure functions!
const memoizedFactorial = memoize(function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
});

console.log('First call:', memoizedFactorial(5));
console.log('Second call:', memoizedFactorial(5));

// =====================================================
// 9. ISOLATING SIDE EFFECTS
// =====================================================

console.log('\n--- Isolating Side Effects ---');

// Pure business logic
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}

function applyDiscount(total, discountPercent) {
  return total * (1 - discountPercent / 100);
}

function formatCurrency(amount) {
  return `$${amount.toFixed(2)}`;
}

// Usage: Pure core, side effects at edges
const orderItems = [
  { name: 'Widget', price: 10, quantity: 2 },
  { name: 'Gadget', price: 25, quantity: 1 },
];

const total = calculateTotal(orderItems);
const discounted = applyDiscount(total, 10);
const formatted = formatCurrency(discounted);

console.log('Order total:', formatted);

// =====================================================
// 10. DEPENDENCY INJECTION FOR TESTABILITY
// =====================================================

console.log('\n--- Dependency Injection ---');

// ❌ Hard to test - uses Date directly
function getAgeImpure(birthYear) {
  return new Date().getFullYear() - birthYear;
}

// ✅ Testable - current year is injected
function getAgePure(birthYear, currentYear) {
  return currentYear - birthYear;
}

// Factory pattern for dependency injection
function createUserService(getCurrentYear) {
  return {
    getAge(birthYear) {
      return getCurrentYear() - birthYear;
    },
  };
}

// Production
const userService = createUserService(() => new Date().getFullYear());
console.log('Age:', userService.getAge(1990));

// Testing
const testUserService = createUserService(() => 2024);
console.log('Test age:', testUserService.getAge(1990)); // Always 34

// =====================================================
// 11. IMMUTABLE DATA PATTERNS
// =====================================================

console.log('\n--- Immutable Data Patterns ---');

// Todo list operations (all pure)
const todos = [
  { id: 1, text: 'Learn JS', completed: true },
  { id: 2, text: 'Build app', completed: false },
];

function addTodo(todos, text) {
  return [...todos, { id: Date.now(), text, completed: false }];
}

function toggleTodo(todos, id) {
  return todos.map((todo) =>
    todo.id === id ? { ...todo, completed: !todo.completed } : todo
  );
}

function removeTodo(todos, id) {
  return todos.filter((todo) => todo.id !== id);
}

function updateTodoText(todos, id, text) {
  return todos.map((todo) => (todo.id === id ? { ...todo, text } : todo));
}

console.log('Original:', todos);
console.log('Toggled:', toggleTodo(todos, 2));
console.log('Still original:', todos);

// =====================================================
// 12. TRANSFORMING DATA PURELY
// =====================================================

console.log('\n--- Data Transformation ---');

const users = [
  { id: 1, name: 'Alice', age: 25, role: 'admin' },
  { id: 2, name: 'Bob', age: 30, role: 'user' },
  { id: 3, name: 'Charlie', age: 35, role: 'user' },
];

// All pure transformations
function getActiveUsers(users) {
  return users.filter((u) => u.role === 'admin');
}

function getUserNames(users) {
  return users.map((u) => u.name);
}

function sortByAge(users) {
  return [...users].sort((a, b) => a.age - b.age);
}

function groupByRole(users) {
  return users.reduce((acc, user) => {
    const role = user.role;
    return {
      ...acc,
      [role]: [...(acc[role] || []), user],
    };
  }, {});
}

console.log('Names:', getUserNames(users));
console.log(
  'Sorted:',
  sortByAge(users).map((u) => u.name)
);
console.log('Grouped:', groupByRole(users));

// =====================================================
// 13. FUNCTION COMPOSITION
// =====================================================

console.log('\n--- Function Composition ---');

// Compose pure functions
const pipe =
  (...fns) =>
  (value) =>
    fns.reduce((acc, fn) => fn(acc), value);

const compose =
  (...fns) =>
  (value) =>
    fns.reduceRight((acc, fn) => fn(acc), value);

// Pure transformation functions
const double = (x) => x * 2;
const addTen = (x) => x + 10;
const stringify = (x) => `Result: ${x}`;

// Composed pipeline
const process = pipe(double, addTen, stringify);

console.log(process(5)); // "Result: 20"

// =====================================================
// 14. HANDLING OPTIONAL VALUES PURELY
// =====================================================

console.log('\n--- Handling Optional Values ---');

// Pure functions that handle null/undefined
function getProperty(obj, path, defaultValue = undefined) {
  const value = path.split('.').reduce((current, key) => current?.[key], obj);

  return value ?? defaultValue;
}

const data = {
  user: {
    profile: {
      name: 'Alice',
    },
  },
};

console.log('Found:', getProperty(data, 'user.profile.name'));
console.log('Not found:', getProperty(data, 'user.settings.theme', 'light'));

// =====================================================
// 15. TESTING PURE FUNCTIONS
// =====================================================

console.log('\n--- Testing Pure Functions ---');

// Pure functions are easy to test
function simpleTest(name, fn, input, expected) {
  const result = fn(...input);
  const passed = JSON.stringify(result) === JSON.stringify(expected);
  console.log(`${passed ? '✅' : '❌'} ${name}`);
  if (!passed) {
    console.log(`   Expected: ${JSON.stringify(expected)}`);
    console.log(`   Got: ${JSON.stringify(result)}`);
  }
}

simpleTest('add', add, [2, 3], 5);
simpleTest('multiply', multiply, [4, 5], 20);
simpleTest('square', square, [5], 25);
simpleTest('average', average, [[1, 2, 3, 4, 5]], 3);

// =====================================================
// SUMMARY
// =====================================================

console.log('\n--- Summary ---');
console.log(`
PURE FUNCTIONS:
  • Same input → same output
  • No side effects
  • Predictable and testable

AVOID:
  • Mutating arguments
  • Global state
  • I/O in pure functions
  • Date.now(), Math.random()

PATTERNS:
  • Return new arrays/objects
  • Spread operator for copies
  • Dependency injection
  • Function composition
  • Memoization
`);
Examples - JavaScript Tutorial | DeepML