TechLead
Lección 2 de 8

Funciones puras

Comprende funciones puras, efectos secundarios y transparencia referencial

¿Qué es una función pura?

Una función pura es una función que:

  1. Determinista: Dada la misma entrada, siempre devuelve la misma salida
  2. Sin efectos secundarios: No modifica ni depende de estado externo

Las funciones puras son los bloques de construcción de la PF. Son predecibles, fáciles de probar y se pueden reutilizar con seguridad.

✅ Ejemplos de funciones puras

// Pure: same input → same output, no side effects
function add(a, b) {
  return a + b;
}

function double(x) {
  return x * 2;
}

function formatName(first, last) {
  return `${first} ${last}`;
}

// Array methods that return new arrays are pure
function getActiveUsers(users) {
  return users.filter(user => user.active);
}

// String methods are pure
function toUpperCase(str) {
  return str.toUpperCase();
}

❌ Ejemplos de funciones impuras

// Impure: depends on external state
let taxRate = 0.1;
function calculateTax(price) {
  return price * taxRate; // Uses external variable
}

// Impure: modifies external state
let total = 0;
function addToTotal(amount) {
  total += amount; // Mutates external variable
  return total;
}

// Impure: modifies input
function addItem(cart, item) {
  cart.push(item); // Mutates the input array
  return cart;
}

// Impure: I/O operations
function logMessage(msg) {
  console.log(msg); // Side effect: writes to console
}

// Impure: random/time-based
function getRandomNumber() {
  return Math.random(); // Different output each time
}

function getCurrentTime() {
  return new Date(); // Depends on current time
}

Efectos secundarios

Un efecto secundario es cualquier cambio de estado observable fuera de la función, o cualquier dependencia del estado externo.

// Common side effects:

// 1. Mutating input parameters
function sortArray(arr) {
  return arr.sort(); // ❌ Mutates original array!
}

// Pure version:
function sortArrayPure(arr) {
  return [...arr].sort(); // ✅ Creates new array
}

// 2. Modifying global/external variables
let counter = 0;
function increment() {
  counter++; // ❌ Modifies external state
}

// 3. DOM manipulation
function updateUI(message) {
  document.getElementById('output').textContent = message; // ❌
}

// 4. Network requests
async function fetchUser(id) {
  return await fetch(`/api/users/${id}`); // ❌ I/O side effect
}

// 5. Writing to storage
function saveData(data) {
  localStorage.setItem('data', JSON.stringify(data)); // ❌
}

// 6. Logging
function processData(data) {
  console.log('Processing...', data); // ❌ Side effect
  return data.map(x => x * 2);
}

Transparencia referencial

Una función es referencialmente transparente si puede reemplazarse por su valor de retorno sin cambiar el comportamiento del programa.

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

// This expression:
const result = add(2, 3) + add(2, 3);

// Can be replaced with:
const result = 5 + 5;

// The program behaves identically!

// NOT referentially transparent
let count = 0;
function incrementAndGet() {
  return ++count;
}

// This expression:
const x = incrementAndGet() + incrementAndGet();
// x = 1 + 2 = 3

// Cannot be replaced with:
const x = 1 + 1; // Would give 2, not 3!

// The function returns different values each time

Hacer puras las funciones impuras

// BEFORE: Impure - uses external state
let discount = 0.1;
function applyDiscount(price) {
  return price * (1 - discount);
}

// AFTER: Pure - all dependencies are parameters
function applyDiscount(price, discount) {
  return price * (1 - discount);
}

// BEFORE: Impure - mutates input
function addTodo(todos, text) {
  todos.push({ text, done: false });
  return todos;
}

// AFTER: Pure - returns new array
function addTodo(todos, text) {
  return [...todos, { text, done: false }];
}

// BEFORE: Impure - depends on Date
function isExpired(expirationDate) {
  return new Date() > expirationDate;
}

// AFTER: Pure - inject current time
function isExpired(expirationDate, currentDate) {
  return currentDate > expirationDate;
}
// Or use dependency injection
function createIsExpired(getCurrentDate) {
  return (expirationDate) => getCurrentDate() > expirationDate;
}

Beneficios de las funciones puras

// 1. TESTABLE - No mocking needed
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// Test is simple:
test('calculateTotal sums prices', () => {
  const items = [{ price: 10 }, { price: 20 }];
  expect(calculateTotal(items)).toBe(30);
});

// 2. CACHEABLE - Same input = same output
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 expensiveCalculation = memoize((n) => {
  console.log('Computing...');
  return n * n;
});

expensiveCalculation(5); // Computing... 25
expensiveCalculation(5); // 25 (cached, no computation)

// 3. PARALLELIZABLE - No shared state to worry about
const results = await Promise.all([
  pureFunction(data1),
  pureFunction(data2),
  pureFunction(data3),
]);

// 4. COMPOSABLE - Easy to combine
const processData = compose(
  formatOutput,
  filterInvalid,
  transformData,
  parseInput
);

📖 Wikipedia: Función pura →

Manejo de efectos secundarios

// Strategy 1: Push side effects to the edges
// Keep core logic pure, handle effects at boundaries

// Pure core logic
function calculateOrder(items, discount) {
  const subtotal = items.reduce((sum, i) => sum + i.price, 0);
  const discountAmount = subtotal * discount;
  return {
    subtotal,
    discount: discountAmount,
    total: subtotal - discountAmount,
  };
}

// Side effects at the boundary
async function processOrder(orderId) {
  // Side effect: fetch data
  const items = await fetchItems(orderId);
  const discount = await fetchDiscount(orderId);
  
  // Pure calculation
  const order = calculateOrder(items, discount);
  
  // Side effect: save result
  await saveOrder(orderId, order);
  
  // Side effect: notify
  await sendConfirmation(orderId);
  
  return order;
}

// Strategy 2: Return descriptions of effects (like Redux)
function reducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.todo] };
    // Pure function returns new state
    // Side effects happen elsewhere (middleware)
  }
}

💡 Lista de verificación de funciones puras

  • ✅ ¿Solo usa sus parámetros?
  • ✅ ¿Devuelve un valor basado solo en las entradas?
  • ✅ ¿Evita modificar parámetros?
  • ✅ ¿Evita leer/escribir variables externas?
  • ✅ ¿Evita E/S (console, red, almacenamiento)?
  • ✅ ¿Evita valores aleatorios o la hora actual?