Portes mitja dotzena de lliçons fregant aquesta idea sense posar-li nom. A Expressions de Funció i Funcions Fletxa vas desar funcions dins d'un objecte de formatadors i d'un array de validadors. A Àmbit i Closures vas escriure llistarSegons(filtre), que rebia una funció, i crearFiltrePerResponsable(nom), que en retornava una. Aquest nom és funció d'ordre superior, i és la tècnica que converteix el codi repetitiu en codi configurable. En aquesta lliçó l'estudiaràs a fons: construiràs les teves pròpies versions de forEach, map, filter i reduce per entendre exactament què fan per dins, aprendràs a compondre funcions en canonades i acabaràs muntant un motor d'informes per a Taller Nómada al qual s'endollen un filtre, un transformador i un agregador diferents segons el que demani la Marta.

Avís de progressió: els arrays de JavaScript ja porten forEach, map, filter i reduce de sèrie. Aquí els implementaràs a mà per entendre el mecanisme; els mètodes natius, amb tota la seva potència i els seus matisos, arriben a Iteració sobre Arrays i Cercar, Ordenar i Agregar Dades. Fins aleshores, continuem amb bucles i arrays paral·lels.

Contingut

  1. Què és una funció d'ordre superior
  2. Callbacks: el vocabulari
  3. elMeuForEach: fer alguna cosa amb cada element
  4. elMeuMap: transformar cada element
  5. elMeuFilter: quedar-se amb alguns
  6. elMeuReduce: reduir-ho tot a un valor
  7. Encadenar les quatre sobre el backlog
  8. Funcions que retornen funcions: fàbriques i currying
  9. Composició de funcions: compondre i pipe
  10. Funcions com a configuració
  11. Exemple integrador: el motor d'informes de Taller Nómada
  12. Una nota sobre llegibilitat
  13. Errors Habituals i Consells
  14. Exercicis
  15. Conclusió

  1. Què és una funció d'ordre superior

La definició és curta i precisa:

Una funció d'ordre superior (higher-order function, HOF) és una funció que rep una o més funcions com a argument, retorna una funció, o totes dues coses.

Tota la resta és una funció normal, o de primer ordre. I això només és possible perquè, com vas veure a 03-02, en JavaScript les funcions són valors.

// D'ordre superior: REP una funció
function repetir(vegades, accio) {
  for (let i = 0; i < vegades; i++) {
    accio(i);
  }
}

repetir(3, (n) => console.log(`Recordatori ${n + 1} a Iván`));
// Recordatori 1 a Iván
// Recordatori 2 a Iván
// Recordatori 3 a Iván

// D'ordre superior: RETORNA una funció
function crearRecordatori(persona) {
  return (assumpte) => `Recordatori per a ${persona}: ${assumpte}`;
}

const avisarLucia = crearRecordatori('Lucía');
console.log(avisarLucia("el web de reserves venç el 2 d'octubre"));
// Recordatori per a Lucía: el web de reserves venç el 2 d'octubre

Què s'hi guanya? Separar l'esquelet del detall. repetir sap repetir, però no sap què cal fer a cada volta: això li ho dius tu. El mateix esquelet serveix per imprimir, per acumular o per validar.

flowchart LR
    A["Esquelet fix<br/>(recórrer, repetir, ordenar)"] --> C["Funció d'ordre superior"]
    B["Detall variable<br/>(què fer amb cada element)"] --> C
    C --> D["Comportament concret"]

  1. Callbacks: el vocabulari

Una funció que es passa com a argument perquè una altra la cridi s'anomena callback (funció de retrucada). El vocabulari associat, que convé fer servir amb precisió:

Terme Significat A l'exemple
Funció d'ordre superior La que rep o retorna funcions repetir
Callback La funció que es passa per ser cridada (n) => console.log(...)
Invocar el callback Cridar-lo des de dins de la HOF accio(i)
Signatura del callback Quins paràmetres rebrà i què ha de retornar (element, index) => ...
Predicat Callback que retorna true/false (h) => h > 10
Transformador Callback que retorna un valor nou (h) => h * 2
Comparador Callback que retorna -1, 0 o 1 (a, b) => a - b
Reductor Callback que combina acumulador i element (acc, h) => acc + h

Un punt que confon al principi: qui defineix el callback no decideix quan s'executa ni amb quins arguments. Això ho decideix la funció d'ordre superior. Tu escrius el "què fer"; ella hi posa el "quan" i el "amb què".

function ambCadaHora(hores, callback) {
  for (let i = 0; i < hores.length; i++) {
    callback(hores[i], i);          // ← la HOF decideix: dos arguments, en aquest ordre
  }
}

ambCadaHora([12, 6, 14], (h, i) => console.log(`Tasca ${i + 1}: ${h} h`));
ambCadaHora([12, 6, 14], (h) => console.log(h));        // pots ignorar el segon

Per això, abans d'escriure un callback, cal saber quina signatura espera la funció que el rebrà. És el primer que es mira a la documentació de qualsevol mètode.

  1. elMeuForEach: fer alguna cosa amb cada element

El més senzill: recorre l'array i crida el callback amb cada element. No retorna res; el seu valor és a l'efecte que produeix.

'use strict';

