Lezione 8 di 8 · 20 min di lettura
Riepilogo e sfida finale
Tutto il modulo in una pagina, un quiz di controllo e una macchina che dà il resto in monete, da scrivere da zero.
Guarda quanta strada
All’inizio del modulo i tuoi programmi andavano dritti dalla prima riga all’ultima, e ogni formula andava ricopiata. Adesso sai:
- scrivere funzioni tue, con parametri, tipi, documentazione, pubbliche o private;
- rendere le chiamate leggibili con le etichette, e scrivere funzioni che stanno bene nel tubo;
- usare le funzioni come valori: passarle, restituirle, crearle al volo, catturarle con
_; - far scegliere il programma con
case, i pattern, i soggetti multipli e le guardie, con il compilatore che controlla i casi dimenticati; - ripetere con la ricorsione, e scriverla in coda con un accumulatore perché non consumi memoria.
Con questi strumenti, e senza nemmeno un if o un for, si può già scrivere qualsiasi calcolo. Il resto del corso aggiunge soprattutto modi migliori di organizzare i dati.
La pagina da tenere accanto
| Cosa | Come si scrive |
|---|---|
| Definire una funzione | fn area(width: Int, height: Int) -> Int { width * height } |
| Renderla pubblica | pub fn area(...) |
| Documentarla | /// Una riga di descrizione sopra la funzione |
| Etichette | fn add_vat(cents: Int, rate rate: Int), chiamata add_vat(1000, rate: 22) |
| Scorciatoia delle etichette | area(width:, height:) |
| Tipo di una funzione | fn(Int) -> Int |
| Funzione anonima | fn(x) { x * 2 } |
| Cattura | add_vat(_, rate: 22), cioè fn(x) { add_vat(x, rate: 22) } |
| Variabile di tipo | fn twice(value: a, f: fn(a) -> a) -> a |
| Decidere | case x { 1 -> "one" _ -> "other" } |
| Sì o no | case age >= 18 { True -> ... False -> ... } |
| Pattern alternativi | 6 \| 7 -> "weekend" |
| Prefisso di stringa | "/say " <> text -> text |
| Più soggetti | case n % 3, n % 5 { 0, 0 -> ... } |
| Guardia | s if s >= 90 -> "A" (niente chiamate di funzione) |
| Ricorsione | caso base + passo ricorsivo più vicino al caso base |
| Ricorsione in coda | sum_loop(n - 1, total + n): la chiamata è l’ultima cosa |
| Nascondere l’accumulatore | pub fn sum_to(n) chiama fn sum_to_loop(n, 0) |
| Fermare un programma bloccato | Ctrl+C, poi a e Invio |
Trovi tutto anche nel Codex (tasto K).
Quiz di controllo
Tre domande che mescolano tutto il modulo. Se ne sbagli una, la spiegazione ti dice in quale lezione tornare.
Quiz
Con fn twice(value: a, f: fn(a) -> a) -> a { f(f(value)) }, quanto vale twice(3, fn(x) { x * x })?
Quiz
Cosa restituisce label(15) con fn label(n: Int) -> String { case n { n if n > 10 -> "big" 15 -> "fifteen" _ -> "small" } }?
Quiz
Quale di queste funzioni ricorsive è scritta in coda?
La sfida: la macchina del resto
Ora un programma completo, da zero. È il distributore automatico che, dopo il pagamento, ti restituisce il resto in monete, sempre usando le monete più grandi possibili.
Esercizio · sul tuo computer
La macchina del resto
Nel progetto exercises crea src/change_machine.gleam. Un prodotto costa 1315 centesimi e il cliente paga con 2000. Il programma stampa il resto, poi le monete che lo compongono, dalla più grande alla più piccola (saltando quelle che non servono), e infine quante monete ha usato in tutto.
Le monete in euro valgono, in centesimi: 200, 100, 50, 20, 10, 5, 2, 1. Scrivi:
format_cents(cents: Int) -> String, quella che conosci;next_coin(coin: Int) -> Int, che con uncasedà la moneta successiva più piccola:next_coin(200)vale 100,next_coin(5)vale 2, e dopo l’ultima (1) restituisce 0;give_change(amount: Int) -> Int, pubblica, che stampa le righe delle monete e restituisce quante monete ha usato. Per farlo chiama una funzione privata ricorsiva in coda con due accumulatori, la moneta attuale (si parte da 200) e le monete usate finora (si parte da 0). Usa le etichette per renderne leggibile la chiamata.
Per ogni moneta, quante ne servono è amount / coin, e quello che resta da dare è amount % coin. Se una moneta non serve (ne servono 0), non si stampa niente. Quando la moneta attuale è 0, le monete sono finite e si restituisce il conteggio.
L’output deve essere:
Change: 6.85
3 x 2.00
1 x 0.50
1 x 0.20
1 x 0.10
1 x 0.05
Coins: 7Suggerimento: nel passo ricorsivo, un blocco con tre cose: calcola quante monete servono con un let, stampa la riga oppure no con un case (ricordi? l’altro ramo vale Nil), poi chiamati con la moneta successiva.
Mostra una soluzione (prima prova da solo!)
import gleam/int
import gleam/io
import gleam/string
pub fn main() -> Nil {
let price = 1315
let paid = 2000
let change = paid - price
io.println("Change: " <> format_cents(change))
let coins = give_change(change)
io.println("Coins: " <> int.to_string(coins))
}
/// Prints the coins that make up `amount`, largest first,
/// and returns how many coins were used.
pub fn give_change(amount: Int) -> Int {
give_loop(amount, coin: 200, used: 0)
}
fn give_loop(amount: Int, coin coin: Int, used used: Int) -> Int {
case coin {
0 -> used
_ -> {
let count = amount / coin
case count {
0 -> Nil
_ -> io.println(int.to_string(count) <> " x " <> format_cents(coin))
}
give_loop(amount % coin, coin: next_coin(coin), used: used + count)
}
}
}
/// The next smaller euro coin, or 0 after the smallest one.
fn next_coin(coin: Int) -> Int {
case coin {
200 -> 100
100 -> 50
50 -> 20
20 -> 10
10 -> 5
5 -> 2
2 -> 1
_ -> 0
}
}
fn format_cents(cents: Int) -> String {
int.to_string(cents / 100)
<> "."
<> { cents % 100 |> int.to_string |> string.pad_start(2, "0") }
}Qualche dettaglio da notare:
give_changeè l’interfaccia pulita: un importo dentro, il numero di monete fuori. I due accumulatori e i loro valori iniziali restano un affare privato digive_loop.give_loopè in coda: sia il resto (amount % coin) sia il conteggio (used + count) si calcolano negli argomenti.- Il
caseinterno serve solo a stampare o no: il suo valore è sempreNil, e il compilatore non protesta perché unNilnon usato non è un problema. - La sequenza delle monete sta tutta in
next_coin. Se un giorno sparissero le monete da 1 e 2 centesimi, basterebbe cambiare quelcase.
Cosa succede nel Modulo 3
Finora ogni nostro valore era da solo: un numero, un testo, un Bool. Le monete della sfida le abbiamo dovute mettere in fila con un trucco, una funzione che dà “la moneta dopo”. Nel prossimo modulo arrivano le liste: sequenze di valori, come [200, 100, 50], con i loro pattern ([first, ..rest]), la ricorsione che le percorre, e le funzioni della libreria standard che fanno la maggior parte del lavoro al posto tuo, come list.map e list.filter. E lì le funzioni come valori della lezione 3 diventeranno i tuoi attrezzi di tutti i giorni.