Lezione 4 di 7 · 20 min di lettura
Valori, variabili e tipi
Dare un nome ai valori con let, capire perché in Gleam niente cambia mai, e conoscere Int, Float, String e Bool.
Un nome per un valore
Un programma lavora con dei valori: numeri, testi, risposte sì/no. Per non doverli riscrivere ogni volta, gli si dà un nome con let:
import gleam/io
pub fn main() -> Nil {
let age = 34
let name = "Ada"
echo age
echo name
io.println("Done")
}Crea questo file dentro il progetto exercises della lezione precedente ed eseguilo con gleam run -m variables:
src/variables.gleam:6
34
src/variables.gleam:7
"Ada"
Donelet age = 34 si legge “sia age il valore 34”. Da quella riga in poi, scrivere age è come scrivere 34. Il nome si chiama variabile (anche se, come vedremo tra poco, in Gleam non varia affatto).
echo: la lente d’ingrandimento
echo è una parola del linguaggio che stampa qualsiasi valore, di qualsiasi tipo, preceduto dal file e dalla riga in cui l’hai scritto. È pensato per il debug, cioè per sbirciare dentro il programma mentre lo scrivi: nota che le stringhe compaiono tra virgolette, "Ada", così vedi esattamente cosa contengono.
io.println invece è per l’output “vero” del programma, e accetta solo testo. Useremo echo in questa lezione per esplorare i tipi, e poi vedremo come stampare un numero con io.println.
Niente cambia, mai
Ecco la prima grande differenza rispetto a molti altri linguaggi: in Gleam un valore non si può modificare. Si dice che i valori sono immutabili. Se provi ad assegnare un nuovo valore a una variabile esistente, il compilatore ti ferma:
let score = 10
score = 15error: Syntax error
┌─ /home/ada/learn-gleam/exercises/src/variables.gleam:5:9
│
5 │ score = 15
│ ^ There must be a 'let' to bind variable to value
See: https://tour.gleam.run/basics/assignments/
Hint: Use let for binding.Il compilatore suggerisce di usare let. E infatti puoi scrivere un nuovo let con lo stesso nome:
import gleam/io
pub fn main() -> Nil {
let score = 10
echo score
let score = score + 5
echo score
io.println("Done")
}src/rebind.gleam:5
10
src/rebind.gleam:7
15
DoneSembra una modifica, ma non lo è. La seconda riga let crea un valore nuovo (15) e gli dà il nome score, che da lì in poi indica quello. Il 10 non è stato toccato: semplicemente il nome è passato a un altro valore. Si dice che il secondo score oscura il primo.
La differenza si vede quando qualcun altro stava guardando il valore vecchio:
import gleam/io
pub fn main() -> Nil {
let x = "Original"
let y = x
let x = "New"
io.println(x)
io.println(y)
}New
Originaly era stato legato al valore "Original", e quel valore non è cambiato: il nome x ora indica un altro valore, ma y no.
Ogni valore ha un tipo
Ogni valore in Gleam ha un tipo, che dice che genere di dato è e cosa ci si può fare. Non devi quasi mai scriverlo: il compilatore lo deduce da solo guardando il valore. 34 è un Int, "Ada" è una String, quindi age è un Int e name una String.
Se vuoi, puoi comunque scriverlo esplicitamente, con un’annotazione di tipo dopo il nome:
let age: Int = 34
let name: String = "Ada"L’annotazione non cambia niente nel programma: è una promessa, e il compilatore controlla che sia mantenuta. Se non lo è, si ferma:
let price: Float = 3error: Type mismatch
┌─ /home/ada/learn-gleam/exercises/src/variables.gleam:4:22
│
4 │ let price: Float = 3
│ ^
Expected type:
Float
Found type:
IntNel codice Gleam di tutti i giorni le annotazioni sui let si usano poco: il compilatore sa già tutto. Le vedrai invece sempre sulle funzioni, come il -> Nil di main.
I tipi fondamentali sono quattro, più uno speciale. Vediamoli uno per uno.
Int: i numeri interi
Int è il tipo dei numeri interi, positivi o negativi: 0, 42, -7. Per rendere leggibili i numeri lunghi puoi separare le cifre con il trattino basso, che il compilatore ignora: 1_000_000 è un milione. E se ti servono, ci sono anche le basi 2, 8 e 16:
| Scritto così | Base | Vale |
|---|---|---|
1_000_000 | 10 | un milione |
0b1010 | 2 (binario) | 10 |
0o17 | 8 (ottale) | 15 |
0xFF | 16 (esadecimale) | 255 |
Sulla BEAM gli Int non hanno un limite di grandezza: crescono quanto serve.
import gleam/io
pub fn main() -> Nil {
echo 12_345_678_901_234_567_890 * 98_765_432_109_876_543_210
io.println("Done")
}src/big.gleam:4
1219326311370217952237463801111263526900
DoneDettagli nerd Perché sulla BEAM gli interi non traboccano?
Il processore lavora con numeri di dimensione fissa, di solito 64 bit: il più grande intero che ci sta è circa 9,2 miliardi di miliardi. In molti linguaggi, superare quel limite fa “traboccare” il numero, che ricomincia dal negativo o fa andare in crash il programma.
La BEAM invece usa gli interi del processore finché bastano, e quando un numero diventa troppo grande passa da sola a una rappresentazione a precisione arbitraria (in gergo bignum): il numero viene spezzato in tanti pezzi da 64 bit messi in fila, e le operazioni lavorano pezzo per pezzo. È più lento, ma il risultato è sempre giusto. Il limite diventa solo la memoria del computer.
Attenzione però: se compili per JavaScript, gli Int diventano numeri di JavaScript, precisi solo fino a circa 9 milioni di miliardi (2⁵³). Il compilatore ti avvisa se scrivi un letterale più grande.
Float: i numeri con la virgola
Float è il tipo dei numeri decimali. In Gleam, come in inglese, la virgola si scrive con il punto: 3.14, -0.5, 2.0. E il punto è obbligatorio: 2 è un Int, 2.0 è un Float, e per Gleam sono due tipi diversi che non si mescolano mai da soli.
Per i numeri molto grandi o molto piccoli c’è la notazione scientifica: 1.5e3 significa 1,5 × 10³, cioè 1500.0, e 3.0e-4 è 0.0003.
Dettagli nerd Come si scrive 3,14 con solo 0 e 1?
Un Float occupa 64 bit ed è codificato secondo lo standard IEEE 754, lo stesso usato da quasi tutti i processori. L’idea è quella della notazione scientifica, ma in base 2: il numero viene scritto come segno × mantissa × 2^esponente. Un bit per il segno, 11 bit per l’esponente, 52 bit per le cifre significative (la mantissa).
Il prezzo da pagare: molti numeri che in base 10 sono “tondi”, come 0,1, in base 2 hanno infinite cifre, proprio come 1/3 in base 10 è 0,333… Il computer tiene le prime 52 cifre binarie e butta il resto, quindi 0,1 viene memorizzato come un valore vicinissimo a 0,1, ma non esattamente 0,1. Lo toccheremo con mano nella prossima lezione.
Bool: vero o falso
Bool ha solo due valori: True (vero) e False (falso). Con la maiuscola: in Gleam true minuscolo non esiste. Li userai ovunque ci sia da prendere una decisione, e nella prossima lezione vedremo come ottenerli dai confronti.
let is_ready = True
let is_raining = FalseString: il testo
String è il tipo del testo, scritto tra virgolette doppie. Una stringa può contenere qualsiasi carattere: lettere accentate, alfabeti diversi, perfino emoji. Due stringhe si attaccano con l’operatore <>:
let first = "Ada"
let full = first <> " Lovelace"Ora full vale "Ada Lovelace". Attenzione allo spazio: <> attacca e basta, lo spazio tra le due parole l’abbiamo messo noi dentro la seconda stringa.
Per lavorare sulle stringhe c’è il modulo gleam/string della libreria standard. Per esempio string.length conta i caratteri:
import gleam/io
import gleam/string
pub fn main() -> Nil {
let drink = "caffè"
echo string.length(drink)
echo string.byte_size(drink)
io.println("Done")
}src/strlen.gleam:6
5
src/strlen.gleam:7
6
DoneCinque caratteri, ma sei byte. Come mai?
Dettagli nerd Byte, caratteri e UTF-8
La memoria del computer è fatta di byte: gruppi di 8 bit, ognuno capace di contenere un numero da 0 a 255. Il testo va quindi trasformato in numeri, e il modo standard per farlo oggi è UTF-8.
In UTF-8 le lettere dell’alfabeto inglese, le cifre e la punteggiatura comune occupano un byte ciascuna. Le lettere accentate ne occupano due, molti simboli e ideogrammi tre, le emoji quattro. Le stringhe di Gleam sono sempre in UTF-8: "caffè" ha 5 caratteri, ma la è pesa 2 byte, per un totale di 6.
string.length conta i caratteri come li vede una persona; string.byte_size conta i byte in memoria. Quasi sempre ti interessa il primo. Ma se un giorno una lunghezza “non torna” con del testo accentato, adesso sai dove guardare.
Nil: il niente
L’hai già incontrato in -> Nil: Nil è sia un tipo sia il suo unico valore, e significa “niente di interessante”. È quello che restituiscono le funzioni che fanno qualcosa (come stampare) invece di calcolare un risultato. Non lo userai quasi mai direttamente, ma ora sai cos’è quando lo vedi.
Quiz
Quale di queste righe non compila?
Le regole dei nomi
Gleam è rigido sui nomi, e il compilatore ti corregge se sbagli. Le regole sono poche:
- Variabili, funzioni e costanti sono in
snake_case: minuscole, cifre e trattini bassi, e iniziano con una lettera.age,total_price,player_2. - Tipi sono in
PascalCase: ogni parola con la maiuscola, niente trattini.Int,String,Bool, e i tipi che creerai tu. - Moduli sono in
snake_case, come i file che li contengono.
Se scrivi un nome nello stile sbagliato, non è solo una questione di gusto: non compila.
let myName = "Ada"error: Invalid variable name
┌─ /home/ada/learn-gleam/exercises/src/variables.gleam:4:7
│
4 │ let myName = "Ada"
│ ^^^^^^ This is not a valid variable name
Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or
_.
Try: my_nameC’è anche una regola di buona educazione, che il compilatore fa rispettare con un avvertimento (warning): se crei una variabile e poi non la usi, te lo fa notare.
warning: Unused variable
┌─ /home/ada/learn-gleam/exercises/src/unused.gleam:4:7
│
4 │ let score = 1000
│ ^^^^^ This variable is never used
Hint: You can ignore it with an underscore: `_score`.Un avvertimento non blocca il programma, che parte lo stesso. Ma di solito segnala una svista: ti sei dimenticato di usare qualcosa. Se invece lo fai apposta, il suggerimento ti dice come zittirlo: un nome che inizia con _, come _score, dice “lo so che non lo uso”.
Costanti: valori con un nome per tutto il modulo
Un let vive dentro una funzione. Se un valore serve in tutto il modulo (un’aliquota, un limite, un nome fisso), lo puoi definire fuori da ogni funzione con const:
const max_players = 4
const vat_rate = 0.22Le costanti vanno in cima al file, dopo gli import. Hanno le stesse regole dei nomi delle variabili: snake_case, non in maiuscolo come in molti altri linguaggi.
error: Syntax error
┌─ /home/ada/learn-gleam/exercises/src/variables.gleam:3:7
│
3 │ const MAX = 3
│ ^^^ I'm expecting a lowercase name here
A variable name must start with a lowercase letter, and can contain a-z,
0-9, or _.Il valore di una costante deve essere scritto direttamente (un numero, un testo, True…): non puoi calcolarlo chiamando una funzione. Se vuoi che anche altri moduli la vedano, scrivi pub const.
Mettiamo tutto insieme
Manca un ultimo pezzo: come si stampa un numero con io.println, che accetta solo String? In Gleam niente si converte da solo, quindi lo converti tu, con le funzioni dei moduli della libreria standard dedicati a ciascun tipo:
| Da | Funzione | Esempio |
|---|---|---|
Int | int.to_string (modulo gleam/int) | int.to_string(42) → "42" |
Float | float.to_string (modulo gleam/float) | float.to_string(12.5) → "12.5" |
Bool | bool.to_string (modulo gleam/bool) | bool.to_string(True) → "True" |
Ecco una scheda completa, con una costante, variabili di ogni tipo e un compleanno festeggiato con un nuovo let:
import gleam/bool
import gleam/float
import gleam/int
import gleam/io
const species = "Cat"
pub fn main() -> Nil {
let name = "Tom"
let age = 7
let weight = 4.5
let indoor = True
// Happy birthday!
let age = age + 1
io.println("Name: " <> name)
io.println("Species: " <> species)
io.println("Age: " <> int.to_string(age))
io.println("Weight: " <> float.to_string(weight) <> " kg")
io.println("Indoor: " <> bool.to_string(indoor))
}Name: Tom
Species: Cat
Age: 8
Weight: 4.5 kg
Indoor: TrueEsercizio · sul tuo computer
La tua scheda
Nel progetto exercises crea il modulo src/card.gleam, che stampi esattamente:
Name: Rex
Species: Dog
Age: 4
Weight: 12.5 kg
Vaccinated: TrueRegole del gioco:
- la specie deve essere una costante
species; - l’età deve partire da
3e diventare4con un secondolet(il compleanno); - il peso deve essere un
Floate la vaccinazione unBool, convertiti in testo con le funzioni giuste.
Eseguilo con gleam run -m card e copia qui sotto l’output.
Mostra una soluzione (prima prova da solo!)
import gleam/bool
import gleam/float
import gleam/int
import gleam/io
const species = "Dog"
pub fn main() -> Nil {
let name = "Rex"
let age = 3
let weight = 12.5
let vaccinated = True
// Happy birthday!
let age = age + 1
io.println("Name: " <> name)
io.println("Species: " <> species)
io.println("Age: " <> int.to_string(age))
io.println("Weight: " <> float.to_string(weight) <> " kg")
io.println("Vaccinated: " <> bool.to_string(vaccinated))
}Ricapitolando
let nome = valoredà un nome a un valore; il tipo viene dedotto, ma puoi annotarlo:let age: Int = 34.- I valori sono immutabili: non si riassegna, si crea un nuovo
letcon lo stesso nome. echostampa qualsiasi valore per il debug (e lo restituisce: occhio all’ultima riga dimain).Intper gli interi (senza limiti sulla BEAM),Floatper i decimali (sempre con il punto),BoolperTrue/False,Stringper il testo,Nilper il niente.- Le stringhe si attaccano con
<>;string.lengthconta i caratteri. - Nomi in
snake_case, tipi inPascalCase;_davanti a un nome zittisce l’avvertimento “non usato”. constdefinisce un valore per tutto il modulo, sempre insnake_case.- Niente conversioni automatiche:
int.to_string,float.to_string,bool.to_string.
Nella prossima lezione mettiamo i numeri al lavoro: operatori, confronti, logica, e una sorpresa sulla divisione per zero.