/**
 * Crida `accio` amb cada element de l'array.
 * @param {Array} llista    array a recórrer
 * @param {Function} accio  callback (element, index, llista)
 */
function elMeuForEach(llista, accio) {
  for (let i = 0; i < llista.length; i++) {
    accio(llista[i], i, llista);
  }
}

const titols = [
  'Redissenyar la sala polivalent',
  'Cartelleria del taller de serigrafia',
  'Actualitzar el web de reserves',
  'Inventari de tintes de serigrafia',
  'Guia sobre enquadernació per a residents',
  'Pressupost de la fusteria'
];

elMeuForEach(titols, (titol, i) => {
  console.log(`${i + 1}. ${titol}`);
});
// 1. Redissenyar la sala polivalent
// 2. Cartelleria del taller de serigrafia
// ...

Els tres arguments del callback (element, index, llista) no són un caprici: és exactament la signatura que fa servir el forEach natiu, així que aprendre-la ara t'estalvia feina al Mòdul 4.

Comparat amb un for clàssic, el que aporta és que desapareix la maquinària del bucle: no hi ha i = 0, ni i < llista.length, ni i++, i per tant no hi ha errors d'índex fora de rang.

Una diferència important amb el for: des d'un callback no es pot fer break. Un return dins del callback acaba el callback, no el recorregut.

elMeuForEach(titols, (titol) => {
  if (titol.startsWith('Inventari')) return;   // salta AQUEST, no talla el recorregut
  console.log(titol);
});

Si necessites tallar, fes servir un for amb break o una funció pensada per cercar (ho veuràs a 04-05 amb find i some).

  1. elMeuMap: transformar cada element

map construeix un array nou de la mateixa mida, on cada element és el resultat d'aplicar el callback a l'original.

/**
 * Retorna un array nou amb el resultat de transformar cada element.
 * @param {Array} llista
 * @param {Function} transformar  callback (element, index, llista) → valor nou
 * @returns {Array} array nou de la mateixa mida
 */
function elMeuMap(llista, transformar) {
  const resultat = [];
  for (let i = 0; i < llista.length; i++) {
    resultat.push(transformar(llista[i], i, llista));
  }
  return resultat;
}

const hores = [12, 6, 14, 3, 8, 5];

const enMinuts = elMeuMap(hores, (h) => h * 60);
console.log(enMinuts);           // [ 720, 360, 840, 180, 480, 300 ]
console.log(hores);              // [ 12, 6, 14, 3, 8, 5 ]  ← intacte

const numerades = elMeuMap(titols, (t, i) => `${i + 1}. ${t}`);
console.log(numerades[5]);       // 6. Pressupost de la fusteria

Tres propietats que defineixen map i que convé gravar:

Propietat Conseqüència
Retorna un array nou L'original no es toca: és una operació pura
Mateixa mida que l'original Si n'entren 6, en surten 6 (per filtrar hi ha filter)
Un a un, en ordre L'element i del resultat ve del i de l'original

L'error més comú és fer servir map quan en realitat vols forEach:

// ✗ map el resultat del qual es llença: fes servir forEach
elMeuMap(titols, (t) => console.log(t));   // retorna [undefined × 6]

// ✓
elMeuForEach(titols, (t) => console.log(t));

Regla: si faràs servir l'array resultant, map; si només vols l'efecte, forEach.

  1. elMeuFilter: quedar-se amb alguns

filter construeix un array nou amb els elements per als quals el callback (un predicat) retorna un valor truthy.

/**
 * Retorna un array nou amb els elements que compleixen el predicat.
 * @param {Array} llista
 * @param {Function} predicat  callback (element, index, llista) → booleà
 * @returns {Array} array nou, de 0 a llista.length elements
 */
function elMeuFilter(llista, predicat) {
  const resultat = [];
  for (let i = 0; i < llista.length; i++) {
    if (predicat(llista[i], i, llista)) {
      resultat.push(llista[i]);
    }
  }
  return resultat;
}

const llargues = elMeuFilter(hores, (h) => h > 8);
console.log(llargues);   // [ 12, 14 ]

const deSerigrafia = elMeuFilter(titols, (t) => t.toLowerCase().includes('serigrafia'));
console.log(deSerigrafia);
// [ 'Cartelleria del taller de serigrafia', 'Inventari de tintes de serigrafia' ]

Amb arrays paral·lels, el truc és filtrar índexs en lloc de valors, per poder consultar després la resta dels arrays:

const responsables = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const estats       = ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent'];

const indexs = [0, 1, 2, 3, 4, 5];

const obertesDeIvan = elMeuFilter(indexs, (i) => responsables[i] === 'Iván' && estats[i] !== 'feta');
console.log(obertesDeIvan);   // [ 0, 4, 5 ]

elMeuForEach(obertesDeIvan, (i) => console.log(`${titols[i]} — ${hores[i]} h`));
// Redissenyar la sala polivalent — 12 h
// Guia sobre enquadernació per a residents — 8 h
// Pressupost de la fusteria — 5 h

Aquest patró "filtrar índexs" és un apedaçament necessari mentre les dades visquin en arrays paral·lels. Al Mòdul 4, amb un array d'objectes, s'escriurà directament sobre les tasques.

