Le funzioni: scrivere una volta, usare molte
Finora ogni programma è una fila di istruzioni dentro main. Quando lo stesso calcolo
serve in tre punti diversi — la media di tre materie, il prezzo di tre biglietti — copiarlo tre
volte è la strada più corta e la più fragile: basta sbagliare una copia e i risultati non tornano
più. Una funzione dà un nome a un pezzo di calcolo e ti permette di usarlo quante
volte vuoi, scrivendolo una volta sola.
- a dichiarare una funzione con
fun nome(parametri): tipo; - il corpo scritto con
=(una riga) e quello con le graffe ereturn; - a chiamare una funzione, passando gli argomenti nell'ordine giusto;
- le funzioni che non restituiscono niente;
- i parametri con valore di default;
- le funzioni che ricevono un elenco (
List<Int>) e restituiscono un numero.
Ripasso lampo: valori che escono dai comandi
Una funzione assomiglia a quello che già usi: ripasso del capitolo 8 per i testi e del capitolo 6 per gli elenchi.
Che cosa stampa questo programma?
fun main() {
val parola = "manuale"
println(parola.uppercase())
println(parola.length)
}MANUALE e poi 7Esatto: uppercase() consegna una versione maiuscola del testo, length il numero di caratteri.manuale e poi 7No: uppercase() non serve a niente se poi non usi quello che restituisce. Qui il suo risultato finisce dentro println.MANUALE e poi manualeAttenzione: length è un numero (la lunghezza), non le lettere della parola.Molti comandi restituiscono un valore invece di stampare: uppercase() consegna il testo maiuscolo, length consegna un numero. In questo capitolo impari a costruire i tuoi: pezzi di calcolo che consegnano un risultato a chi li chiama.
Che cosa stampa questo programma?
fun main() {
val voti = listOf(6, 8, 10)
var somma = 0
for (v in voti) somma = somma + v
println(somma / voti.size)
}8Esatto: 24 diviso 3 fa 8, e la divisione fra interi dà un intero.24No: 24 è la somma, ma poi si divide per il numero di voti.8.0Attenzione: somma e size sono due Int, quindi il risultato è 8. Per i decimali serve un Double nel conto.Questo è il conto che nel capitolo 6 scrivevi a mano ogni volta: sommare e dividere per la dimensione. Nel capitolo di oggi diventa una funzione con un nome — media(voti) — da riusare senza ricopiare il ciclo.
9.1 Perché le funzioni: lo stesso conto tre volte
Guarda il programma qui sotto. Calcola la media di italiano e di matematica:
le stesse sei righe sono copiate due volte. C'è già anche la lista dell'inglese, ma non la usi.
Funziona, ma ogni copia è un'occasione di sbagliare:
se nella seconda dimentichi toDouble(), la matematica stampa un numero intero
e nessuno se ne accorge. Nel secondo esempio la funzione media serve tutte e tre.
Adesso lo stesso programma con una funzione. Il calcolo ha un nome —
media — e i voti su cui lavorare diventano un parametro:
L'output è lo stesso (prova ad aggiungere a mano la riga dell'inglese nel primo esempio e confronta). Nel secondo, però, il calcolo della media esiste una volta sola: oggi tre materie, domani trenta, e le righe da scrivere restano due. Se domani scopri un errore nel conto, lo correggi in un punto solo.
- Lo stesso calcolo ti serve due o più volte: scrivilo una volta e chiamalo.
- Il calcolo ha un nome che si spiega da solo:
media(voti)si legge,somma1/sizeva interpretato. - Un pezzo di
mainè diventato lungo e non si capisce più dove comincia e dove finisce: dagli un nome e portalo fuori.
9.2 Dichiarare una funzione: parametri e tipo di ritorno
Una dichiarazione di funzione si legge come una frase: "c'è una funzione che si chiama
media, riceve un elenco di interi e restituisce un decimale". Ogni pezzo ha il
suo posto:
| Pezzo | Nell'esempio | A che cosa serve |
|---|---|---|
fun | fun media(...) | dice a Kotlin che stai dichiarando una funzione |
| nome | media | il nome con cui la chiamerai: si scrive minuscolo, come le variabili |
| parametri | (voti: List<Int>) | i dati che la funzione riceve dall'esterno: nome e tipo, separati da : |
| tipo di ritorno | : Double | che cosa consegna a chi la chiama |
| corpo | { ... return ... } | le istruzioni che fanno il lavoro |
I parametri possono essere più di uno, e vanno passati nell'ordine in cui sono dichiarati: nel prossimo esempio la funzione vuole prima il nome e poi l'età.
Nel visualizzatore sopra trovi due tipi di passo nuovi: Chiamata di funzione, dove vedi
il valore dell'argomento che entra nella funzione, e Ritorno dalla funzione, dove vedi il
risultato che esce. È il viaggio di ogni funzione: main chiama,
doppio lavora sulla sua copia di n e restituisce 2 * n,
poi main riprende da dove si era fermato.
- La prima chiamata passa
50.0aprezzoe10apercentuale: lo sconto è50.0 * 10 / 100.0 = 5.0, quindi stampa45.0. - La seconda chiamata riusa la stessa funzione con altri numeri:
80.0 * 50 / 100.0 = 40.0, e80.0 - 40.0 = 40.0. - Il
100.0scritto con il punto è quello che salva i decimali: con100la divisione sarebbe fra interi e il risultato sarebbe sbagliato. - La stessa funzione, due coppie di valori diversi: è tutto il vantaggio delle funzioni.
9.3 Corpo con = o con le graffe, e funzioni che non restituiscono niente
Se il lavoro sta in una riga sola, puoi scrivere il corpo con =
invece che con le graffe e return: è lo stesso. Se invece
la funzione deve solo stampare, non scrivere il tipo dopo le parentesi:
non consegna niente.
fun doppio(n: Int): Int = n * 2 è la forma corta di
fun doppio(n: Int): Int { return n * 2 }. Per un calcolo di una riga si usa il
=; quando servono più istruzioni, come in media, si usano le graffe con
return. Entrambe le forme vanno bene: scegli quella che si legge meglio.
saluta("Anna") va bene come istruzione a sé stante, ma
println(saluta("Anna")) non ha senso: saluta non consegna nessun valore.
Se ti serve il risultato dentro un'altra espressione, la funzione deve avere un tipo di ritorno e
un return.
Che cosa stampa questo programma?
fun saluta(nome: String) {
println("Ciao $nome")
}
fun main() {
saluta("Anna")
}saluta non ha un tipo di ritornoNo: senza tipo di ritorno la funzione non consegna un valore, ma può comunque stampare.Ciao AnnaEsatto: main chiama saluta, e quella funzione fa il println.: String dopo i parametriNo: se non restituisci niente, il tipo di ritorno non si scrive. È proprio la forma di saluta nell'esempio sopra.Una funzione senza tipo di ritorno serve a fare qualcosa (stampare, aggiornare). Una con il tipo serve a consegnare un valore.
9.4 return: consegnare il risultato e uscire subito
return fa due cose insieme: consegna il valore a chi ha chiamato la funzione e
esce da lì, saltando tutto quello che viene dopo. È il modo più chiaro di scrivere
una funzione con più casi: si mettono le condizioni e si esce dalla prima che vale.
return finale non ha bisogno di if
Se siamo arrivati fino in fondo, vuol dire che nessuna delle condizioni precedenti era vera: il
caso rimasto si scrive con un return diretto, senza condizione. È lo stesso ragionamento
dell'else finale di una catena di if, che non deve ripetere tutte le
condizioni già escluse.
9.5 Funzioni che ricevono elenchi e parametri con valore di default
Qui succedono due cose, una per volta.
Prima: media chiama somma. Non riscrivi il ciclo.
Poi: formula ha già un valore ("Ciao"). Se non lo passi, vale quello.
fun somma(numeri: List<Int>) dice che cosa la funzione si aspetta. Nota il
it che non c'è: dentro la funzione i parametri hanno un nome, come numeri.
it lo hai usato in filter: qui non serve.
Che cosa stampa questo programma?
fun saluta(nome: String, formula: String = "Ciao"): String = "$formula $nome!"
fun main() {
println(saluta("Anna"))
println(saluta("Luca", "Ehi"))
}Ciao Anna! e poi Ehi Luca!Esatto: nella prima chiamata il secondo argomento manca e vale il default "Ciao"; nella seconda il valore passato sostituisce il default.formula non avesse un valore di default. Qui Kotlin lo trova nella dichiarazione e va avanti.Ciao Anna! e poi Ciao Luca!No: il valore passato ha la precedenza sul default, altrimenti il default sarebbe inutile.Il default rende il parametro facoltativo: chi chiama può decidere se usarlo o no. Serve per i casi in cui il valore "normale" è quasi sempre lo stesso, e lo si vuole cambiare solo ogni tanto.
9.6 Riepilogo
| Scrivi | Cosa fa |
|---|---|
fun media(voti: List<Int>): Double { ... } | dichiara una funzione: nome, parametri fra parentesi, tipo di ritorno dopo i due punti |
fun quadrato(n: Int): Int = n * n | corpo a espressione: forma corta di { return n * n } |
fun saluta(nome: String) { println(...) } | funzione che non restituisce niente: il tipo di ritorno non si scrive |
return valore | consegna il risultato a chi ha chiamato e esce subito dalla funzione |
media(voti) | chiama la funzione passando gli argomenti nell'ordine dei parametri |
fun saluta(nome: String, formula: String = "Ciao") | parametro con valore di default: si può omettere nella chiamata |
fun media(numeri: List<Int>): Double = somma(numeri).toDouble() / numeri.size | una funzione può chiamarne un'altra e usarne il risultato |
println(cubo(3)) | il risultato di una funzione si usa dentro un'espressione, come una variabile |
- Manca l'argomento "x" nella chiamata a f(...) → hai dimenticato un valore fra le parentesi.
- Troppi argomenti per f → ne hai passati più di quanti la funzione ne dichiara.
- La funzione f non ha il parametro "x" → hai scritto
f(nome = ...)con un nome di parametro che non esiste. - Nome non definito: "f" → hai chiamato una funzione che non hai mai dichiarato, oppure l'hai scritta con un altro nome.
9.7 Esercizi guidati
Leggi 5 voti in una lista. Poi stampa: i voti letti, la media della classe,
l'elenco dei soli sufficienti e la media dei sufficienti. Il calcolo della media
va scritto in una funzione, perché serve due volte.
Scrivi la funzione media sopra main, non dentro.
Con i voti 6, 7, 8, 4, 9 le quattro righe sono
Voti: 6, 7, 8, 4, 9,
Media della classe: 6.80,
Sufficienti: 6, 7, 8, 9 e
Media dei sufficienti: 7.50.
Il conto della media è quello del ripasso: accumula in var somma, poi
somma.toDouble() / voti.size (il toDouble() è quello che tiene i
decimali). I sufficienti si prendono con voti.filter { it >= 6 }, e il risultato
è una lista che puoi passare di nuovo alla stessa funzione.
- La funzione riceve la lista come parametro: non sa e non le interessa se i voti sono cinque o cinquanta.
- Il ciclo somma gli elementi e
returnconsegna il risultato: senzareturnla funzione non darebbe niente a chi la chiama. media(voti)emedia(sufficienti)sono due chiamate della stessa funzione: è il vantaggio di scriverla una volta sola.filterrestituisce una lista, quindi si può passare direttamente come argomento: funzioni e metodi delle collezioni si combinano senza problemi.
Il museo applica queste tariffe: fino a 5 anni gratis, da 6 a 17 anni 5 euro, da 18 a 64 anni
12 euro, dai 65 anni in su 4 euro. Scrivi una funzione prezzo(eta: Int): Double che
restituisce il prezzo giusto, poi leggi nome e età e stampa quanto paga la persona.
Con Marta e 12 anni la riga è Marta, 12 anni: 5.00 euro.
La funzione sta sopra main, non dentro.
Dentro la funzione non serve una catena di else if: usa un return per
ogni fascia, dalla più stretta alla più larga, e lascia il caso dei più grandi come
return finale senza if. I prezzi sono Double: scrivili
0.0, 5.0, 12.0, 4.0.
- Ogni
ifcontrolla solo il limite superiore: se siamo al secondo, sappiamo già che l'età non è sotto i 6 anni. returnesce dalla funzione appena trova la fascia giusta: le condizioni successive non vengono nemmeno guardate.- L'ultimo
return 4.0non ha bisogno diif: se il programma è arrivato lì, l'età è per forza 65 o più. - La funzione restituisce un
Doubleemainlo usa dentro"%.2f".format(...): il risultato di una funzione si usa come qualsiasi altro valore.
Data la lista 12, 7, 9, 21, 5, 18, scrivi quattro funzioni — minimo,
massimo, media e sopraMedia — e stampa i quattro
risultati. sopraMedia deve dire quanti valori sono più grandi della media, e deve
usare la funzione media invece di ricalcolarla.
Stampa anche la lista. Le cinque righe sono:
Misure: 12, 7, 9, 21, 5, 18,
Minimo: 5, Massimo: 21,
Media: 12.0 e Sopra la media: 2.
Le funzioni stanno sopra main.
Per minimo e massimo parti dal primo elemento come riferimento e confronta gli
altri, come nello schema del capitolo 6: partire da 0 darebbe risultati sbagliati con numeri
tutti negativi. Il tipo di ritorno è Int per minimo, massimo e sopraMedia,
Double per media.
minimoemassimopartono danumeri[0]: con la lista vuota darebbero un errore, ma qui la lista ha sempre sei valori.- Le quattro funzioni sono indipendenti: ognuna riceve la lista e restituisce un numero, senza sapere niente delle altre.
sopraMediachiamamedia: il conto della media esiste in un punto solo, e se lo correggi lo correggi per tutti.- Nella media il
toDouble()è obbligatorio:72 / 6fra interi darebbe12, e va bene solo per caso.
Scrivi da zero il programma del conto di un ristorante. Legge, nell'ordine:
- quante voci ci sono nel conto;
- per ogni voce: il nome del piatto, la quantità e il prezzo unitario.
Per ogni voce stampa una riga nel formato Pizza x2 = 13.00 euro.
Alla fine stampa due righe:
Totale: 26.50 euro e
Con il servizio: 29.15 euro
(il servizio è il 10% in più sul totale).
Devono esserci almeno due funzioni: una che calcola il totale di una voce
e una che applica il servizio.
Con i dati di prova: Pizza 2 × 6.5 = 13, Acqua 1 × 1.5 = 1.5, Tiramisu 3 × 4.0 = 12;
somma 26.5; con servizio 29.15.
Il totale di una voce è prezzo * quantita; il servizio è importo * 1.1.
Accumula il conto in un var conto = 0.0 che parte da zero e cresce a ogni giro:
è l'accumulatore del capitolo 5, questa volta con i decimali. Per i prezzi usa
"%.2f".format(...).
- Le due funzioni stanno fuori da
main, in cima al programma: è la posizione abituale delle funzioni di servizio. totalericeve prezzo e quantità nell'ordine dichiarato: invertirli darebbe un risultato sbagliato senza nessun errore, ed è l'errore più frequente del capitolo.- L'accumulatore
contoparte da0.0perché sommaDouble: con0il primo risultato sarebbe comunque decimale, ma il tipo dichiarato sarebbe fuorviante. conServiziosi applica una volta sola, alla fine, al totale: applicarlo a ogni voce darebbe lo stesso numero qui, ma non nei conti con arrotondamenti.- Le letture usano la forma robusta
readlnOrNull()?.toDoubleOrNull() ?: 0.0: un dato mancante o sporco non ferma il programma.
Questo programma deve stampare il totale di un acquisto e il totale scontato del 10%, ma non
parte. Contiene due errori: uno riguarda una chiamata, l'altro un nome.
Correggili uno alla volta. Alla fine le due righe sono
Totale: 7.50 euro e Con lo sconto: 6.75 euro.
Il primo errore è dentro la chiamata a prezzo: la funzione dichiara
due parametri e ne stai passando uno solo. Il secondo è il nome della funzione
chiamata nella seconda stampa: esiste una funzione dichiarata che fa esattamente quel lavoro,
ma si chiama in un altro modo.
- Il messaggio Manca l'argomento "costoUnitario" nella chiamata a prezzo(...) dice esattamente che cosa manca:
prezzo(quantita, costo). - Il secondo errore non dà un messaggio di sintassi ma di nome: Nome non definito: "scontoTotale". La funzione si chiama
sconto. - I due argomenti si passano nell'ordine dei parametri: prima la quantità (
Int), poi il costo unitario (Double). - Con i dati
3e2.5il totale è7.50euro e lo sconto6.75.
fun, parametri e tipo di ritorno= e il corpo con le graffe e return