Lezione 2 di 7 · 15 min di lettura
Hello, World!
Il primo programma, smontato parola per parola. Poi lo eseguiamo e lo rompiamo apposta, per conoscere il compilatore.
Il rito di iniziazione
Ogni programmatore, dal 1978 a oggi, ha iniziato scrivendo un programma che stampa “Hello, World!“. Non c’è una legge, ma quasi.
In Gleam ogni programma vive dentro un progetto: una cartella con una struttura precisa, che il comando gleam sa creare da solo. Entra nella cartella degli esercizi e crea il primo:
cd ~/learn-gleam
gleam new helloYour Gleam project hello has been successfully created.
The project can be compiled and tested by running these commands:
cd hello
gleam testGleam ha creato la cartella hello con dentro un programma già pronto. Apri con l’editor il file src/hello.gleam: il codice è quasi quello che ci serve. Cambia solo il testo tra virgolette, così:
import gleam/io
pub fn main() -> Nil {
io.println("Hello, World!")
}Quattro righe. Ora, dal terminale, entra nel progetto ed eseguilo:
cd hello
gleam runLa prima volta Gleam scarica la libreria standard, quindi vedrai qualcosa del genere (i tempi cambiano da computer a computer):
Resolving versions
Downloading packages
Downloaded 2 packages in 0.31s
Added gleam_stdlib v1.0.5
Added gleeunit v1.11.0
Compiling gleam_stdlib
Compiling gleeunit
Compiling hello
Compiled in 0.42s
Running hello.main
Hello, World!Le righe prima di Running le scrive gleam per dirti cosa sta facendo: risolve le versioni delle librerie, le scarica, le compila insieme al tuo codice. Running hello.main significa “sto avviando la funzione main del modulo hello”. L’ultima riga, Hello, World!, l’hai scritta tu. Congratulazioni: hai fatto fare qualcosa a un computer.
Dalla seconda volta in poi le librerie sono già lì, e l’output si accorcia:
Compiling hello
Compiled in 0.22s
Running hello.main
Hello, World!Ora smontiamo il programma. Ogni parola ha un motivo.
Riga 1: import gleam/io
import gleam/ioUn programma Gleam parte “vuoto”: non sa nemmeno stampare sullo schermo. Il codice è organizzato in moduli, e import dice al compilatore quali vuoi usare. gleam/io è il modulo di input/output della libreria standard di Gleam: dentro ci sono le funzioni per scrivere testo nel terminale.
Il nome di un modulo è un percorso, con i livelli separati da /: il modulo gleam/io sta in un file io.gleam dentro una cartella gleam. Una volta importato, lo chiami con l’ultima parte del percorso: io.
Nota anche cosa non c’è: nessun punto e virgola in fondo alla riga. In Gleam non servono, mai.
Riga 3: pub fn main() -> Nil
pub fn main() -> Nil {Questa riga dichiara una funzione: un blocco di codice con un nome, che puoi far eseguire chiamandolo. Leggila da sinistra a destra:
| Pezzo | Significato |
|---|---|
pub | Public: la funzione è visibile da fuori del modulo. Senza pub, resterebbe privata. |
fn | “Sto per definire una funzione.” Tutte le funzioni in Gleam iniziano così. |
main | Il nome della funzione. |
() | La lista dei parametri, cioè dei dati che la funzione riceve. Qui è vuota. |
-> Nil | Il tipo di ritorno: cosa la funzione restituisce a chi la chiama. Nil significa “niente di interessante”. |
Il nome main è speciale: è il punto d’ingresso. Quando lanci gleam run, Gleam cerca la funzione main nel modulo che ha lo stesso nome del progetto (qui hello, cioè il file src/hello.gleam) e comincia da lì. Per questo deve essere pub: gleam run la chiama da fuori.
Le graffe e l’ultima espressione
Le parentesi graffe { e } delimitano il corpo della funzione: tutto ciò che sta in mezzo viene eseguito quando la funzione viene chiamata, dall’alto in basso.
C’è una regola che in Gleam vale sempre, e che conviene imparare subito: il valore dell’ultima espressione del corpo è quello che la funzione restituisce. Non esiste una parola return. Nel nostro main, l’ultima (e unica) espressione è io.println("Hello, World!"), e io.println restituisce… Nil. Ecco perché il conto torna con -> Nil.
Riga 4: io.println("Hello, World!")
io.println("Hello, World!")Questa è una chiamata di funzione: chiediamo alla funzione println del modulo io di fare il suo lavoro. Il punto . separa il modulo dalla funzione che ci vive dentro.
Il testo tra virgolette, "Hello, World!", è una stringa (una sequenza di caratteri) e viene passato alla funzione come argomento. println la stampa e poi va a capo (ln sta per line). Esiste anche io.print, identica ma senza andare a capo.
Dettagli nerd Dove scrive println, esattamente? (e cos'è un buffer)
Ogni programma nasce con tre “canali” già aperti dal sistema operativo: lo standard input (da cui legge, di solito la tastiera), lo standard output (dove scrive, di solito il terminale) e lo standard error (riservato ai messaggi di errore). io.println scrive sullo standard output; la sua sorella io.println_error scrive sullo standard error.
Scrivere davvero sul terminale significa chiedere un servizio al sistema operativo: un’operazione relativamente lenta. Per questo l’output passa spesso da un buffer: una zona di memoria usata come sala d’attesa, dove i caratteri si accumulano per essere spediti tutti insieme. È come aspettare di avere la lavatrice piena prima di avviarla: stesso risultato, molta meno fatica.
Il rientro della riga (i due spazi all’inizio) serve agli occhi umani: al compilatore non interessa. In Gleam la convenzione è due spazi, e non devi nemmeno ricordartelo: il comando gleam format sistema da solo spazi, rientri e a capo di tutto il progetto. Lo rivedremo nella prossima lezione.
Quiz
Cosa succede se rinomini main in start e lanci gleam run?
Cosa succede davvero con gleam run
gleam run fa tre cose in sequenza:
- Controlla il programma: sintassi, nomi, tipi. Se qualcosa non torna, si ferma qui e ti spiega il problema.
- Traduce ogni file
.gleamin un file Erlang, e da lì in bytecode per la BEAM (file.beam). Tutto finisce nella cartellabuild/del progetto. - Avvia la BEAM e le fa eseguire la tua funzione
main.
Rompiamolo apposta
Il modo più rapido per conoscere un compilatore è farlo arrabbiare. Togli la parentesi tonda chiusa:
import gleam/io
pub fn main() -> Nil {
io.println("Hello, World!"
}error: Syntax error
┌─ /home/ada/learn-gleam/hello/src/hello.gleam:5:1
│
5 │ }
│ ^ I was not expecting this
Found `}`, expected one of:
- `)`Il compilatore ti dice dove si è bloccato (file, riga 5, colonna 1), sottolinea con ^ il punto esatto e spiega: ha trovato una } quando si aspettava una ). Nota che l’errore è sulla riga dopo quella sbagliata: il compilatore si accorge che manca la parentesi solo quando incontra qualcosa che non può stare lì.
Ora rimetti la parentesi e prova a stampare un numero invece di un testo:
io.println(4)error: Type mismatch
┌─ /home/ada/learn-gleam/hello/src/hello.gleam:4:14
│
4 │ io.println(4)
│ ^
Expected type:
String
Found type:
IntQuesto è il controllo dei tipi al lavoro: io.println accetta solo stringhe (String), e 4 è un numero intero (Int). Gleam non converte niente di nascosto: se vuoi stampare un numero, devi prima trasformarlo in testo. Vedremo come fra un paio di lezioni.
Infine, togli le prime due righe (l’import e la riga vuota sotto):
error: Unknown module
┌─ /home/ada/learn-gleam/hello/src/hello.gleam:2:3
│
2 │ io.println("Hello, World!")
│ ^^
No module has been found with the name `io`.
Hint: Did you mean to import `gleam/io`?Il compilatore non sa cosa sia io, ma indovina cosa volevi fare e te lo suggerisce. Ogni errore che provochi adesso è un errore che riconoscerai al volo dopo.
Quiz
Quale di queste righe stampa hello e poi world sulla stessa riga?
Più righe, più stampe
Puoi mettere quante espressioni vuoi dentro main, una per riga. Vengono eseguite in ordine, dall’alto in basso:
import gleam/io
pub fn main() -> Nil {
io.println("First line")
io.println("Second line")
io.print("Third")
io.print(" line")
io.println("")
}First line
Second line
Third lineNota l’ultima io.println(""): stampa una stringa vuota e va a capo. È un trucco comune per “chiudere” una riga costruita con più print. Ed è anche l’ultima espressione del corpo: restituisce Nil, come promesso da -> Nil.
Esercizio · sul tuo computer
Presentati alla macchina
Crea un nuovo progetto chiamato greeting (dalla cartella ~/learn-gleam, con gleam new greeting) e modifica src/greeting.gleam perché stampi esattamente queste due righe:
Hello, Gleam!
I'm ready.Eseguilo con gleam run dentro la cartella del progetto, poi copia qui sotto le righe stampate dal tuo programma (non quelle di gleam, come Compiling e Running).
Mostra una soluzione (prima prova da solo!)
import gleam/io
pub fn main() -> Nil {
io.println("Hello, Gleam!")
io.println("I'm ready.")
}Commenti: note per gli umani
Una cosa che vedrai spesso nel codice sono i commenti: testo ignorato dal compilatore, scritto per chi legge. In Gleam iniziano tutti con delle barre, e ce ne sono di tre tipi:
//// This module prints a greeting.
//// Four slashes document the whole module.
import gleam/io
/// Three slashes document the definition that follows:
/// here, the `main` function.
pub fn main() -> Nil {
// Two slashes: an ordinary comment, for whoever reads the code.
io.println("Hello")
}//è un commento normale: dura fino alla fine della riga.///è un commento di documentazione: descrive la funzione (o il tipo) che viene subito dopo.////documenta il modulo intero, e va in cima al file.
I commenti di documentazione non sono solo per chi legge il sorgente: il comando gleam docs build li raccoglie e ne fa un sito di documentazione, e l’editor te li mostra quando passi sopra un nome. Non esistono commenti su più righe con /* */: per un commento lungo, si ripetono le barre a ogni riga.
Usa i commenti per spiegare il perché, non il cosa. // prints Hello non aiuta nessuno; // the greeting must stay in English for the automated test sì. E per convenzione il commento va sulla riga prima della cosa di cui parla, non in fondo alla stessa riga.
Ricapitolando
- In Gleam ogni programma vive in un progetto: si crea con
gleam new nomee si esegue congleam run. import gleam/iorende disponibili le funzioni di stampa, che poi chiami comeio.println.pub fn main() -> Nil { ... }è il punto d’ingresso, nel modulo con il nome del progetto.- Una funzione restituisce la sua ultima espressione: niente
return, niente punti e virgola. io.println("...")stampa e va a capo;io.printnon va a capo.- Il compilatore controlla i tipi prima di eseguire:
io.printlnvuole unaString, non unInt. - Commenti:
//normale,///documenta una definizione,////documenta il modulo.
Nella prossima lezione guardiamo dentro la cartella del progetto: cosa sono tutti quei file che gleam new ha creato, e quali altri comandi ci mette a disposizione gleam.