DMarlon Studio

Lección 9 · 9 de 20

Orden superior y composición · JavaScript

Cree en JavaScript funciones que reciben y devuelven funciones: pipe, compose, envoltorios y reglas combinables para sus pedidos.

JavaScript · Orden superior y composición en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una función de orden superior recibe funciones, devuelve funciones o ambas cosas. Ya conoce map o filter; aquí se crean las propias. pipe recibe varias funciones y devuelve una nueva que pasa el valor por todas, de izquierda a derecha, con reduce.

La composición consiste en construir una tarea grande a partir de pasos pequeños con una sola responsabilidad. cleanUsername encadena trim, lower y dots, y convierte " Ana Maria Ruiz " en ana.maria.ruiz. compose hace lo mismo pero de derecha a izquierda, como en matemáticas: en shout, primero se pasa a mayúsculas y después se añade la exclamación.

withLog es un envoltorio (o decorador): recibe una función y devuelve otra que hace lo mismo y, además, escribe sus argumentos y su resultado. Gracias a ...args funciona con cualquier número de parámetros. Al componer addVat y coupon, la salida muestra cada paso del cálculo antes del total.

Las funciones también son datos: every recibe una lista de reglas y devuelve un único predicado que las exige todas. freeShipping se pasa tal cual a filter, y solo el pedido 1 cumple las dos condiciones. Este estilo es muy habitual en React, uno de los destinos de DMarlon Studio que se programan en JavaScript.

Código · main.js

// pipe and compose take functions and return a new function.const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);// Small single-purpose steps for cleaning a username.const trim = (s) => s.trim();const lower = (s) => s.toLowerCase();const dots = (s) => s.split(" ").join(".");const cleanUsername = pipe(trim, lower, dots);console.log(cleanUsername("  Ana Maria Ruiz "));// compose reads right to left: the last function runs first.const shout = compose((s) => s + "!", (s) => s.toUpperCase());console.log(shout("order shipped"));// A function that wraps another one to add logging.function withLog(name, fn) {  return (...args) => {    const result = fn(...args);    console.log(`  ${name}(${args.join(", ")}) = ${result}`);    return result;  };}const addVat = withLog("addVat", (net) => +(net * 1.21).toFixed(2));const coupon = withLog("coupon", (price) => price - 5);console.log("Checkout:", pipe(addVat, coupon)(100));// Functions as data: a list of rules combined into one test.const every = (...rules) => (item) => rules.every((rule) => rule(item));const freeShipping = every((o) => o.total > 50, (o) => o.country === "ES");const orders = [{ id: 1, total: 80, country: "ES" }, { id: 2, total: 30, country: "ES" }, { id: 3, total: 90, country: "FR" }];console.log("Free shipping:", orders.filter(freeShipping).map((o) => `#${o.id}`).join(", "));

Salida · node main.js

ana.maria.ruiz
ORDER SHIPPED!
  addVat(100) = 121
  coupon(121) = 116
Checkout: 116
Free shipping: #1

Para recordar

  • Una función de orden superior recibe o devuelve funciones.
  • pipe aplica las funciones de izquierda a derecha; compose, de derecha a izquierda.
  • Un envoltorio añade comportamiento sin cambiar la función original.
  • Las reglas en forma de función se pueden combinar como datos.

Ejercicio

Añada una regla más a freeShipping (por ejemplo, que el pedido no supere 10 artículos) y cree con pipe una función formatPrice que redondee a dos decimales y añada EUR.