elMeuMap elMeuFilter
Mida del resultat Igual que l'original Entre 0 i l'original
El callback retorna Un valor nou Un booleà
Els elements del resultat Transformats Els originals, sense tocar
Nom del callback Transformador Predicat

  1. elMeuReduce: reduir-ho tot a un valor

reduce és la més potent i la que més costa. Recorre l'array mantenint un acumulador que es va actualitzant amb cada element, i al final retorna aquest acumulador. El resultat pot ser un nombre, un text, un array o un objecte: qualsevol cosa.

/**
 * Redueix l'array a un únic valor.
 * @param {Array} llista
 * @param {Function} reductor  callback (acumulador, element, index, llista) → nou acumulador
 * @param {*} valorInicial     valor de partida de l'acumulador
 */
function elMeuReduce(llista, reductor, valorInicial) {
  let acumulador = valorInicial;
  for (let i = 0; i < llista.length; i++) {
    acumulador = reductor(acumulador, llista[i], i, llista);
  }
  return acumulador;
}

Tot el mecanisme cap en quatre línies, però convé veure'l pas a pas amb la suma d'hores del backlog:

const total = elMeuReduce(hores, (acc, h) => acc + h, 0);
console.log(total);   // 48
Volta acc en entrar h acc + h
0 0 (inicial) 12 12
1 12 6 18
2 18 14 32
3 32 3 35
4 35 8 43
5 43 5 48

I ara els casos que demostren que reduce no és només "sumar":

// Màxim
const maxHores = elMeuReduce(hores, (acc, h) => (h > acc ? h : acc), hores[0]);
console.log(maxHores);   // 14

// Concatenar text
const llistat = elMeuReduce(titols, (acc, t) => `${acc}\n· ${t}`, 'Backlog:');
console.log(llistat);
// Backlog:
// · Redissenyar la sala polivalent
// · ...

// Comptar per categoria → retorna un OBJECTE
const perResponsable = elMeuReduce(responsables, (acc, nom) => {
  acc[nom] = (acc[nom] ?? 0) + 1;
  return acc;
}, {});
console.log(perResponsable);   // { 'Iván': 3, Marta: 2, 'Lucía': 1 }

// Esforç ponderat (la regla que arrossegues des del Mòdul 2)
const PESOS = { alta: 3, mitjana: 2, baixa: 1 };
const prioritats = ['alta', 'mitjana', 'alta', 'baixa', 'mitjana', 'alta'];
const indexs2 = [0, 1, 2, 3, 4, 5];

const esforc = elMeuReduce(indexs2, (acc, i) => acc + (PESOS[prioritats[i]] ?? 0) * hores[i], 0);
console.log(esforc);   // 124

Dos advertiments sobre reduce:

  1. El reductor ha de retornar sempre l'acumulador. Oblidar el return és l'error número u; l'acumulador passa a ser undefined i tot peta a la volta següent.
  2. Dóna sempre un valor inicial. Sense ell, el natiu fa servir el primer element com a acumulador i falla amb arrays buits. El nostre elMeuReduce simplement retornaria undefined.
flowchart LR
    A["inicial: 0"] --> B["+12 → 12"] --> C["+6 → 18"] --> D["+14 → 32"]
    D --> E["+3 → 35"] --> F["+8 → 43"] --> G["+5 → 48"]

  1. Encadenar les quatre sobre el backlog

Amb les quatre peces ja es poden expressar preguntes de negoci completes de manera gairebé declarativa. Pregunta de la Marta: quantes hores obertes de prioritat alta hi ha al backlog, i quines són?

'use strict';

const AVUI = '2026-09-20';

const indexs       = [0, 1, 2, 3, 4, 5];
const titols       = [
  'Redissenyar la sala polivalent',
  'Cartelleria del taller de serigrafia',
  'Actualitzar el web de reserves',
  'Inventari de tintes de serigrafia',
  'Guia sobre enquadernació per a residents',
  'Pressupost de la fusteria'
];
const responsables = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const prioritats   = ['alta', 'mitjana', 'alta', 'baixa', 'mitjana', 'alta'];
const estats       = ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent'];
const hores        = [12, 6, 14, 3, 8, 5];
const datesLimit   = ['2026-09-30', '2026-10-15', '2026-10-02',
                      '2026-09-12', '2026-11-05', '2026-09-05'];

// 1. Filtrar: prioritat alta i no acabades
const altesObertes = elMeuFilter(indexs, (i) => prioritats[i] === 'alta' && estats[i] !== 'feta');
console.log(altesObertes);   // [ 0, 2, 5 ]

// 2. Transformar: una línia llegible per tasca
const linies = elMeuMap(altesObertes, (i) => {
  const avis = datesLimit[i] < AVUI ? ' ⚠ VENÇUDA' : '';
  return `${titols[i]} · ${responsables[i]} · ${hores[i]} h${avis}`;
});

// 3. Reduir: total d'hores
const horesAltes = elMeuReduce(altesObertes, (acc, i) => acc + hores[i], 0);

// 4. Efecte: imprimir
elMeuForEach(linies, (linia) => console.log(`  ${linia}`));
console.log(`Total d'hores obertes en prioritat alta: ${horesAltes} h`);

