Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Programmazione funzionale

Alcuni esempi di come i principi di programmazione funzionale possono essere sfruttati in Swift, utilizzando le closure e le funzioni map, reduce e filter.
Alcuni esempi di come i principi di programmazione funzionale possono essere sfruttati in Swift, utilizzando le closure e le funzioni map, reduce e filter.
Link copiato negli appunti

Da quanto illustrato fino ad ora, è chiaro che Swift è un linguaggio di programmazione orientato agli oggetti, sia per il fatto di esser stato progettato per sostituire Objective-C sia per renderlo adatto al framework Cocoa, usato per lo sviluppo di app per iOS e OS X.

Tuttavia i designer di Swift hanno pensato di introdurre nel linguaggio dei costrutti che permettessero allo sviluppatore di adottare altri stili di programmazione, in particolare quello funzionale.

Nello stile di programmazione orientato agli oggetti, il programmatore usa gli oggetti per astrarre i problemi e fornire le relative soluzioni, mentre nello stile funzionale, l'unita base di astrazione è la funzione.

Swift è dunque un linguaggio di programmazione ibrido, dato che supporta sia lo stile object-oriented che quello funzionale.

Principi della programmazione funzionale

Alla base della programmazione funzionale sta il concetto di uso delle funzioni nel senso matematico del termine.

Se consideriamo lo spazio percorso x da un oggetto che si muove con una velocità costante v in un dato tempo t, possiamo determinare univocamente x con una funzione matematica:

x = v * t

In Swift questo verrebbe tradotto in:

func spazioPercorso(v: Double, t: Double) -> Double {
	return v * t
}

La funzione spazioPercorso restituirà sempre il medesimo valore per gli stessi input v e t.

Consideriamo ora la seguente funzione Swift:

func spazioPercorso(v: Double, t: Double) -> Double {
	let random = arc4random_uniform(2) + 1
	return v * t * Double(random)
}

Questa funzione Swift non restituisce sempre il medesimo risultato per le stesse coppie di valori di input. Questa dunque non modella una funzione matematica, pertanto tale funzione non è conforme allo stile di programmazione funzionale, in cui il risultato di una funzione è sempre ben determinato dai parametri di input, così come avviene per le funzioni matematiche.

Il secondo principio della programmazione funzionale è basato sul concetto di immutabilità. Questo in Swift si traduce nel preferire l'uso delle costanti (definite con let) alle variabili, in modo da modellare il concetto delle variabili matematiche:

y = 3

in Swift:

let y = 3

Il terzo principio è basato sull'utilizzo delle funzioni di ordine superiore (high order function): si tratta di funzioni che accettano altre funzioni come parametri, trattando quindi le funzioni come valori. Le closure in Swift ci permettono di implementare questa terza pratica.

Queste tre pratiche permettono di scrivere codice che risulta lineare e ben deterministico, principi alla base dello stile di programmazione funzionale.

Funzioni di ordine superiore: map, filter e reduce

Swift include 3 funzioni di ordine superiore presenti in tutti i linguaggio funzionali: map, filter e reduce.

La funzione map opera su una collezione di oggetti, applicando ad ogni singolo elemento una trasformazione, e restituendo una nuova collezione con gli oggetti trasformati.

Supponiamo ad esempio di avere una collezione di nomi, rappresentata in Swift da un array di String:

var names = ["antonio", "filippo", "salvatore", "simone"]

Se volessimo convertire ogni elemento della collezione in lettere maiuscole potremmo utilizzare il seguente codice:

var capitalNames: [String] = []
for name in names {
	capitalNames.append(name.uppercaseString)
}

Se volessimo risolvere il precedente problema con un approccio funzionale, potremmo utilizzare map:

var upperNames = names.map({ (name: String) -> String in return name.uppercaseString })

map è un metodo automaticamente disponibile su tutte i tipi che implementano il protocollo SequenceType, tra cui Array. Esso richiede un solo parametro, una funzione di trasformazione da applicare ad ogni elemento. Nell'esempio precedente abbiamo utilizzato una closure. Possiamo semplificare ulteriormente la sintassi del precedente codice nei seguenti modi:

var upperNames2 = names.map({ (name: String) -> String in name.uppercaseString })
var upperNames3 = names.map({ (name: String) in name.uppercaseString })
var upperNames4 = names.map({ name in name.uppercaseString })
var upperNames5 = names.map({ $0.uppercaseString })
var upperNames6 = names.map() { $0.uppercaseString }

sfruttando il meccanismo di type inference e della trailing closure illustrata nella prima parte di questo articolo.

Il metodo filter permette di filtrare una collezione di elementi in base al risultato di un predicato funzionale: una funzione che accetta un singolo elemento come parametro e restituisce true o false. filter applica il predicato ad ogni singolo elemento della collezione, restituendo una nuova collezione con i soli elementi il cui predicato ritorna true:

var namesWithA = upperNames6.filter({$0.containsString("A")})

namesWithA è un nuovo array con i soli nomi che contengono la lettera "A" maiuscola.

map e filter permettono di trasformare una collezione in una nuova collezione. reduce, invece, permette di trasformare una collezione in qualsiasi altro tipo. reduce richiede come parametro una funzione capace di combinare un elemento della collezione di input con un risultato intermedio. Ogni singolo elemento della collezione verrà combinato con il risultato dell'iterazione precedente, e il valore finale verrà restituito al termine di tutte le iterazioni.

reduce accetta due parametri di input: un valore iniziale e una funzione di combinazione.

Se dovessimo sommare i valori numerici di un array di interi, usando il metodo reduce potremmo scrivere quanto segue:

var numeri = [1,2,3,4,5]
var somma = numeri.reduce(0, combine: {$0 + $1})

o ancora più brevemente:

var somma2 = numeri.reduce(0, combine: +)

Con reduce possiamo riscrivere codice che fa uso di map o filter. Ad esempio, la seguente funzione:

func namesWithLetter(letter: String, list: [String]) -> [String] {
	return list.reduce([], combine: {
		$0 + ($1.containsString(letter) ? [$1]: [])
	})
}

fa uso del metodo reduce per implementare una generica funzione che filtra i nomi di un array che contengono una data lettera:

namesWithLetter("A", list: upperNames4)

che in precedenza avevamo scritto con il metodo filter.

Ti consigliamo anche