DMarlon Studio

Lección 10 · 10 de 20

Currificación y memoización · JavaScript

Fije argumentos por partes con currificación y evite cálculos repetidos con memoización en JavaScript, con precios y Fibonacci.

JavaScript · Currificación y memoización en DMarlon Studio: el código del ejemplo y su salida en la terminal

Currificar es transformar una función de varios argumentos en una cadena de funciones de uno solo. price recibe el IVA y devuelve una función que recibe el descuento, que a su vez devuelve otra que recibe el precio neto. La ventaja es la aplicación parcial: spain ya tiene fijado el 21 % y spainSale, además, el 10 % de descuento, listas para reutilizar.

curry convierte cualquier función normal en currificada. Usa fn.length, el número de parámetros que declara la función: mientras falten argumentos, devuelve otra función que los va acumulando; cuando están todos, llama a la original. Por eso shipping admite varias formas de llamada: toEurope(3)(true), toEurope(1, false) o los tres argumentos a la vez.

La memoización guarda el resultado de una función para cada argumento y, si se repite, lo devuelve sin recalcular. memoize usa un Map como caché dentro de una clausura. Solo vale para funciones puras, las que siempre devuelven lo mismo para la misma entrada y no tienen efectos secundarios.

La diferencia se ve con Fibonacci: la versión recursiva simple hace 242 785 llamadas para calcular fib(25), porque repite los mismos cálculos una y otra vez. La memoizada solo necesita 26 (una por cada número del 0 al 25), y la segunda vez no hace ninguna. fastFib se llama a sí misma a través de la versión memoizada, y así aprovecha la caché también en la recursión.

Código · main.js

// Currying: one function per argument, each returning the next one.const price = (vat) => (discount) => (net) => +(net * (1 - discount) * (1 + vat)).toFixed(2);const spain = price(0.21);const spainSale = spain(0.1);console.log("Sale prices:", spainSale(100), spainSale(250), "| full price:", spain(0)(100));// A generic curry helper: waits until it has every parameter.function curry(fn) {  return function curried(...args) {    return args.length >= fn.length ? fn(...args) : (...more) => curried(...args, ...more);  };}const shipping = curry((zone, kilos, express) => (zone === "EU" ? 5 : 12) + kilos * 2 + (express ? 10 : 0));const toEurope = shipping("EU");console.log("EU 3 kg express:", toEurope(3)(true), "| EU 1 kg:", toEurope(1, false));console.log("World 2 kg:", shipping("World", 2, false));// Memoization: remember each result so it is computed only once.function memoize(fn) {  const cache = new Map();  return (n) => {    if (!cache.has(n)) cache.set(n, fn(n));    return cache.get(n);  };}let calls = 0;const slowFib = (n) => (calls++, n < 2 ? n : slowFib(n - 1) + slowFib(n - 2));console.log("fib(25) =", slowFib(25), "in", calls, "calls");calls = 0;const fastFib = memoize((n) => (calls++, n < 2 ? n : fastFib(n - 1) + fastFib(n - 2)));console.log("fib(25) =", fastFib(25), "in", calls, "calls (memoized)");console.log("fib(25) again:", fastFib(25), "- still", calls, "calls");

Salida · node main.js

Sale prices: 108.9 272.25 | full price: 121
EU 3 kg express: 21 | EU 1 kg: 7
World 2 kg: 16
fib(25) = 75025 in 242785 calls
fib(25) = 75025 in 26 calls (memoized)
fib(25) again: 75025 - still 26 calls

Para recordar

  • Currificar convierte f(a, b, c) en f(a)(b)(c).
  • La aplicación parcial crea funciones especializadas a partir de una general.
  • fn.length indica cuántos parámetros declara una función.
  • Memoizar es guardar resultados en caché; solo sirve para funciones puras.

Ejercicio

Use curry con una función formatLine(currency, decimals, amount) y cree euros = formatLine("EUR")(2); después memoice una función factorial y cuente cuántas veces se ejecuta.