Sortida:

  Redissenyar la sala polivalent · Iván · 12 h
  Actualitzar el web de reserves · Lucía · 14 h
  Pressupost de la fusteria · Iván · 5 h ⚠ VENÇUDA
Total d'hores obertes en prioritat alta: 31 h

Observa l'estructura: filtrar → transformar → agregar → efecte. Cada pas és una operació amb nom, es pot llegir per separat i es pot canviar sense tocar els altres. Comparat amb el bucle únic del Mòdul 2 —que feia les quatre coses barrejades dins d'un if— la diferència de llegibilitat és enorme.

  1. Funcions que retornen funcions: fàbriques i currying

Ja vas conèixer les fàbriques a 03-04. Aquí les portem un pas més enllà.

// Fàbrica de predicats per camp i valor
function crearPredicat(arrayCamp, valorEsperat) {
  return (index) => arrayCamp[index] === valorEsperat;
}

const esDeIvan     = crearPredicat(responsables, 'Iván');
const estaPendent  = crearPredicat(estats, 'pendent');
const esAlta       = crearPredicat(prioritats, 'alta');

console.log(elMeuFilter(indexs, esDeIvan));       // [ 0, 4, 5 ]
console.log(elMeuFilter(indexs, estaPendent));    // [ 1, 2, 5 ]

I combinadors que produeixen predicats nous a partir d'altres:

const tots = (...predicats) => (i) => {
  for (const p of predicats) {
    if (!p(i)) return false;
  }
  return true;
};

const algun = (...predicats) => (i) => {
  for (const p of predicats) {
    if (p(i)) return true;
  }
  return false;
};

const no = (predicat) => (i) => !predicat(i);

console.log(elMeuFilter(indexs, tots(esDeIvan, esAlta)));               // [ 0, 5 ]
console.log(elMeuFilter(indexs, algun(esAlta, estaPendent)));           // [ 0, 1, 2, 5 ]
console.log(elMeuFilter(indexs, no(crearPredicat(estats, 'feta'))));    // [ 0, 1, 2, 4, 5 ]

Amb tres funcions de cinc línies has construït un petit llenguatge de consultes sobre el backlog. Això és poder expressiu.

Currying

Currying és transformar una funció de diversos paràmetres en una cadena de funcions d'un paràmetre cadascuna:

// Versió normal
function esforc(pes, hores) {
  return pes * hores;
}

// Versió "currificada"
const esforcCurry = (pes) => (hores) => pes * hores;

console.log(esforc(3, 12));          // 36
console.log(esforcCurry(3)(12));     // 36

// La gràcia és fixar el primer argument
const esforcAlta = esforcCurry(3);
console.log(esforcAlta(12));   // 36
console.log(esforcAlta(14));   // 42
console.log(esforcAlta(5));    // 15

La tècnica emparentada, l'aplicació parcial, fixa alguns arguments i deixa la resta:

function ambMarge(limit, horesObertes) {
  return limit - horesObertes;
}

const margeSetmanal = (horesObertes) => ambMarge(40, horesObertes);   // R7 fixada

console.log(margeSetmanal(25));   // 15
console.log(margeSetmanal(45));   // -5

Quan val la pena? Quan cridaràs moltes vegades amb el mateix primer argument, o quan necessites una funció d'un sol paràmetre per passar-la a elMeuMap o elMeuFilter. Fora d'aquests casos, currificar per currificar fa el codi més difícil de llegir.

  1. Composició de funcions: compondre i pipe

Si tens diverses transformacions petites, encadenar-les a mà és incòmode:

const treureEspais = (t) => t.trim();
const aMinuscules  = (t) => t.toLowerCase();
const senseAccents = (t) => t.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
const aGuionets    = (t) => t.replace(/\s+/g, '-');

// Imbricat: es llegeix de dins cap enfora, al revés de com passa
const slug = aGuionets(senseAccents(aMinuscules(treureEspais('  Pressupost de la Fusteria  '))));
console.log(slug);   // pressupost-de-la-fusteria

Compondre és crear una funció nova que n'aplica diverses en cadena:

/** compondre(f, g)(x) === f(g(x))  — s'aplica de dreta a esquerra */
function compondre(f, g) {
  return (x) => f(g(x));
}

const netejar = compondre(aMinuscules, treureEspais);
console.log(netejar('  ETIQUETA DE SERIGRAFIA  '));   // etiqueta de serigrafia

I pipe fa el mateix però en l'ordre en què es llegeix, d'esquerra a dreta, que és molt més natural:

/** pipe(f, g, h)(x) === h(g(f(x)))  — s'aplica d'esquerra a dreta */
function pipe(...funcions) {
  return function (valorInicial) {
    let valor = valorInicial;
    for (const fn of funcions) {
      valor = fn(valor);
    }
    return valor;
  };
}

const aEtiqueta = pipe(treureEspais, aMinuscules, senseAccents, aGuionets);

console.log(aEtiqueta('  Pressupost de la Fusteria  '));   // pressupost-de-la-fusteria
console.log(aEtiqueta('  Serigrafia  '));                  // serigrafia
console.log(aEtiqueta('Enquadernació Manual'));            // enquadernacio-manual

Fixa't que pipe és literalment elMeuReduce aplicat a funcions en lloc de a dades: parteix d'un valor inicial i el va transformant. L'aplicació directa al projecte és la R9 (etiquetes en minúscules, sense duplicats):

