La programmazione funzionale non riguarda l'assenza di stato. Invece, tutta quella roba di immutabilità riguarda il rendere esplicito lo stato. Un semplice esempio è l'aggiunta di un elenco di numeri. In Python, faremmo qualcosa del genere:
xs = [1, 2, 3, 4, 5]
sum = 0
for x in xs:
sum += x
(In realtà, utilizzeremmo sum
incorporato, ma questo è solo un esempio). Questo ciclo ha lo stato, poiché mutiamo la variabile sum
a ogni iterazione. La variabile sum
non è referenzialmente trasparente, perché punta a valori diversi in diversi punti di tempo. Lo stato è codificato nel flusso di controllo. Scala non è dogmatico sulla programmazione funzionale e ti permette di scrivere codice in modo statico. Tuttavia, sei incoraggiato a limitare lo stato implicito.
Come possiamo rendere esplicito lo stato, o sbarazzarci di esso quando si sommano i numeri? Possiamo definire la somma di una lista come zero se la lista è vuota, e il primo elemento più la somma se gli altri elementi sono altrimenti. Si noti che questa è una definizione ricorsiva. In Scala, lo scriveremo come
val xs = Seq(1, 2, 3, 4, 5)
def summer(xs: Seq[Int]): Int = xs match {
case Seq() => 0
case x :: rest => x + summer(rest)
}
val sum = summer(xs)
Da notare che mentre abbiamo fatto ricorsivamente il ciclo for … in …
, non c'è ancora uno stato ovvio. Questo perché la funzione ricorsiva utilizza lo stack di chiamate per memorizzare valori intermedi. Lo stato del loop diventa esplicito se lo scriviamo come funzione ricorsiva di coda:
import scala.annotation.tailrec
@tailrec
def summer(xs: Seq[Int], acc: Int = 0): Int = xs match {
case Seq() => acc
case x :: rest => summer(rest, x + acc)
}
L'argomento acc
(accumulator) rappresenta qui lo stato. Non è mutato, ma creiamo un nuovo stato per ogni iterazione.
In effetti, eseguire un'operazione del genere è così comune che di solito viene eseguito tramite fold
(o foldLeft
, che consente al risultato di essere di un tipo diverso):
def summer (xs: Seq[Int]): Int = (xs fold 0) (_ + _)
Come si configura lo stato esplicito con la programmazione orientata agli oggetti? Abbastanza bene, in realtà: l'oggetto è lo stato. Pensa all'attuale oggetto come a un parametro che viene passato invisibilmente in qualsiasi chiamata di metodo. Tuttavia, non si modifica mai lo stato e ne si crea uno nuovo. Attenzione però: questo può rendere orribilmente inefficiente alcune strutture dati se non progettate pensando all'immutabilità.
Che cosa significa per implementare un'API REST? Niente. È compito del programmatore decidere quali paradigmi e modelli sono adatti in ogni situazione specifica. Usare lo stato esplicito può avere dei benefici, ma potrebbe anche essere l'offuscamento di ciò che sta realmente accadendo. In ogni caso, diverse parti del programma possono utilizzare diverse quantità di programmazione funzionale. L'utilizzo dei pattern FP è possibile anche su una scala piuttosto ridotta e non richiede che l'intero programma sia scritto in assoluta "purezza".