Continuiamo il nostro excursus sul linguaggio Swift, introducendo uno dei costrutti basilari di ogni linguaggio di programmazione, che permette implementare la modularità: la funzione.
La definizione di una funzione in Swift è introdotta dalla parola chiave func
, seguita dal nome della funzione, i parametri, il tipo del valore di ritorno e il corpo della funzione:
func nome_funzione (parametri) -> tipo_valore_di_ritorno {
// corpo della funzione
// return valore_di_ritorno
}
Facciamo subito un esempio, definendo una funzione che stampa un messaggio di buongiorno sulla console:
func saluta(nome: String) {
print("Buongiorno, " + nome + "!")
}
Dato che Swift è fortemente tipizzato, per ogni parametro della funzione bisogna specificarne il tipo. Il tipo di ritorno può essere omesso, come nell'esempio precedente, se la funzione non restituisce nulla. In alternativa è possibile specificare il tipo Void
per indicare che non vi è un valore di ritorno.
Per invocare una funzione, basta usarne il nome con lo stesso numero e tipo di parametri della sua definizione (la cosidetta signature):
saluta(nome: "Antonio")
Quando chiamiamo una funziome, ogni argomento dovrà essere preceduto da un'etichetta, che normalmente è uguale al nome del parametro. È possibile tuttavia definire un'etichetta diversa per ogni argomento al momento dell'invocazione, specificandola prima del nome di ogni parametro::
func saluta(con messaggio: String, ripeti r: Int) {
for i in 0...r {
print(messaggio)
}
}
In questo caso lo sviluppatore, per invocare la precedente funzione, utilizzerà la sintassi seguente:
saluta(con: "Ciao a tutti", ripeti: 10)
Se, invece, non volessimo utilizzare alcuna etichetta per uno o più argomenti, possiamo utilizzare il simbolo _
prima di ogni parametro a cui non vogliamo assegnare un'etichetta::
func saluta(_ messaggio: String, ripetizioni: Int) {
for i in 0...ripetizioni {
print(messaggio)
}
}
Possiamo adesso invocare la funziona saluta
senza indicare l'etichetta messaggio:
func saluta("Good Morning", ripetizioni: 5)
Gli esempi precedenti hanno mostrato funzioni che non restituisco alcun valore (le cosiddette "procedure"). Vediamo invece un esempio di funzione che restituisce un tipo intero:
func somma(a: Int, b: Int) -> Int {
return a + b
}
var result = somma(3, 5) // result sarà di tipo Int
Come in C, il valore di ritorno di una funzione viene restituito con la parola riservata return
, che termina immediatamente la funzione. Nell'esempio precedente, result
sarà implicitamente di tipo Int
per il meccanismo di type inference di Swift.
Valori di default
È possibile definire dei valori di default per uno o più parametri di una funzione, in modo che questi possano opzionalmente essere omessi al momento della chiamata. In quel caso verrà utilizzato il valore di default utilizzato nella definizione. Per definire un valore di default per un argomento, basta assegnare un valore al parametro subito dopo il suo tipo:
func saluta(messaggio: String = "Buongiorno", nome: String) {
print("\(messaggio), \(nome)")
}
Il tipo funzione
In Swift, ogni funzione ha un tipo, formato dal tipo dei suoi parametri e da quello del valore di ritorno. Se consideriamo la precedente funzione somma, il suo tipo sarà:
(Int, Int) -> Int
Possiamo utilizzare il tipo funzione per qualsiasi variabile, costante, parametro, valore di ritorno, come tutti i tipi semplici che fino ad ora abbiamo incontrato. Ad esempio:
var operazioneMatematica: (Int, Int) -> Int?
[code lang=plain]
definisce una variabile che avrà come valore una funzione che prende in input due interi e restituisce opzionalmente un intero. Se consideriamo la seguente funzione:
[code lang=plain]
func divisione(primoOp: Int, secondoOp: Int) -> Int? {
if secondoOp == 0 { return nil }
return primoOp / secondoOp
}
potremo assegnare alla variabile operazioneMatematica
la funzione divisione
:
divisione(primoOp: 5, secondoOp: 2) // restituisce 2
var operazioneMatematica: (Int, Int) -> Int? = divisione
operazioneMatametica(5, 2) // restituisce 2
[code lang=plain]
Il tipo funzione può essere utilizzato anche per i parametri di una funzione e/o i valori di ritorno. Questo significa che possiamo passare funzioni come argomenti di un'altra funzione, o restiutire una funzione da una funzione. Swift, infatti, come i più moderni linguaggi di programmazione, grazie al tipo funzione, offre un supporto di prima classe alla programmazione funzionale.
Parametri In-out
Normalmente, i valori passati come argomenti di una funzione non possono essere modificati all'interno del corpo della stessa, perchè sono delle costanti. Tuttavia, è possibile marcare un parametro con la parola riservata inout, in modo poter modificare il valore di un parametro e rendere le modifiche persistenti al termine dell'esecuzione della funzione.
Per dimostrare questa caratteristica di Swift, utilizziamo un classico esempio della programmazione: implementiamo la funzione swap, che permette di scambiare il valore di due variabili:
[code lang=plain]
func swap(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
Definiamo ed inizializziamo due variabili e scambiamone i valori con la precedente funzione:
var firstValue = 5
var secondValue = 18
print("\(firstValue) \(secondValue)") // "5 18\n"
swap(&firstValue, &secondValue)
print("\(firstValue) \(secondValue)") // "18 5\n"
È da notare l'uso dell'operatore &
, da anteporre agli argomenti passati nella chiamata, per indicare che il valore potrà essere modificato all'interno della funzione.
Tuple
Le tuple sono un tipo composto, che permette di raggruppare insiemi di valori, anche di tipo diverso. Una tupla viene definita dalla lista dei suoi valori, separati da virgola tra parentesi. Ad esempio:
let serverResponse: (Int, String) = (400, "Not found")
è una tupla di tipo (Int, String)
. È possibile usare qualsiasi tipo come membro di una tupla, anche tipi opzionali. I seguenti sono 3 esempi di tuple di tipo diverso:
(Int, String)
(Int?, String?)
(Int, String)?
Data una tupla, è possibile accedere singolarmente ai suoi elementi in due modi:
-
usando un indice che fa riferimento alla posizione dell'elemento all'interno della tupla. Ad esempio:
let serverResponse = (400, "Not Found") print(serverResponse.0) print(serverResponse.1)
-
assegnando un nome alle componenti della tupla. Ad esempio:
let serverResponse = (statusCode: 400, errorMessage: "Not Found") println(serverResponse.statusCode) println(serverResponse.errorMessage)
È possibile decomporre una tupla in singole variabili o costanti con un'assegnazione:
let (statusCode, errorMessage) = (400, "Not Found")
if statusCode != 0 {
print("Error: \(errorMessage)")
}
Le tuple risultano particolarmente utili come valori di ritorno di funzioni che devono restituire più di un valore. La tupla dell'esempio precedente potrebbe essere restituita da una funzione che contatta un server Web, la cui signature sia, ad esempio:
func checkWebAPI(url: String) -> (Int, String?) {
let page_is_found: Bool
...
if page_is_found {
return (200, nil)
} else {
return (404, "not found")
}
}
Da notare che il secondo elemento della tupla è opzionale nel caso in cui non vi sia alcun errore.
Un altro esempio potrebbe essere quello di una funzione che calcola il baricentro di un triangolo, dati i tre vertici, rappresentati da tre tuple:
var p1 = (0, 4)
var p2 = (3, 5)
var p3 = (2, 5)
func baricentro(a: (Int, Int), b: (Int,Int), c: (Int, Int)) -> (Float, Float) {
var x = a.0 + b.0 + c.0
var y = a.1 + b.1 + c.1
return (Float(x)/3, Float(y)/3)
}
baricentro(a: p1, b: p2, c: p3)
Si noti che in tutti gli esempi precedenti abbiamo visto tuple composte da soli 2 elementi, ma di fatto una tupla può averne anche più di due.
Riassumendo, le tuple sono molto comode per funzioni che restituiscono più valori, o per creare costanti/variabili temporanee e di breve durata. Più raramente, possono essere utilizzate anche come argomenti delle funzioni, dato che queste possono avere un numero qualsiasi di parametri. Per incapsulare qualcosa di più complesso è consigliabile, però, usare una classe o una struct
, argomenti delle lezioni successive.
Il Playground completo con tutti gli esempi di questa lezione è disponibile su GitHub.