const etiquetesCrues = ['  Fusteria ', 'FUSTERIA', 'Enquadernació', 'enquadernació ', 'Disseny'];

const normalitzades = elMeuMap(etiquetesCrues, pipe(treureEspais, aMinuscules, senseAccents));
console.log(normalitzades);
// [ 'fusteria', 'fusteria', 'enquadernacio', 'enquadernacio', 'disseny' ]

const senseDuplicats = elMeuReduce(normalitzades, (acc, e) => {
  if (!acc.includes(e)) acc.push(e);
  return acc;
}, []);
console.log(senseDuplicats);   // [ 'fusteria', 'enquadernacio', 'disseny' ]
flowchart LR
    A["'  Pressupost de la Fusteria  '"] --> B["treureEspais"] --> C["aMinuscules"]
    C --> D["senseAccents"] --> E["aGuionets"] --> F["'pressupost-de-la-fusteria'"]

  1. Funcions com a configuració

El tercer gran ús de les HOF és parametritzar el comportament d'un component. En lloc d'escriure una funció diferent per a cada variant, n'escrius una i li passes la peça que canvia.

// Un únic "ordenador" que accepta qualsevol comparador
function ordenarIndexs(indexs, comparar) {
  const copia = [];
  for (const i of indexs) copia.push(i);

  // Ordenació per inserció, per no dependre encara de sort() (arriba a 04-05)
  for (let i = 1; i < copia.length; i++) {
    const actual = copia[i];
    let j = i - 1;
    while (j >= 0 && comparar(copia[j], actual) > 0) {
      copia[j + 1] = copia[j];
      j--;
    }
    copia[j + 1] = actual;
  }
  return copia;
}

const perData      = (a, b) => (datesLimit[a] < datesLimit[b] ? -1 : datesLimit[a] > datesLimit[b] ? 1 : 0);
const perHoresDesc = (a, b) => hores[b] - hores[a];
const perPrioritat = (a, b) => (PESOS[prioritats[b]] ?? 0) - (PESOS[prioritats[a]] ?? 0);

console.log(elMeuMap(ordenarIndexs(indexs, perData), (i) => `${datesLimit[i]} ${titols[i]}`));
// [ '2026-09-05 Pressupost de la fusteria',
//   '2026-09-12 Inventari de tintes de serigrafia',
//   '2026-09-30 Redissenyar la sala polivalent', ... ]

console.log(elMeuMap(ordenarIndexs(indexs, perHoresDesc), (i) => `${hores[i]} h`));
// [ '14 h', '12 h', '8 h', '6 h', '5 h', '3 h' ]

El mateix principi s'aplica a validadors i formatadors intercanviables:

function validarAmb(regles, index) {
  const errors = [];
  elMeuForEach(regles, (regla) => {
    if (!regla.comprovar(index)) errors.push(`${regla.codi}: ${regla.missatge}`);
  });
  return errors;
}

const reglesAlta = [
  { codi: 'R2', missatge: 'El títol és obligatori.',           comprovar: (i) => titols[i].trim().length > 0 },
  { codi: 'R3', missatge: 'Les hores han de ser entre 0 i 40.', comprovar: (i) => hores[i] > 0 && hores[i] <= 40 },
  { codi: 'R10', missatge: 'La tasca està vençuda.',            comprovar: (i) => !(datesLimit[i] < AVUI && estats[i] !== 'feta') }
];

console.log(validarAmb(reglesAlta, 5));
// [ 'R10: La tasca està vençuda.' ]
console.log(validarAmb(reglesAlta, 0));
// []

Fixa't en el patró, perquè és el mateix que estructura gairebé tot el programari professional: un motor genèric + un conjunt de funcions endollables. Afegir una regla, un ordre o un format ja no vol dir tocar el motor.

  1. Exemple integrador: el motor d'informes de Taller Nómada

Ajuntem tot en una sola peça. generarInforme no sap res de tasques: sap filtrar, transformar, agregar i presentar, i rep cadascuna d'aquestes quatre coses com a funció.

'use strict';

/**
 * Motor d'informes genèric.
 * @param {Array}    font          elements de partida (aquí, índexs del backlog)
 * @param {Object}   configuracio
 * @param {string}   configuracio.titol
 * @param {Function} configuracio.filtre      (element) → booleà
 * @param {Function} configuracio.transformar (element) → línia de text
 * @param {Function} configuracio.agregar     (elements) → resum
 * @param {Function} [configuracio.ordenar]   comparador opcional
 */
function generarInforme(font, configuracio) {
  const filtre      = configuracio.filtre      ?? (() => true);
  const transformar = configuracio.transformar ?? ((x) => String(x));
  const agregar     = configuracio.agregar     ?? (() => '');
  const ordenar     = configuracio.ordenar     ?? null;

  let seleccionats = elMeuFilter(font, filtre);
  if (ordenar !== null) seleccionats = ordenarIndexs(seleccionats, ordenar);

  const linies = elMeuMap(seleccionats, transformar);

  const sortida = [`── ${configuracio.titol} ──`];
  elMeuForEach(linies, (linia) => sortida.push(`  ${linia}`));
  sortida.push(`  ${agregar(seleccionats)}`);

  return elMeuReduce(sortida, (acc, l) => `${acc}\n${l}`, '').trim();
}

