Lezione 5 di 7 · 18 min di lettura
Operatori
Fare i conti con Int e Float (che non si mescolano), confrontare valori, combinare condizioni. E una sorpresa sulla divisione per zero.
Il computer è una calcolatrice molto pignola
Le quattro operazioni funzionano come te le aspetti, più una quinta: il resto.
import gleam/int
import gleam/io
pub fn main() -> Nil {
let a = 17
let b = 5
io.println("a + b = " <> int.to_string(a + b))
io.println("a - b = " <> int.to_string(a - b))
io.println("a * b = " <> int.to_string(a * b))
io.println("a / b = " <> int.to_string(a / b))
io.println("a % b = " <> int.to_string(a % b))
}a + b = 22
a - b = 12
a * b = 85
a / b = 3
a % b = 2Somma, sottrazione e moltiplicazione sono quelle di sempre. Le ultime due meritano un paragrafo ciascuna.
La divisione tra interi
17 diviso 5 fa 3,4. Ma a / b ha stampato 3. Non è un errore: quando dividi due Int, il risultato è un Int, e la parte decimale viene tagliata via (non arrotondata: tagliata). 9 / 2 fa 4, non 5; 1 / 3 fa 0.
Con i numeri negativi il taglio avviene verso lo zero: -7 / 2 fa -3, non -4.
Quiz
Quanto vale 9 / 2 * 2?
Il resto: %
L’operatore % restituisce il resto della divisione intera: 17 diviso 5 fa 3 con il resto di 2, quindi 17 % 5 vale 2. Sembra un’operazione da scuola elementare, ma è uno degli attrezzi più usati in programmazione:
n % 2vale 0 senè pari, 1 se è dispari;seconds % 60sono i secondi “avanzati” dopo aver contato i minuti interi;cents % 100sono i centesimi dopo aver tolto gli euro interi.
Insieme, / e % spezzano un numero in unità più grandi e avanzo: 137 minuti sono 137 / 60 = 2 ore e 137 % 60 = 17 minuti.
La sorpresa: dividere per zero
Cosa fa 10 / 0? In matematica non ha senso, e in quasi tutti i linguaggi il programma si ferma con un errore. In Gleam, invece:
import gleam/io
pub fn main() -> Nil {
echo 10 / 0
echo 10 % 0
echo 3.0 /. 0.0
io.println("Still alive")
}src/zero.gleam:4
0
src/zero.gleam:5
0
src/zero.gleam:6
0.0
Still aliveIn Gleam, dividere per zero dà zero. La spiegazione sta nelle regole del linguaggio: Gleam non ha eccezioni, quindi non può “lanciare un errore”, e la BEAM non ha un valore “infinito” da restituire. L’unica scelta rimasta è restituire zero, e la documentazione ufficiale lo dice chiaramente.
I Float hanno i loro operatori
Ed ecco una particolarità di Gleam che all’inizio fa storcere il naso: gli operatori sui Float sono diversi da quelli sugli Int. Hanno un punto in più.
| Operazione | Con Int | Con Float |
|---|---|---|
| somma | + | +. |
| sottrazione | - | -. |
| moltiplicazione | * | *. |
| divisione | / | /. |
7.0 /. 2.0 fa 3.5, come ti aspetti. Ma se provi a mescolare i tipi, il compilatore ti ferma e ti dice cosa usare:
error: Type mismatch
┌─ /home/ada/learn-gleam/hello/src/hello.gleam:5:23
│
5 │ let total = price * 1.5
│ ^^^
The * operator expects arguments of this type:
Int
But this argument has this type:
Float
Hint: The *. operator can be used with FloatsQui price era un Int, e 1.5 un Float: * vuole due Int, *. vuole due Float, e non esiste un operatore che accetti un po’ e un po’. Se hai un Int e ti serve come Float, lo converti tu con int.to_float. Per il viaggio inverso, da Float a Int, devi decidere come perdere i decimali:
| Funzione | Cosa fa | Esempio |
|---|---|---|
int.to_float | da Int a Float | int.to_float(3) → 3.0 |
float.round | arrotonda all’Int più vicino | float.round(2.5) → 3 |
float.truncate | taglia i decimali, dà un Int | float.truncate(2.9) → 2 |
float.floor | arrotonda per difetto, resta Float | float.floor(2.9) → 2.0 |
float.ceiling | arrotonda per eccesso, resta Float | float.ceiling(2.1) → 3.0 |
import gleam/float
import gleam/int
import gleam/io
pub fn main() -> Nil {
let price = 2.5
let quantity = 3
let total = price *. int.to_float(quantity)
io.println("Total: " <> float.to_string(total))
}Total: 7.5C’è poi un dettaglio che riguarda i decimali di ogni linguaggio, non solo di Gleam:
import gleam/io
pub fn main() -> Nil {
echo 0.1 +. 0.2
io.println("Done")
}src/tenths.gleam:4
0.30000000000000004
DoneNon è un bug di Gleam: è la conseguenza del modo in cui i computer memorizzano i decimali, raccontato nei dettagli nerd della lezione precedente (“Come si scrive 3,14 con solo 0 e 1?”). 0,1 e 0,2 non sono rappresentabili esattamente in binario, e i due piccoli errori sommati diventano visibili. Per questo i soldi, in un programma serio, non si tengono mai in Float: si contano in centesimi, con gli Int. Lo faremo tra poco.
Niente x += 1
Molti linguaggi hanno scorciatoie per modificare una variabile: x += 5, x++. In Gleam non ci sono, per il motivo che conosci: niente si modifica. Quando ti serve un valore aggiornato, crei un nuovo let:
let score = 10
let score = score + 5Confronti: domande con risposta sì o no
Un confronto è un’espressione che fa una domanda e restituisce un Bool: True o False.
| Operatore | Domanda | Con Float |
|---|---|---|
< | è minore? | <. |
> | è maggiore? | >. |
<= | è minore o uguale? | <=. |
>= | è maggiore o uguale? | >=. |
== | è uguale? | == |
!= | è diverso? | != |
Anche qui i Float hanno la loro versione con il punto, e se ti dimentichi il compilatore te lo ricorda con un Use >. instead. Fanno eccezione == e !=, che funzionano con qualsiasi tipo: numeri, stringhe, booleani. L’unica condizione è che i due lati siano dello stesso tipo: 1 == 1.0 non compila, perché un Int e un Float non sono mai “uguali” per Gleam, sono proprio cose diverse.
import gleam/io
pub fn main() -> Nil {
let age = 17
let height = 1.72
let name = "gleam"
echo age >= 18
echo height >. 1.7
echo name == "Gleam"
echo age != 18
io.println("Done")
}src/compare.gleam:7
False
src/compare.gleam:8
True
src/compare.gleam:9
False
src/compare.gleam:10
True
DoneNota name == "Gleam": il confronto tra stringhe distingue maiuscole e minuscole, quindi "gleam" e "Gleam" sono diverse.
Logica: combinare le domande
Spesso una domanda sola non basta. Per combinarle ci sono tre operatori logici:
| Operatore | Nome | È True quando… |
|---|---|---|
&& | e (and) | entrambi i lati sono True |
\|\| | o (or) | almeno uno dei due lati è True |
! | non (not) | il valore è False (lo capovolge) |
let is_teenager = age >= 13 && age <= 19
let gets_discount = age < 14 || age > 65
let is_adult = !is_teenager && age > 19Quiz
Con let a = 5 e let b = 10, quanto vale a * 2 == b && b / a == 2?
Precedenza: chi va per primo
Come in matematica, 2 + 3 * 4 fa 14, non 20: la moltiplicazione ha la precedenza sulla somma. L’ordine completo, da chi va per primo:
!e il meno davanti a un numero (-x);*,/,%(e le versioni con il punto);+,-(e le versioni con il punto);|>, che incontreremo nella prossima lezione;<>per attaccare le stringhe;- i confronti di grandezza
<,>,<=,>=; - i confronti di uguaglianza
==,!=; &&;||.
Non serve impararla a memoria: è costruita perché le espressioni “normali” si leggano come ti aspetti. a + 1 > b && ok è { a + 1 } > b, e il risultato messo in && con ok.
E se vuoi cambiare l’ordine? In matematica si usano le parentesi tonde. In Gleam, sorpresa: si usano le graffe.
let result = { 2 + 3 } * 4Ora result vale 20. Se provi con le tonde, il compilatore ti corregge con garbo:
error: Syntax error
┌─ /home/ada/learn-gleam/hello/src/hello.gleam:2:11
│
2 │ let x = (1 + 2) * 3
│ ^ This parenthesis cannot be understood here
Hint: To group expressions in Gleam, use "{" and "}"; tuples are created
with `#(` and `)`.Le graffe non sono una stranezza a caso: in Gleam { ... } è un blocco, un gruppo di espressioni che vale quanto la sua ultima espressione. Proprio come il corpo di una funzione. { 2 + 3 } è semplicemente un blocco che contiene una sola espressione, e vale 5.
Dettagli nerd Perché le tonde servono ad altro?
In Gleam le parentesi tonde hanno già due lavori: racchiudono gli argomenti delle funzioni, come in io.println("hi"), e, precedute da #, costruiscono le tuple, gruppi di valori come #(1, "one") che incontreremo più avanti. Usarle anche per raggruppare i calcoli renderebbe il codice ambiguo da leggere, per le persone e per il compilatore. Con le graffe, ogni simbolo ha un solo significato.
Un programma vero
Mettiamo insieme tutto quello che sappiamo per calcolare un resto, con i soldi contati in centesimi:
import gleam/bool
import gleam/int
import gleam/io
pub fn main() -> Nil {
let price = 1370
let paid = 2000
let change = paid - price
let euros = change / 100
let cents = change % 100
io.println("Change: " <> int.to_string(change) <> " cents")
io.println(
"That is "
<> int.to_string(euros)
<> " euros and "
<> int.to_string(cents)
<> " cents",
)
io.println("Enough for a coffee: " <> bool.to_string(change >= 120))
}Change: 630 cents
That is 6 euros and 30 cents
Enough for a coffee: TrueNota come gleam format ha spezzato la stringa lunga: una riga per pezzo, con i <> all’inizio. E l’ultimo argomento di io.println ha una virgola in fondo, che Gleam permette (e il formattatore aggiunge) quando gli argomenti vanno a capo.
Esercizio · sul tuo computer
Quanto dura?
Nel progetto exercises crea src/duration.gleam. Parti da un totale di 7384 secondi e stampa esattamente:
Total: 7384 seconds
Time: 2 h 3 min 4 s
More than 2 hours: TrueSuggerimenti: le ore sono i secondi diviso 3600. Quello che avanza (% 3600) va diviso per 60 per avere i minuti, e quello che avanza ancora (% 60) sono i secondi. L’ultima riga è un confronto convertito in testo con bool.to_string.
Mostra una soluzione (prima prova da solo!)
import gleam/bool
import gleam/int
import gleam/io
pub fn main() -> Nil {
let total = 7384
let hours = total / 3600
let minutes = total % 3600 / 60
let seconds = total % 60
io.println("Total: " <> int.to_string(total) <> " seconds")
io.println(
"Time: "
<> int.to_string(hours)
<> " h "
<> int.to_string(minutes)
<> " min "
<> int.to_string(seconds)
<> " s",
)
io.println("More than 2 hours: " <> bool.to_string(total > 2 * 3600))
}total % 3600 / 60 funziona senza graffe perché % e / hanno la stessa precedenza e vengono calcolati da sinistra a destra: prima il resto, poi la divisione. Per i secondi basta total % 60: le ore e i minuti interi sono tutti multipli di 60, quindi non cambiano il resto.
Ricapitolando
+ - * /e%(resto) per gliInt; la divisione tra interi taglia i decimali.- Dividere per zero dà zero, sia con gli
Intsia con iFloat: se un divisore può essere zero, controllalo tu. - I
Floathanno operatori propri:+. -. *. /.e<. >. <=. >=.. Int e Float non si mescolano mai. - Conversioni:
int.to_float, e verso gli interifloat.roundofloat.truncate. 0.1 +. 0.2non fa esattamente0.3: i soldi si contano in centesimi, con gliInt.- Niente
+=né++: si crea un nuovolet. ==e!=funzionano su ogni tipo, purché i due lati siano dello stesso tipo.&&,||,!per la logica, con il cortocircuito.- Per raggruppare si usano le graffe:
{ 2 + 3 } * 4.
Nella prossima lezione ci concentriamo sull’output: come costruire esattamente il testo che vuoi, con i decimali giusti e le colonne allineate.