Introduzione

La programmazione funzionale - sebbene non costituisca il paradigma “predefinito” di JavaScript - è pienamente supportata dal linguaggio fin dalle sue origini. Riferendosi ai capitoli 6 (Array) e 7.8 (Functional Programming) del JavaScript: The Definitive Guide, 7ᵃ edizione, questo approccio incentra la costruzione del software sull’uso intensivo di funzioni, privilegiando espressività e concisione rispetto ai tradizionali pattern imperativi basati su cicli e istruzioni di controllo.

Paradigma dichiarativo e leggibilità

Nel paradigma funzionale il codice tende a descrivere che cosa si vuole ottenere piuttosto che come ottenerlo passo-passo. Un esempio emblematico è l’operatore filter: invece di iterare manualmente su un array, valutare una condizione e popolare un nuovo contenitore, si delega tutto a una singola espressione dichiarativa. Questo stile riduce il rumore sintattico, concentra l’attenzione sulla logica del problema e - se usato con criterio - migliora la leggibilità complessiva del programma.

Synchronous callbacks: l’esempio di filter

const market = [
	{ name: 'GOOG', var: -3.2 },
	{ name: 'AMZN', var: 2.2 },
	{ name: 'MSFT', var: -1.8 }
];

Invocare market.filter(stock => stock.var < 0) restituisce immediatamente (callback sincrona) un nuovo array contenente i titoli in perdita, mentre market.filter(stock => stock.var > 0) produce la lista di quelli in guadagno. Il punto chiave è duplice: la funzione di callback viene eseguita sincronicamente su ogni elemento e, soprattutto, l’array originale rimane intatto; il metodo genera sempre un nuovo valore, preservando l’immutabilità.

Caratteristiche distintive del paradigma

  1. Funzioni come first-class citizens: possono essere assegnate a variabili, passate come argomenti, restituite da altre funzioni e combinate in modi creativi.
  2. Funzioni di ordine superiore: operano su funzioni (le ricevono o le producono) e rappresentano il cuore della programmazione funzionale.
  3. Composizione di funzioni: consente di costruire pipeline compatte, in cui l’output di una funzione diventa l’input della successiva, riducendo boilerplate e favorendo la modularità.
  4. Call chaining: molti operatori restituiscono un valore dello stesso tipo dell’argomento (ad es. un array), permettendo concatenazioni eleganti senza variabili temporanee.

Immutabilità come requisito

Per praticare genuinamente la programmazione funzionale in JavaScript è essenziale evitare effetti collaterali e mutazioni in-place. Se devi trasformare un array, crea e costruisci una copia modificata; se occorre aggiornare un oggetto, usa spread operator o metodi che non alterano lo stato originale. Questa disciplina rende il flusso dei dati predicibile, semplifica il reasoning sull’applicazione e riduce l’incidenza di bug difficili da tracciare.

Iterating over Arrays

Quando si lavora con strutture dati lineari in JavaScript, la necessità ricorrente è percorrere un array per elaborarne o valutarne gli elementi. L’approccio imperativo tradizionale - i costrutti for ... of o for(inizio; condizione; passo) - offre controllo esplicito sull’indice e sul flusso ma tende a produrre codice verboso e meno espressivo. La comunità JavaScript privilegia da tempo un paradigma funzionale che riduce il boilerplate e rende l’intento più evidente: le funzioni di iterazione integrate nell’oggetto Array.

Il primo strumento di questa famiglia è forEach(f). Riceve una callback f invocata per ogni elemento, nell’ordine in cui compaiono. È la scelta giusta quando l’obiettivo è eseguire un’azione con effetto collaterale su ogni item (log, mutazioni di stato esterno, chiamate di rete) senza produrre un valore di ritorno significativo: l’array originario rimane invariato e forEachE restituisce sempre undefined.

Quando lo scopo è verificare condizioni logiche sull’insieme, entrano in gioco every(f) e some(f). Il primo restituisce true soltanto se f è vera per tutti gli elementi, interrompendo l’iterazione al primo fallimento; il secondo restituisce true se f è vera per almeno uno, terminando non appena la condizione è soddisfatta. Entrambe le funzioni forniscono dunque una risposta booleana immediata, con breve-circuito che evita iterazioni inutili.

Per trasformare i dati mantenendo l’immutabilità dell’array originale, si ricorre a map(f) e filter(f). map applica f a ciascun elemento e costruisce un nuovo array con i risultati, preservando la lunghezza; filter applica f come predicato e restituisce un array che contiene solo gli elementi per cui f è vera, potenzialmente di dimensione diversa. In entrambi i casi si ottiene un oggetto distinto, ideale per pipeline funzionali prive di effetti collaterali.

Infine, reduce(callback, initialValue) conferisce il massimo potere espressivo: attraversa l’intero array accumulando progressivamente un risultato. Alla callback vengono passati in sequenza l’accumulatore, il valore corrente (opzionalmente l’indice e l’array stesso) e, se specificato, un valore iniziale. Con questa singola astrazione è possibile calcolare somme, concatenazioni, strutture aggregate o anche derivare oggetti complessi, in modo dichiarativo e compatto.