Si consideri la funzione sum
introdotta in precedenza:
def sum(f: Int => Int, a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sum(f, a + 1, b)
Essa era stata impiegata per definire le funzioni specializzate sumInts
, sumSquares
e sumFactorials
:
def sumInts(a: Int, b: Int) = sum(x => x, a, b)
def sumSquares(a: Int, b: Int) = sum(x => x * x, a, b)
def sumFactorials(a: Int, b: Int) = sum(factorial, a, b)
CIascuna di questa ultime tre funzioni prende come argomenti gli estremi dell’intervallo e li passa alla funzione sum
senza modificarli: in sostanza, si usa la funzione sum
a tre argomenti per definire delle funzioni a due argomenti, istanziando solo il primo argomento con un valore specifico. Ci si può allora chiedere se sia possibile evitare di passare gli ultimi due parametri all’atto della definizione delle funzioni sumInts
, ecc…, in modo da rendere le definizioni più compatte e meno ridondanti.
La risposta è sì, e la soluzione non richiede neanche nuove funizonalità del linguaggio: è sufficiente poter restituire le funzioni come valori dalle funzioni. L’idea è quella di definire sum
come una funzione che prende come argomento solo la funzione f: Int => Int
, e non dipende direttamente dagli estremi a
e b
, ma invece costruisce e restituisce un’altra funzione (Int, Int) => Int
che riceve gli argomenti a
e b
ed effettua la somma con la funzione f
prestabilita:
def sum(f: Int => Int): (Int, Int) => Int = {
def sumF(a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sumF(a + 1, b)
sumF
}
Si osservi che questa funzione è all’ordine superiore sia per il parametri f
che per il valore restituito, entrambi di tipo funzione.
Adesso, data questa versione di sum
, le funzioni sumInts
, ecc… possono essere definite in questo modo:
def sumInts = sum(x => x) // sumInts: (Int, Int) => Int
def sumSquares = sum(x => x * x) // sumSquares: (Int, Int) => Int
def sumFactorials = sum(factorial) // sumFactorials: (Int, Int) => Int
Infatti, quando si istanzia il primo (tecnicamente unico) argomento di sum
si ottiene una funzione nei restanti due argomenti, che può essere associata a un nome senza bisogno di specificare esplicitamente i restanti argomenti. E’ importante notare che si usa la sintassi dei nomi senza argomenti, ma i tipi di questi nomi sono tipi funzionali, dunque i nomi possono essere applicati come ogni altra funzione; ad esempio:
sumInts(2, 3) + sumSquares(2, 3) + sumFactorials(2, 3)
Volendo, le funzioni restituite da sum
potrebbero essere usate direttamente, senza associarle a dei nomi:
sum(x => x)(2, 3) + sum(x => x * x)(2, 3) + sum(factorial)(2, 3)
Questo funziona perchè, considerando ad esempio sum(factorial)(2, 3)
:
sum(factorial)
applica la funzione sum
alla funzione factorial
, dunque restituisce la funzione di somma dei fattoriali (quella che in precedenza si era chiamata sumFactorials
), il cui tipo è (Int, Int) => Int
;(2, 3)
In generale, l’applicazione di funzione è associativa a sinistra: ad esempio, l’espressione sum(factorial)(2, 3)
corrisponde a (sum(factorial))(2, 3)
.