TechLead
Lección 6 de 8

Currificación y aplicación parcial

Transforma funciones para aceptar argumentos uno por uno y ganar flexibilidad

¿Qué es la currificación?

La currificación transforma una función con múltiples argumentos en una secuencia de funciones, cada una con un solo argumento. Nombrada por Haskell Curry, es fundamental en la PF y habilita patrones de composición potentes.

// Regular function with multiple arguments
const add = (a, b, c) => a + b + c;
add(1, 2, 3); // 6

// Curried version
const addCurried = a => b => c => a + b + c;
addCurried(1)(2)(3); // 6

// The magic: partial application
const add1 = addCurried(1);      // b => c => 1 + b + c
const add1and2 = add1(2);        // c => 1 + 2 + c
const result = add1and2(3);      // 6

// Or step by step
const addTo10 = addCurried(10);
const addTo10And5 = addTo10(5);
addTo10And5(3);  // 18

Currificación vs aplicación parcial

  • Currificación: Siempre produce funciones unarias (de un solo argumento). Una función de n argumentos se convierte en n funciones de 1 argumento.
  • Aplicación parcial: Fija algunos argumentos y devuelve una función con menos argumentos. Es más flexible respecto a cuántos argumentos fijar.
// CURRYING: unary functions only
const curriedAdd = a => b => c => a + b + c;
curriedAdd(1)(2)(3); // Must call one arg at a time

// PARTIAL APPLICATION: fix any number of arguments
const add = (a, b, c) => a + b + c;

// Using bind for partial application
const add1 = add.bind(null, 1);       // a=1 fixed
add1(2, 3); // 6

const add1and2 = add.bind(null, 1, 2); // a=1, b=2 fixed
add1and2(3); // 6

// Partial application helper
const partial = (fn, ...fixedArgs) =>
  (...remainingArgs) => fn(...fixedArgs, ...remainingArgs);

const add5 = partial(add, 5);
add5(10, 20); // 35

Implementar curry

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

// Usage
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);

curriedAdd(1)(2)(3);     // 6
curriedAdd(1, 2)(3);     // 6 - flexible!
curriedAdd(1)(2, 3);     // 6
curriedAdd(1, 2, 3);     // 6

// Arrow function version
const curry = fn =>
  function curried(...args) {
    return args.length >= fn.length
      ? fn(...args)
      : (...more) => curried(...args, ...more);
  };

Ejemplos prácticos de currificación

// 1. Configuration functions
const log = level => message => 
  console.log(`[${level}] ${message}`);

const logError = log('ERROR');
const logInfo = log('INFO');
const logDebug = log('DEBUG');

logError('Something went wrong!');
logInfo('User logged in');

// 2. Event handlers
const handleEvent = eventType => handler => element => {
  element.addEventListener(eventType, handler);
  return () => element.removeEventListener(eventType, handler);
};

const onClick = handleEvent('click');
const onSubmit = handleEvent('submit');

const removeClickHandler = onClick(() => console.log('Clicked!'))(button);

// 3. API helpers
const api = baseUrl => endpoint => options =>
  fetch(`${baseUrl}${endpoint}`, options).then(r => r.json());

const myApi = api('https://api.example.com');
const getUsers = myApi('/users');
const getPosts = myApi('/posts');

// Call with options
getUsers({ headers: { 'Authorization': 'Bearer token' } });

// 4. Data transformation
const map = fn => arr => arr.map(fn);
const filter = pred => arr => arr.filter(pred);
const prop = key => obj => obj[key];

const getNames = map(prop('name'));
const getActiveUsers = filter(prop('active'));

const users = [
  { name: 'Alice', active: true },
  { name: 'Bob', active: false },
];

getNames(users); // ['Alice', 'Bob']
getActiveUsers(users); // [{ name: 'Alice', active: true }]

Currificación para composición

// Curried functions compose beautifully
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

// Curried utilities
const map = fn => arr => arr.map(fn);
const filter = pred => arr => arr.filter(pred);
const sort = compareFn => arr => [...arr].sort(compareFn);
const take = n => arr => arr.slice(0, n);
const prop = key => obj => obj[key];

// Build a data processing pipeline
const processUsers = pipe(
  filter(user => user.age >= 18),
  sort((a, b) => a.name.localeCompare(b.name)),
  take(5),
  map(prop('name'))
);

const users = [
  { name: 'Charlie', age: 25 },
  { name: 'Alice', age: 17 },
  { name: 'Bob', age: 30 },
  { name: 'Diana', age: 22 },
];

processUsers(users); // ['Bob', 'Charlie', 'Diana']

// Compare to non-curried
users
  .filter(user => user.age >= 18)
  .sort((a, b) => a.name.localeCompare(b.name))
  .slice(0, 5)
  .map(user => user.name);

// The curried version is more reusable!
// processUsers can be used anywhere

Currificación con placeholders

// Sometimes you want to fix arguments out of order
// Ramda uses R.__ as placeholder

// Simple placeholder implementation
const _ = Symbol('placeholder');

function curryWithPlaceholder(fn) {
  return function curried(...args) {
    const hasPlaceholder = args.some(arg => arg === _);
    const complete = args.length >= fn.length && !hasPlaceholder;
    
    if (complete) return fn(...args);
    
    return (...more) => {
      const merged = args.map(arg => 
        arg === _ && more.length ? more.shift() : arg
      );
      return curried(...merged, ...more);
    };
  };
}

const divide = (a, b) => a / b;
const curriedDivide = curryWithPlaceholder(divide);

const divideBy2 = curriedDivide(_, 2);  // Fix second arg
divideBy2(10); // 5

const divide10By = curriedDivide(10, _); // Fix first arg  
divide10By(2); // 5

Currificación en React

// Event handler factories
function TodoList({ todos, onToggle, onDelete }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input 
            type="checkbox"
            checked={todo.done}
            onChange={handleToggle(todo.id)}
          />
          {todo.text}
          <button onClick={handleDelete(todo.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
  
  // Curried handlers
  function handleToggle(id) {
    return () => onToggle(id);
  }
  
  function handleDelete(id) {
    return () => onDelete(id);
  }
}

// HOC pattern with currying
const withAuth = requiredRole => Component => props => {
  const { user } = useAuth();
  
  if (!user || user.role !== requiredRole) {
    return <Navigate to="/login" />;
  }
  
  return <Component {...props} />;
};

const AdminDashboard = withAuth('admin')(Dashboard);
const UserProfile = withAuth('user')(Profile);

⚠️ Cuándo usar currificación

  • ✅ Cuando necesitas crear funciones especializadas a partir de funciones generales
  • ✅ Cuando compones funciones en pipelines
  • ✅ Cuando creas fábricas de handlers de eventos
  • ✅ Cuando construyes funciones basadas en configuración
  • ❌ No sobre-currifiques: a veces los argumentos explícitos son más claros
  • ❌ Cuidado con el contexto de this en métodos curried