Vai al contenuto
Leo

Parte II — Il flusso

Capitolo 9 — Le funzioni

Man mano che un programma cresce, cominci a ripetere gli stessi gruppi di istruzioni. Le funzioni servono a dare un nome a un blocco di codice, così da poterlo richiamare quando serve invece di riscriverlo. Sono il mattone con cui si costruiscono i programmi grandi: ogni funzione fa una cosa, la fa bene, e il resto del programma la usa senza doversi preoccupare di come.

🦁 Curiosità — Una parola presa in prestito

Il termine «funzione» arriva dalla matematica, dove indica una regola che a ogni ingresso associa un'uscita: dai un numero, ne ottieni un altro. In programmazione l'idea è la stessa, con un vantaggio pratico enorme: scrivi una cosa una volta e la riusi cento. Questo principio ha persino un nome e una sigla, DRY, da «Don't Repeat Yourself» — non ripeterti — reso celebre dal libro The Pragmatic Programmer del 1999.

Definire e chiamare

Una funzione si definisce con funzione, seguita dal nome e, tra parentesi, dai parametri — i dati che riceve. Dentro, restituisci dice quale valore mandare indietro:

Leo
funzione saluta(nome, saluto = "Ciao")
    restituisci "{saluto}, {nome}!"
fine

scrivi(saluta("Giulia"))
scrivi(saluta("Marco", saluto = "Buongiorno"))
Risultato
Ciao, Giulia!
Buongiorno, Marco!

💡 L'idea — Come un distributore automatico

Una funzione è come un distributore automatico: inserisci qualcosa (gli argomenti), premi il pulsante e ne esce un risultato. Non hai bisogno di sapere come funziona dentro — ti fidi di quello che restituisce. È questo il bello delle funzioni: una volta scritte bene, le usi senza più pensare a come fanno il loro mestiere.

Guarda il parametro saluto = "Ciao": è un valore predefinito. Se chi chiama la funzione non lo fornisce (come nel primo caso), vale "Ciao"; se lo fornisce (secondo caso), vince il suo. E nota come, nella seconda chiamata, l'abbiamo passato per nome: saluto = "Buongiorno". Passare gli argomenti per nome rende le chiamate leggibili, specie quando i parametri sono tanti.

Una regola da ricordare: una funzione va definita prima di essere chiamata. Se la metti dopo il punto in cui la usi, spostala sopra — oppure usa il blocco inizio del Capitolo 1, che parte quando tutto il file è già stato letto.

Le funzioni come comandi

Hai notato nel Capitolo 1 che scrivi si usa senza parentesi? La stessa cortesia vale per le tue funzioni, a una condizione: che non abbiano restituisci, cioè che facciano qualcosa invece di calcolare un valore. In quel caso puoi chiamarle come comandi, con gli argomenti separati da virgole e senza parentesi:

Leo
funzione saluta(nome)
    scrivi "Ciao, {nome}!"
fine

saluta "Ada Lovelace"
Risultato
Ciao, Ada Lovelace!

Le funzioni che invece restituiscono un valore vogliono le parentesi, come in un foglio di calcolo: saluta("Giulia"), radice(16), media_di(voti...). La regola è coerente con tutto il resto: le parentesi dicono «calcola e dammi un valore».

Le funzioni sono valori

Ecco un'idea che all'inizio spiazza e poi diventa indispensabile: in Leo una funzione è un valore come un numero o un testo. Puoi metterla in una variabile, passarla a un'altra funzione, restituirla. Puoi anche creare una funzione senza nome — una funzione anonima — al volo:

Leo
variabile doppio = funzione (x) restituisci x * 2 fine
scrivi(doppio(21))
variabile voti = [4, 6, 7, 9, 10]
scrivi(mappa(voti, doppio))
scrivi(filtra(voti, funzione (v) restituisci v >= 6 fine))
Risultato
42
[8, 12, 14, 18, 20]
[6, 7, 9, 10]

mappa(lista, funzione) applica una funzione a ogni elemento e restituisce la nuova lista; filtra(lista, funzione) tiene solo gli elementi per cui la funzione dà vero. A entrambe passiamo una funzione: nel primo caso doppio (per nome), nel secondo una funzione anonima scritta sul posto. Questo stile — passare comportamenti, non solo dati — è potentissimo, e lo ritroveremo parlando della libreria.

Le chiusure: funzioni che ricordano

Quando definisci una funzione dentro un'altra, quella interna ricorda le variabili del posto in cui è nata, anche dopo che la funzione esterna è terminata. Questo meccanismo si chiama chiusura, ed è più facile vederlo che spiegarlo:

Leo
funzione crea_contatore()
    variabile conteggio = 0
    funzione incrementa()
        conteggio += 1
        restituisci conteggio
    fine
    restituisci incrementa
fine

variabile conta = crea_contatore()
conta()
conta()
scrivi(conta())
Risultato
3

crea_contatore crea una variabile conteggio e restituisce la funzione incrementa, che la usa. Anche se crea_contatore è già finita, la funzione conta continua ad avere accesso al suo conteggio, che cresce a ogni chiamata. Ogni contatore creato così ha un conteggio tutto suo, indipendente dagli altri. Le chiusure sono il modo elegante di «incapsulare» uno stato senza bisogno di variabili globali.

Quanti argomenti vuoi

A volte non sai in anticipo quanti argomenti riceverà una funzione. Tre puntini dopo l'ultimo parametro li raccolgono tutti in una lista; e tre puntini nella chiamata fanno il contrario, srotolando una lista in tanti argomenti:

Leo
funzione media_di(valori...)
    restituisci somma(valori) / lunghezza(valori)
fine

scrivi(media_di(6, 7, 8))
variabile voti = [5, 7, 9]
scrivi(media_di(voti...))
Risultato
7.0
7.0

La stessa funzione accetta sia argomenti sciolti (media_di(6, 7, 8)) sia una lista già pronta srotolata con i puntini (media_di(voti...)).

La ricorsione

Una funzione può chiamare sé stessa. Sembra un gioco di prestigio, ma è il modo naturale per risolvere i problemi che si definiscono in termini di sé stessi — come il fattoriale, dove 5! è 5 x 4!:

Leo
funzione fattoriale(n)
    se n <= 1 allora
        restituisci 1
    fine
    restituisci n * fattoriale(n - 1)
fine

scrivi(fattoriale(5))
Risultato
120

La chiave di ogni ricorsione è il caso base — qui se n <= 1, che ferma la discesa. Senza di esso, la funzione si richiamerebbe all'infinito; Leo ti protegge fermandosi dopo 1000 chiamate annidate con un ErroreRicorsione, ma il caso base devi sempre scriverlo tu.

Prova tu

  1. Scrivi una funzione area_rettangolo(base, altezza) che restituisce l'area, e una perimetro_rettangolo(base, altezza) che restituisce il perimetro. Stampale per un paio di rettangoli.
  2. Scrivi una funzione applica_sconto(prezzo, percento = 10) con uno sconto predefinito del 10%, e provala sia con lo sconto predefinito sia con uno diverso passato per nome.
  3. Usando filtra e una funzione anonima, estrai da una lista di numeri solo quelli maggiori della loro media. (Suggerimento: calcola prima la media, poi filtra.)