// ─── Peces reutilitzables ──────────────────────────────────────────
const noFeta       = (i) => estats[i] !== 'feta';
const vencuda      = (i) => datesLimit[i] < AVUI && estats[i] !== 'feta';
const deResponsable = (nom) => (i) => responsables[i] === nom;

const liniaDetallada = (i) =>
  `${titols[i]} · ${responsables[i]} · ${prioritats[i]} · ${hores[i]} h · venç ${datesLimit[i]}`;
const liniaCompacta = (i) => `${titols[i]} (${hores[i]} h)`;

const sumaHores    = (seleccio) => `Total: ${elMeuReduce(seleccio, (acc, i) => acc + hores[i], 0)} h`;
const recompteIPes = (seleccio) => {
  const n = seleccio.length;
  const pes = elMeuReduce(seleccio, (acc, i) => acc + (PESOS[prioritats[i]] ?? 0) * hores[i], 0);
  return `${n} tasca(s) · esforç ponderat ${pes}`;
};

// ─── Informe 1: tot el que és obert, per data ──────────────────────
console.log(generarInforme(indexs, {
  titol: 'Feina oberta per data límit',
  filtre: noFeta,
  ordenar: perData,
  transformar: liniaDetallada,
  agregar: sumaHores
}));
── Feina oberta per data límit ──
  Pressupost de la fusteria · Iván · alta · 5 h · venç 2026-09-05
  Redissenyar la sala polivalent · Iván · alta · 12 h · venç 2026-09-30
  Actualitzar el web de reserves · Lucía · alta · 14 h · venç 2026-10-02
  Cartelleria del taller de serigrafia · Marta · mitjana · 6 h · venç 2026-10-15
  Guia sobre enquadernació per a residents · Iván · mitjana · 8 h · venç 2026-11-05
  Total: 45 h
// ─── Informe 2: la càrrega de l'Iván, compacta ─────────────────────
console.log(generarInforme(indexs, {
  titol: 'Càrrega oberta: Iván',
  filtre: tots(deResponsable('Iván'), noFeta),
  ordenar: perHoresDesc,
  transformar: liniaCompacta,
  agregar: recompteIPes
}));
── Càrrega oberta: Iván ──
  Redissenyar la sala polivalent (12 h)
  Guia sobre enquadernació per a residents (8 h)
  Pressupost de la fusteria (5 h)
  3 tasca(s) · esforç ponderat 67
// ─── Informe 3: alertes de venciment ───────────────────────────────
console.log(generarInforme(indexs, {
  titol: '⚠ Tasques vençudes',
  filtre: vencuda,
  transformar: (i) => `${titols[i]} — responsable: ${responsables[i]} — va vèncer el ${datesLimit[i]}`,
  agregar: (seleccio) => `${seleccio.length} tasca(s) requereixen atenció immediata`
}));
── ⚠ Tasques vençudes ──
  Pressupost de la fusteria — responsable: Iván — va vèncer el 2026-09-05
  1 tasca(s) requereixen atenció immediata

Tres informes completament diferents, zero canvis al motor. Això és el que compren les funcions d'ordre superior: quan arribi el Mòdul 6 i la Marta vulgui filtrar el backlog des d'un desplegable de la interfície, l'única cosa que canviarà és quina funció es passa com a filtre.

  1. Una nota sobre llegibilitat

Tot l'anterior és potent, i precisament per això convé dir quan no fer-ho servir. L'estil funcional té un punt de rendiments decreixents.

// ✗ Il·legible: currying + composició + reduce imbricat per a una cosa trivial
const total = pipe(
  (xs) => elMeuFilter(xs, (i) => estats[i] !== 'feta'),
  (xs) => elMeuReduce(xs, (a, i) => a + hores[i], 0)
)(indexs);

// ✓ Igual de correcte i molt més clar
let total = 0;
for (let i = 0; i < hores.length; i++) {
  if (estats[i] !== 'feta') total += hores[i];
}

Criteris per decidir:

Fes servir HOF quan… Fes servir un bucle normal quan…
L'operació es repeteix amb variants És un càlcul puntual i simple
El "què fer" ha de poder canviar El comportament és fix
Vols anomenar cada pas (filtrar, transformar) El bucle cap en quatre línies llegibles
Reutilitzaràs les peces Només es fa servir un cop
Talles la lògica en trossos provables Necessites break, continue o índexs retorçats

I una regla d'or: el codi s'escriu una vegada i es llegeix cinquanta. Si la teva companya ha de dibuixar un diagrama per entendre una línia, aquesta línia està malament, per elegant que sigui.

Errors Habituals i Consells

1. Passar el resultat en lloc de la funció.

elMeuFilter(indexs, esDeIvan(0));   // ✗ passa `true`, no una funció
elMeuFilter(indexs, esDeIvan);      // ✓

Símptoma: TypeError: predicat is not a function.

2. Oblidar el return al reductor.

elMeuReduce(responsables, (acc, n) => { acc[n] = (acc[n] ?? 0) + 1; }, {});
// ✗ TypeError: Cannot set properties of undefined

Amb cos de bloc, return acc; és obligatori.

