cat swift/map-compactmap-flatmap-filter-reduce.md
Map, compactMap, flatMap, filter e reduce
Le trasformazioni sulle collection sono idiomatiche quando raccontano il flusso dei dati senza comprimere il dominio.
Swift offre molte funzioni per trasformare collection. Le più comuni sono:
map;compactMap;flatMap;filter;reduce.
Usate bene, rendono il codice più dichiarativo: descrivi cosa vuoi ottenere, non ogni passo meccanico. Usate male, creano catene dense e difficili da leggere.
map: trasformare ogni elemento
let nomi = prodotti.map { prodotto in
prodotto.nome
}
map prende ogni elemento è lo trasforma in qualcos’altro.
Esempio nel carrello:
let subtotali = carrello.righe.map { riga in
riga.subtotaleInCent
}
Il numero di elementi resta lo stesso. Cambia il tipo o il valore.
filter: tenere solo ciò che serve
let prodottiCostosi = prodotti.filter { prodotto in
prodotto.prezzoInCent > 5000
}
filter non trasforma gli elementi. Decide quali restano.
let righeValide = carrello.righe.filter { riga in
riga.quantita > 0
}
Se una condizione ha significato di dominio, valuta un metodo:
let righeValide = carrello.righe.filter(\.isValida)
compactMap: trasformare e scartare nil
compactMap è utile quando la trasformazione può fallire.
let codici = stringhe.compactMap { CodiceCoupon($0) }
Se CodiceCoupon($0) restituisce nil, quell’elemento viene scartato.
Esempio:
let couponValidi = inputCodici.compactMap { codice in
CodiceCoupon(codice)
}
Attenzione: scartare valori non validi può essere corretto, ma a volte vuoi raccogliere errori. In quel caso compactMap nasconde informazioni.
flatMap: appiattire collection annidate
Se hai array dentro array:
let categoriePerOrdine: [[CategoriaProdotto]] = ordini.map { ordine in
ordine.categorie
}
let tutteLeCategorie = categoriePerOrdine.flatMap { $0 }
Oppure direttamente:
let tutteLeCategorie = ordini.flatMap { ordine in
ordine.categorie
}
flatMap è ottimo quando ogni elemento produce una collection e vuoi un’unica collection finale.
reduce: accumulare
let totale = carrello.righe.reduce(0) { totale, riga in
totale + riga.subtotaleInCent
}
reduce prende un valore iniziale e accumula un risultato.
È potente, ma può diventare poco leggibile se l’accumulatore è complesso.
Se stai costruendo un dizionario o una struttura articolata, valuta un ciclo esplicito.
Catene leggibili
Una catena può essere molto chiara:
let totaleLibri = carrello.righe
.filter { riga in riga.prodotto.categoria == .libri }
.map { riga in riga.subtotaleInCent }
.reduce(0, +)
Si legge come:
- prendi le righe;
- tieni quelle dei libri;
- estrai i subtotali;
- somma.
Questa è una buona catena.
Catene troppo dense
let x = a.flatMap { $0.b }.compactMap { C($0) }.filter { $0.v > 0 }.reduce(0) { $0 + $1.v }
Il problema non sono le funzioni. Il problema è che i nomi non comunicano.
Meglio:
let prodotti = ordini.flatMap { ordine in
ordine.prodotti
}
let coupon = prodotti.compactMap { prodotto in
Coupon(codice: prodotto.codicePromo)
}
let couponApplicabili = coupon.filter { coupon in
coupon.puoEssereApplicato
}
let scontoTotale = couponApplicabili.reduce(0) { totale, coupon in
totale + coupon.scontoInCent
}
Più righe, meno mistero.
Key path
Quando estrai una proprietà semplice, puoi usare key path:
let nomi = prodotti.map(\.nome)
È leggibile se la trasformazione è banale. Se devi aggiungere logica, torna alla closure esplicita.
Quando preferire un ciclo
Un ciclo e perfettamente idiomatico quando:
- hai più condizioni con nomi intermedi;
- devi gestire errori;
- vuoi uscire presto;
- l’accumulazione e complessa;
- la catena diventerebbe difficile da debuggare.
var errori: [ErroreCoupon] = []
for regola in coupon.regole {
do {
try engine.valida(regola, nel: contesto)
} catch let errore as ErroreCoupon {
errori.append(errore)
}
}
Questo è chiaro. Non serve forzarlo in reduce.
Checklist
Usa:
mapper trasformare;filterper selezionare;compactMapper trasformazioni opzionali scartandonil;flatMapper appiattire collection;reduceper accumulare.
Fermati quando:
- i nomi diventano
$0,$1,$2senza chiarezza; - la catena supera 3-4 passaggi non banali;
- stai nascondendo errori;
- un ciclo direbbe meglio l’intenzione.
Esercizio
Sul dominio carrello, scrivi:
- elenco dei nomi prodotto;
- totale delle righe;
- totale solo per una categoria;
- lista dei codici coupon validi usando
compactMap; - lista piatta di categorie da più ordini usando
flatMap; - raccolta errori con ciclo esplicito, spiegando perché non usi
reduce.