cat swift/type-inference-safety-espressivita.md
Type inference, safety ed espressività
Come Swift bilancia codice compatto e tipi forti: scrivere meno senza diventare ambigui.
Swift ha un sistema di tipi forte, ma non ti costringe a scrivere il tipo ovunque. Questo equilibrio si chiama type inference: il compilatore deduce il tipo quando il contesto è abbastanza chiaro.
let nome = "Libro"
let prezzoInCent = 1900
let disponibile = true
Swift capisce:
nomeè unaString;prezzoInCentè unInt;disponibileè unBool.
Il punto non è risparmiare caratteri. Il punto è togliere rumore quando il significato è già evidente.
I tipi restano forti anche se non li scrivi
Type inference non significa tipizzazione debole.
let prezzoInCent = 1900
// prezzoInCent = "gratis" // errore
Il tipo è stato dedotto, ma esiste. Swift non sta rinunciando alla sicurezza: sta evitando ridondanza.
Quando annotare il tipo
Non bisogna trasformare la type inference in una gara a scrivere meno.
Annota il tipo quando:
- il valore iniziale non comunica abbastanza;
- vuoi chiarire un’intenzione;
- il tipo dedotto sarebbe troppo generico o diverso da quello desiderato;
- stai dichiarando una proprietà pubblica;
- stai modellando un concetto di dominio.
Esempio:
let sconto = 10
10 potrebbe essere percentuale, euro, punti, quantità. Il tipo Int è corretto, ma il dominio resta debole.
Meglio:
struct Percentuale {
let valore: Int
}
let sconto = Percentuale(valore: 10)
Qui la sicurezza non viene solo dal tipo primitivo. Viene dal concetto che hai modellato.
Safety: errori prima dell’esecuzione
Swift cerca di spostare molti errori dal runtime al compile-time.
Questo non compila:
let totale = "19" + 90
Il compilatore non prova a indovinare cosa intendi. Vuoi concatenare stringhe? Vuoi sommare numeri? Devi dirlo.
let totaleTestuale = "19" + "90"
let totaleNumerico = 19 + 90
Questa severità è una forma di cura: meno sorprese in produzione.
Espressività: tipi che parlano
Il codice Swift buono tende a leggere bene perché i tipi portano significato.
Versione debole:
func applicaSconto(prezzo: Int, sconto: Int) -> Int {
prezzo - (prezzo * sconto / 100)
}
Funziona, ma non dice molto:
prezzoè in euro o centesimi?scontoè percentuale o importo?- uno sconto negativo è valido?
Versione più espressiva:
struct Prezzo {
let centesimi: Int
}
struct ScontoPercentuale {
let valore: Int
}
func applicaSconto(_ sconto: ScontoPercentuale, a prezzo: Prezzo) -> Prezzo {
let riduzione = prezzo.centesimi * sconto.valore / 100
return Prezzo(centesimi: prezzo.centesimi - riduzione)
}
Non è sempre necessario creare tipi per tutto. Ma quando un valore ha un significato di dominio importante, un tipo dedicato rende il codice più sicuro è più leggibile.
Inference e closure
La type inference diventa molto utile con funzioni e closure.
let prezzi = [1900, 2990, 4990]
let prezziScontati = prezzi.map { prezzo in
prezzo - (prezzo / 10)
}
Swift sa che prezzo è un Int perché prezzi e [Int].
Possiamo anche scrivere:
let prezziScontati = prezzi.map { $0 - ($0 / 10) }
Questa forma è compatta, ma va usata con giudizio. Se la closure diventa complessa, nomina i parametri.
Compattezza non significa chiarezza
Questo codice è breve:
let r = p.filter { $0.q > 0 }.map { $0.v }.reduce(0, +)
Ma è difficile da leggere.
Una versione leggermente più lunga può essere molto migliore:
let righeValide = righe.filter { riga in
riga.quantita > 0
}
let subtotali = righeValide.map { riga in
riga.subtotaleInCent
}
let totale = subtotali.reduce(0, +)
Swift ti permette di essere espressivo. Sta a te non confondere espressività con densità.
Il confine tra tipo e validazione
Un tipo può rendere impossibili molti errori, ma non tutti.
struct Quantita {
let valore: Int
}
let quantita = Quantita(valore: -3)
Abbiamo migliorato il significato, ma non abbiamo ancora impedito valori negativi.
Per farlo, possiamo usare un initializer fallibile:
struct Quantita {
let valore: Int
init?(_ valore: Int) {
guard valore > 0 else {
return nil
}
self.valore = valore
}
}
Ora una quantità non valida non può essere creata come valore normale:
let quantita = Quantita(-3) // nil
Questo è un esempio concreto di safety: il dominio protegge se stesso.
Checklist
Usa la type inference quando:
- il tipo è ovvio dal valore;
- l’annotazione sarebbe rumore;
- il contesto rende chiara l’intenzione.
Annota o crea tipi dedicati quando:
- il significato di dominio è importante;
- un
Into unaStringsarebbe troppo generico; - vuoi impedire combinazioni errate;
- stai progettando API pubbliche o condivise.
Esercizio
Nel dominio carrello, crea tipi dedicati per:
PrezzoQuantitaScontoPercentuale
Poi scrivi:
func subtotale(prezzo: Prezzo, quantita: Quantita) -> Prezzo
func applicaSconto(_ sconto: ScontoPercentuale, a prezzo: Prezzo) -> Prezzo
Usa initializer fallibili dove serve. L’obiettivo non è scrivere tanto codice: è impedire che un numero qualunque possa rappresentare qualunque cosa.