3. Fer servir map pel seu efecte secundari. Si no fas servir l'array retornat, volies forEach.

4. Intentar tallar un forEach amb break. No és possible. Fes servir un for clàssic.

5. Mutar l'array original dins d'un callback. Modificar la llista mentre es recorre produeix salts i elements repetits. Treballa sempre sobre el resultat.

6. Callbacks amb efectes secundaris ocults. Un predicat que a més imprimeix o modifica alguna cosa fa impossible raonar sobre el filtre. Els predicats han de ser purs (03-03).

7. Consell: anomena els callbacks que es repeteixen. elMeuFilter(indexs, noFeta) es llegeix infinitament millor que elMeuFilter(indexs, (i) => estats[i] !== 'feta') repetit set vegades.

8. Consell: documenta la signatura esperada. Quan escriguis una HOF, deixa clar en un comentari què rebrà el callback i què ha de retornar. És el primer que necessita qui la faci servir.

Exercicis

Exercici 1 — elMeuSome, elMeuEvery i elMeuFind

Implementa tres funcions d'ordre superior més, amb la mateixa signatura de callback (element, index, llista):

  • elMeuSome(llista, predicat): true si almenys un compleix; talla tan bon punt en troba un.
  • elMeuEvery(llista, predicat): true si tots compleixen; talla tan bon punt en falla un.
  • elMeuFind(llista, predicat): retorna el primer element que compleix, o undefined.

Fes-les servir per respondre: hi ha alguna tasca vençuda? Estan totes assignades? Quina és la primera tasca de més de 10 h?

Exercici 2 — agruparPer

Escriu agruparPer(llista, obtenirClau), una funció d'ordre superior que retorni un objecte on cada clau és el resultat d'obtenirClau(element) i cada valor és un array amb els elements d'aquest grup. Fes-la servir per agrupar els índexs del backlog per responsable i per estat, i després calcula les hores obertes de cada grup.

Exercici 3 — Motor d'informes ampliat

Amplia generarInforme perquè accepti dues opcions més:

  • limit: nombre màxim de línies a mostrar (si es retalla, afegeix una línia … i N més).
  • formatCapcalera: una funció (titol, total) => string que construeixi la capçalera.

Genera-hi un informe "Top 3 per hores" de la feina oberta.

Solucions

Exercici 1

function elMeuSome(llista, predicat) {
  for (let i = 0; i < llista.length; i++) {
    if (predicat(llista[i], i, llista)) return true;   // talla aquí
  }
  return false;
}

function elMeuEvery(llista, predicat) {
  for (let i = 0; i < llista.length; i++) {
    if (!predicat(llista[i], i, llista)) return false; // talla aquí
  }
  return true;
}

function elMeuFind(llista, predicat) {
  for (let i = 0; i < llista.length; i++) {
    if (predicat(llista[i], i, llista)) return llista[i];
  }
  return undefined;
}

console.log(elMeuSome(indexs, vencuda));                          // true
console.log(elMeuEvery(indexs, (i) => responsables[i] !== null)); // true
console.log(elMeuFind(indexs, (i) => hores[i] > 10));             // 0

const trobada = elMeuFind(indexs, (i) => hores[i] > 10);
console.log(titols[trobada]);   // Redissenyar la sala polivalent

Comentari: fixa't que elMeuSome i elMeuEvery que poden tallar, perquè el return és al bucle de la funció d'ordre superior, no al callback. Aquesta és la diferència amb elMeuForEach. elMeuFind retorna undefined quan no troba res, no -1 ni null: és coherent amb el que fa el mètode natiu que veuràs a 04-05.

Exercici 2

function agruparPer(llista, obtenirClau) {
  return elMeuReduce(llista, (grups, element, index) => {
    const clau = obtenirClau(element, index, llista);
    if (!(clau in grups)) grups[clau] = [];
    grups[clau].push(element);
    return grups;                        // ← imprescindible
  }, {});
}

const perResponsable = agruparPer(indexs, (i) => responsables[i]);
console.log(perResponsable);
// { 'Iván': [ 0, 4, 5 ], Marta: [ 1, 3 ], 'Lucía': [ 2 ] }

const perEstat = agruparPer(indexs, (i) => estats[i]);
console.log(perEstat);
// { 'en-curs': [ 0, 4 ], pendent: [ 1, 2, 5 ], feta: [ 3 ] }

// Hores obertes per responsable
const noms = ['Iván', 'Marta', 'Lucía'];
elMeuForEach(noms, (nom) => {
  const grup = perResponsable[nom] ?? [];
  const obertes = elMeuReduce(elMeuFilter(grup, noFeta), (acc, i) => acc + hores[i], 0);
  console.log(`${nom}: ${obertes} h obertes · marge ${40 - obertes} h`);
});
// Iván: 25 h obertes · marge 15 h
// Marta: 6 h obertes · marge 34 h
// Lucía: 14 h obertes · marge 26 h

Comentari: agruparPer és una HOF que rep un extractor de clau i retorna un objecte de grups. És una de les funcions auxiliars més útils que existeixen i apareix a totes les llibreries d'utilitats. El return grups del reductor és el punt on més gent s'equivoca.

Exercici 3

