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:

terminale
cd ~/learn-gleam
gleam new hello
output
Your Gleam project hello has been successfully created.
The project can be compiled and tested by running these commands:

	cd hello
	gleam test

Gleam 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ì:

src/hello.gleam
import gleam/io

pub fn main() -> Nil {
  io.println("Hello, World!")
}

Quattro righe. Ora, dal terminale, entra nel progetto ed eseguilo:

terminale
cd hello
gleam run

La prima volta Gleam scarica la libreria standard, quindi vedrai qualcosa del genere (i tempi cambiano da computer a computer):

output
  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:

output
  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

gleam
import gleam/io

Un 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

gleam
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:

PezzoSignificato
pubPublic: la funzione è visibile da fuori del modulo. Senza pub, resterebbe privata.
fn“Sto per definire una funzione.” Tutte le funzioni in Gleam iniziano così.
mainIl nome della funzione.
()La lista dei parametri, cioè dei dati che la funzione riceve. Qui è vuota.
-> NilIl 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!")

gleam
  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:

  1. Controlla il programma: sintassi, nomi, tipi. Se qualcosa non torna, si ferma qui e ti spiega il problema.
  2. Traduce ogni file .gleam in un file Erlang, e da lì in bytecode per la BEAM (file .beam). Tutto finisce nella cartella build/ del progetto.
  3. 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:

src/hello.gleam
import gleam/io

pub fn main() -> Nil {
  io.println("Hello, World!"
}
output
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:

gleam
  io.println(4)
output
error: Type mismatch
  ┌─ /home/ada/learn-gleam/hello/src/hello.gleam:4:14
  │
4 │   io.println(4)
  │              ^

Expected type:

    String

Found type:

    Int

Questo è 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):

output
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:

src/hello.gleam
import gleam/io

pub fn main() -> Nil {
  io.println("First line")
  io.println("Second line")
  io.print("Third")
  io.print(" line")
  io.println("")
}
output
First line
Second line
Third line

Nota 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:

output
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!)
src/greeting.gleam
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:

src/hello.gleam
//// 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 nome e si esegue con gleam run.
  • import gleam/io rende disponibili le funzioni di stampa, che poi chiami come io.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.print non va a capo.
  • Il compilatore controlla i tipi prima di eseguire: io.println vuole una String, non un Int.
  • 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.