cat swift/type-inference-safety-espressivita.md

Lezione 05 35 min

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 è una String;
  • prezzoInCent è un Int;
  • disponibile è un Bool.

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 Int o una String sarebbe troppo generico;
  • vuoi impedire combinazioni errate;
  • stai progettando API pubbliche o condivise.

Esercizio

Nel dominio carrello, crea tipi dedicati per:

  • Prezzo
  • Quantita
  • ScontoPercentuale

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.