function generarInforme(font, configuracio) {
  const filtre          = configuracio.filtre          ?? (() => true);
  const transformar     = configuracio.transformar     ?? ((x) => String(x));
  const agregar         = configuracio.agregar         ?? (() => '');
  const ordenar         = configuracio.ordenar         ?? null;
  const limit           = configuracio.limit           ?? Infinity;
  const formatCapcalera = configuracio.formatCapcalera ?? ((t, n) => `── ${t} (${n}) ──`);

  let seleccionats = elMeuFilter(font, filtre);
  if (ordenar !== null) seleccionats = ordenarIndexs(seleccionats, ordenar);

  const total = seleccionats.length;
  const visibles = elMeuFilter(seleccionats, (_, index) => index < limit);
  const ocultes = total - visibles.length;

  const sortida = [formatCapcalera(configuracio.titol, total)];
  elMeuForEach(elMeuMap(visibles, transformar), (linia) => sortida.push(`  ${linia}`));
  if (ocultes > 0) sortida.push(`  … i ${ocultes} més`);
  sortida.push(`  ${agregar(seleccionats)}`);

  return elMeuReduce(sortida, (acc, l) => `${acc}\n${l}`, '').trim();
}

console.log(generarInforme(indexs, {
  titol: 'Top 3 per hores',
  filtre: noFeta,
  ordenar: perHoresDesc,
  transformar: (i) => `${hores[i]} h — ${titols[i]} (${responsables[i]})`,
  agregar: sumaHores,
  limit: 3,
  formatCapcalera: (t, n) => `▓▓ ${t.toUpperCase()} · ${n} tasques obertes ▓▓`
}));
▓▓ TOP 3 PER HORES · 5 tasques obertes ▓▓
  14 h — Actualitzar el web de reserves (Lucía)
  12 h — Redissenyar la sala polivalent (Iván)
  8 h — Guia sobre enquadernació per a residents (Iván)
  … i 2 més
  Total: 45 h

Comentari: l'agregar continua rebent tots els seleccionats, no només els visibles, perquè el total continuï sent el real (45 h) i no el de les tres línies mostrades. És una decisió de disseny que convé fer explícita; la contrària també seria defensable, però cal triar-ne una i documentar-la. Fixa't també en l'ús de _ com a nom del paràmetre que no es fa servir a elMeuFilter(seleccionats, (_, index) => ...): és una convenció molt estesa per dir "aquest argument existeix però l'ignoro".

Conclusió

Ja tens l'eina que converteix codi repetitiu en codi configurable. Una funció d'ordre superior rep funcions, retorna funcions o totes dues coses, i el seu valor és a separar l'esquelet fix —recórrer, filtrar, ordenar, informar— del detall variable que li passes com a callback. Has après el vocabulari amb precisió: callback, predicat, transformador, comparador, reductor, i el fet fonamental que és la funció d'ordre superior, i no pas tu, qui decideix quan s'invoca el callback i amb quins arguments.

Has implementat a mà les quatre operacions fonamentals sobre llistes: elMeuForEach (efecte sobre cada element, sense retorn), elMeuMap (array nou de la mateixa mida amb cada element transformat), elMeuFilter (array nou amb els que compleixen un predicat) i elMeuReduce (tot reduït a un valor mitjançant un acumulador). Saber que caben en quatre o cinc línies cadascuna desmitifica per complet els mètodes natius que estudiaràs a Iteració sobre Arrays i Cercar, Ordenar i Agregar Dades: quan els facis servir, sabràs exactament què està passant per dins.

I has vist els tres patrons que es construeixen a sobre: fàbriques i currying, que fixen arguments i produeixen funcions especialitzades (crearPredicat, tots, algun, no, esforcCurry); composició amb compondre i pipe, que encadena transformacions petites en una canonada llegible —la que normalitza etiquetes segons la R9—; i funcions com a configuració, que van donar lloc al motor d'informes de Taller Nómada: tres informes radicalment diferents sense tocar ni una sola línia del motor. Juntament amb l'advertiment final, que importa tant com la resta: si un bucle de quatre línies és més clar, escriu el bucle.

Queda una última manera de resoldre problemes amb funcions, i és la més desconcertant de totes: una funció que es crida a si mateixa. Ja l'has fregat dues vegades —l'expressió de funció amb nom de 03-02 servia per a això, i el RangeError de 03-05 va aparèixer per una crida que no s'aturava mai—. A Recursivitat hi posaràs mètode: cas base, cas recursiu, la pila de crides dibuixada pas a pas, i el problema real que la justifica a Nómada Tasques: recórrer les subtasques imbricades d'una tasca per sumar-ne les hores.

Curs de JavaScript: De Principiant a Avançat

Mòdul 1: Introducció a JavaScript

Mòdul 2: Estructures de Control

Mòdul 3: Funcions

Mòdul 4: Objectes i Arrays

Mòdul 5: Objectes i Funcions Avançades

Mòdul 6: El Model d'Objectes del Document (DOM)

Mòdul 7: APIs del Navegador i Temes Avançats

Mòdul 8: Proves i Depuració

Mòdul 9: Rendiment i Optimització

Mòdul 10: Frameworks i Llibreries de JavaScript

Mòdul 11: Projecte Final

© Copyright 2026. Tots els drets reservats