Lezione 2 di 8 · 16 min di lettura
Etichette e tubi
Chiamate che si spiegano da sole con gli argomenti etichettati, e funzioni scritte apposta per stare in fila nel tubo |>.
Quale numero è quale?
Guarda questa chiamata della lezione 1.6:
string.pad_start("7", 3, "0")Il 3 è la lunghezza finale o quante volte ripetere lo zero? E lo "0" è il riempitivo o il testo da allungare? Senza aprire la documentazione non lo sai. Con le nostre funzioni è uguale: in area(3, 4) qual è la larghezza? Quando una funzione riceve più valori, soprattutto se hanno lo stesso tipo, la sola posizione non basta a capire.
Argomenti con l’etichetta
Gleam permette di dare a un parametro un’etichetta (label): un nome esterno, da usare nelle chiamate, oltre a quello interno da usare nel corpo. Si scrive prima del nome interno:
import gleam/io
pub fn main() -> Nil {
echo area(width: 3, height: 4)
echo area(height: 4, width: 3)
echo area(3, 4)
echo area(3, height: 4)
io.println("Done")
}
fn area(width w: Int, height h: Int) -> Int {
w * h
}src/labels.gleam:4
12
src/labels.gleam:5
12
src/labels.gleam:6
12
src/labels.gleam:7
12
DoneIn width w: Int, width è l’etichetta e w è il nome del parametro. Fuori dalla funzione si usa l’etichetta, dentro si usa il nome: nel corpo width non esiste, e se lo scrivi il compilatore risponde con un Unknown variable.
Guarda le quattro chiamate:
- con le etichette la chiamata si legge da sola:
area(width: 3, height: 4); - con le etichette l’ordine non conta più: la seconda chiamata le scambia, e il risultato è lo stesso;
- le etichette sono facoltative:
area(3, 4)funziona ancora, per posizione; - si possono mescolare, ma gli argomenti senza etichetta vanno prima di quelli con l’etichetta.
Molto spesso etichetta e nome coincidono, e si scrive due volte la stessa parola: fn area(width width: Int, height height: Int). Sembra strano la prima volta, ma è la forma più comune.
Quando il compilatore ti corregge
Se sbagli a scrivere un’etichetta, il compilatore ti dice quali esistono, e prova anche a indovinare cosa intendevi:
import gleam/io
pub fn main() -> Nil {
echo area(3, heigth: 4)
io.println("Done")
}
fn area(width w: Int, height h: Int) -> Int {
w * h
}error: Unknown label
┌─ /home/ada/learn-gleam/exercises/src/labels.gleam:4:16
│
4 │ echo area(3, heigth: 4)
│ ^^^^^^^^^ Did you mean `height`?
It accepts these labels:
height
widthE se metti un argomento senza etichetta dopo uno con l’etichetta, come in area(height: 4, 3), ottieni un Unexpected positional argument: una volta che hai cominciato con le etichette, devi continuare.
Anche la libreria standard ha le etichette
Molte funzioni della libreria standard hanno etichette, pensate proprio per rendere le chiamate leggibili. string.pad_start è definita così (qui senza il corpo):
pub fn pad_start(
string: String,
to desired_length: Int,
with pad_string: String,
) -> StringIl primo parametro non ha etichetta (è ovvio cosa sia), gli altri due sì. Quindi la chiamata misteriosa di prima si può scrivere:
string.pad_start("7", to: 3, with: "0")“Allunga "7" fino a 3, con degli zeri.” Allo stesso modo string.repeat("-", times: 24). Le etichette di ogni funzione si trovano nella documentazione della libreria standard, su hexdocs.pm/gleam_stdlib, e l’editor te le suggerisce mentre scrivi.
Quiz
Con fn area(width w: Int, height h: Int) -> Int, quale chiamata non compila?
La scorciatoia per le etichette
Capita spesso di avere una variabile che si chiama esattamente come l’etichetta:
let width = 3
let height = 4
area(width: width, height: height)Quel width: width è ripetitivo, e Gleam ha una scorciatoia: scrivi solo l’etichetta seguita dai due punti, e il valore è la variabile con lo stesso nome.
area(width:, height:)Le due chiamate sono identiche. La scorciatoia premia chi dà nomi coerenti alle cose.
Funzioni fatte per il tubo
Nella lezione 1.6 abbiamo visto che x |> f(a) equivale a f(x, a): il valore a sinistra entra come primo argomento. Funziona anche con le tue funzioni, e le etichette ci stanno benissimo:
import gleam/int
import gleam/io
import gleam/string
pub fn main() -> Nil {
4990
|> discount(percent: 20)
|> add_vat(rate: 22)
|> format_cents
|> io.println
}
fn discount(cents: Int, percent percent: Int) -> Int {
cents - cents * percent / 100
}
fn add_vat(cents: Int, rate rate: Int) -> Int {
cents + cents * rate / 100
}
fn format_cents(cents: Int) -> String {
int.to_string(cents / 100)
<> "."
<> { cents % 100 |> int.to_string |> string.pad_start(2, "0") }
}48.70“Prendi 49,90 euro, togli il 20%, aggiungi l’IVA al 22%, trasforma in testo, stampa.” Il programma si legge come la frase.
Perché funzioni così bene, le funzioni sono state scritte con un’idea precisa: il primo parametro è la cosa che viene trasformata (qui l’importo), e i parametri successivi sono le “impostazioni” della trasformazione, con un’etichetta. In Gleam è una convenzione diffusa, e la segue anche la libreria standard: in string.pad_start il primo argomento è la stringa da allungare, in int.to_string il numero da convertire. Quando scrivi una funzione, chiediti: “di cosa parla questa funzione?“. Quella cosa va messa per prima.
Quiz
Cosa vale 10 |> area(height: 2), con la funzione area di questa lezione?
Esercizio · sul tuo computer
Il conto alla romana
Nel progetto exercises crea src/bill.gleam. Quattro amici hanno un conto di 8450 centesimi e vogliono lasciare il 10% di mancia, poi dividere. Scrivi:
add_tip(cents: Int, percent percent: Int) -> Int, che aggiunge la percentuale di mancia all’importo;share(total: Int, among people: Int) -> Int, che divide il totale tra le persone (divisione intera: i centesimi che avanzano restano a parte);format_cents(cents: Int) -> String, la tua versione (o quella della lezione precedente).
In main, usa let people = 4 e calcola il totale con la mancia con un tubo, poi la quota di ciascuno con share(total, among: people) e i centesimi avanzati con %. L’output deve essere:
Total with tip: 92.95
Each of 4 pays: 23.23
Left over: 0.03Mostra una soluzione (prima prova da solo!)
import gleam/int
import gleam/io
import gleam/string
pub fn main() -> Nil {
let people = 4
let total = 8450 |> add_tip(percent: 10)
let each = share(total, among: people)
io.println("Total with tip: " <> format_cents(total))
io.println(
"Each of " <> int.to_string(people) <> " pays: " <> format_cents(each),
)
io.println("Left over: " <> format_cents(total % people))
}
fn add_tip(cents: Int, percent percent: Int) -> Int {
cents + cents * percent / 100
}
fn share(total: Int, among people: Int) -> Int {
total / people
}
fn format_cents(cents: Int) -> String {
int.to_string(cents / 100)
<> "."
<> { cents % 100 |> int.to_string |> string.pad_start(2, "0") }
}L’etichetta among rende la chiamata una frase (“dividi il totale tra le persone”), mentre dentro la funzione il parametro si chiama people. I centesimi avanzati sono il resto della stessa divisione: 9295 % 4 fa 3.
Ricapitolando
- Un’etichetta è il nome esterno di un parametro:
fn area(width w: Int)si chiama conarea(width: 3), e nel corpo si usaw. - Con le etichette l’ordine degli argomenti è libero; senza, vale la posizione. Gli argomenti senza etichetta vanno sempre per primi.
- Spesso etichetta e nome coincidono:
fn add_vat(cents: Int, rate rate: Int). - Scorciatoia:
area(width:, height:)usa le variabili con lo stesso nome delle etichette. - Molte funzioni della libreria standard hanno etichette:
string.pad_start("7", to: 3, with: "0"). - Per stare bene nel tubo, una funzione riceve per primo il valore che trasforma.
|> echoin mezzo a un tubo mostra il valore che passa, senza cambiarlo.
Nella prossima lezione scopriremo che le funzioni sono valori come gli altri: si possono mettere in una variabile, passare a un’altra funzione e perfino creare al volo.