Lezione 5 di 6 · 20 min di lettura
Dare forma a un programma
Dal progetto degli esercizi a un programma vero. Il modulo principale, le cartelle e i nomi dei moduli, quanti moduli scrivere, i moduli interni, la documentazione con gleam docs build, e un eseguibile da consegnare con gleam export escript.
Dall’esercizio al programma
Il progetto exercises è stato una palestra perfetta: un modulo per esercizio, ognuno lanciato con gleam run -m. Ma un programma vero è un’altra cosa: ha un nome, un punto d’ingresso, e il suo codice è diviso in moduli che collaborano. In questa lezione prepariamo lo scheletro del programma della sfida finale: shelf, uno scaffale di libri da usare dalla riga di comando.
Dalla cartella ~/learn-gleam (non da dentro exercises):
gleam new shelf
cd shelf
gleam add gleam_json simplifile argvIl modulo principale
gleam new ha creato src/shelf.gleam: il modulo con lo stesso nome del progetto. È il modulo principale, quello che gleam run esegue quando non gli dici quale modulo usare (lezione 1.3). Il suo main è il punto d’ingresso del programma.
Il modulo principale dovrebbe restare sottile: legge gli argomenti, chiama le funzioni giuste degli altri moduli, stampa i risultati. Il lavoro vero sta altrove.
Le cartelle
Il resto del codice va in una cartella con lo stesso nome del progetto, src/shelf/. Un file src/shelf/book.gleam è il modulo shelf/book:
//// Books on the shelf.
import gleam/int
/// A book, with the year it was published.
pub type Book {
Book(title: String, year: Int)
}
/// A short description, like `Dune (1965)`.
pub fn to_string(book: Book) -> String {
book.title <> " (" <> int.to_string(book.year) <> ")"
}e dal modulo principale si importa con il percorso completo, ma si usa con l’ultimo pezzo, come gleam/list si usa come list:
import gleam/io
import shelf/book.{Book}
pub fn main() -> Nil {
let dune = Book(title: "Dune", year: 1965)
io.println(book.to_string(dune))
}Dune (1965)Perché proprio una cartella con il nome del progetto? Perché in Gleam, come in tutto l’ecosistema della BEAM, i nomi dei moduli sono globali: se il tuo progetto avesse un modulo book, e un giorno aggiungessi un pacchetto che ha anche lui un modulo book, il progetto smetterebbe di compilare. Mettendo tutto sotto shelf/, il rischio sparisce. È la stessa ragione per cui i moduli della libreria standard stanno sotto gleam/; e un pacchetto piccolo come simplifile ha un solo modulo, con il nome del pacchetto.
Quanti moduli?
La guida ufficiale alle convenzioni dà tre consigli su come dividere il codice, e vale la pena seguirli fin da subito:
- Nomi al singolare:
shelf/book, nonshelf/books. Anche nei pezzi intermedi:app/payment/invoice. - Dividi per argomento, non per categoria: un modulo per ogni “cosa” del tuo programma (i libri, il salvataggio su file), e mai moduli come
types.gleam,utils.gleamohelpers.gleam, che raccolgono pezzi scollegati solo perché sono dello stesso genere. - Non frammentare: un modulo lungo non è un problema. Molti moduli piccoli, invece, costringono chi li usa a importarne cinque per fare una cosa semplice, e ti obbligano a rendere pubbliche funzioni che dovrebbero restare private. Dividi quando un pezzo ha davvero una vita sua.
Per lo scaffale basteranno tre moduli: shelf (la riga di comando), shelf/book (i libri) e shelf/storage (il file dove li salviamo).
I moduli interni
A volte un modulo serve agli altri moduli del progetto, ma non è pensato per chi usa il progetto come libreria. Per questi casi Gleam ha i moduli interni: di serie, sono tutti quelli sotto src/<progetto>/internal/, come shelf/internal/format. Si importano normalmente dal tuo codice, ma non compaiono nella documentazione, e non fanno parte di quello che il pacchetto promette: possono cambiare in qualsiasi momento, anche tra due versioni compatibili. Se ti serve un’altra regola, la scrivi in gleam.toml, con internal_modules.
Per un programma come shelf, che non verrà usato come libreria, non servono. Diventano importanti quando scriverai un pacchetto da pubblicare.
La documentazione
I commenti /// e //// che hai imparato nel modulo 1 non sono solo per chi legge il codice: gleam docs build li trasforma in un sito di documentazione, lo stesso formato di hexdocs.pm.
gleam docs build Generating documentation
The documentation for shelf has been rendered to
/home/ada/learn-gleam/shelf/build/dev/docs/shelf/index.htmlApri quel file nel browser (o lancia gleam docs build --open): c’è una pagina per ogni modulo, con i tipi e le funzioni pubbliche, i loro tipi, e i tuoi commenti. Le funzioni private non compaiono: sono affari del modulo. È un ottimo modo per vedere il tuo codice dal punto di vista di chi lo usa.
Consegnare il programma
Finora i programmi si lanciavano con gleam run, dalla cartella del progetto. Ma per usare shelf come uno strumento vero, o per darlo a un’altra persona, serve qualcosa di più comodo. gleam export escript impacchetta il programma, con tutte le sue dipendenze, in un unico file:
gleam export escriptYour escript has been generated to /home/ada/learn-gleam/shelf/shelf.Il file shelf nella cartella del progetto è il programma completo, e si lancia direttamente, senza gleam run:
./shelfDune (1965)Puoi copiarlo dove vuoi, anche su un altro computer: l’unica cosa che serve è che lì sia installato Erlang. Gli argomenti si passano normalmente, senza il --: ./shelf add Dune 1965.
Dettagli nerd Cos'è un escript, e perché serve comunque Erlang?
Un escript è un formato di Erlang per i programmi da riga di comando: un file che comincia con una riga speciale, #!/usr/bin/env escript, seguita dal bytecode compresso di tutti i moduli. Quella prima riga, lo shebang, dice al sistema operativo con quale programma aprire il file: qui, con escript, che fa parte di Erlang. Per questo il file è piccolo (qualche centinaio di KB), ma non basta da solo: la macchina virtuale che esegue il bytecode deve esserci.
Per i server e le applicazioni più grandi c’è gleam export erlang-shipment, che invece di un file unico prepara una cartella con il bytecode e uno script di avvio.
Quiz
Il progetto si chiama shelf. Dove metti il modulo che salva i libri su file, e come lo importi?
Esercizio · sul tuo computer
Lo scheletro dello scaffale
Nel progetto shelf (non in exercises):
- In
src/shelf/book.gleamaggiungi al tipoBookun camporead: Bool, una funzionemark_read(book) -> Bookche lo segna come letto, e cambiato_stringperché metta davanti[x]se il libro è letto e[ ]se no. Scrivi un commento///per ogni funzione pubblica. - In
src/shelf.gleamcrea Dune (1965) ed Emma (1815), segna Emma come letto conbook.mark_read, e stampa entrambi. - Lancia
gleam run, poigleam docs build, e guarda come appaiono i tuoi commenti. - Crea l’eseguibile con
gleam export escript, e lancialo con./shelf.
[ ] Dune (1965)
[x] Emma (1815)Mostra una soluzione (prima prova da solo!)
//// Books on the shelf.
import gleam/int
/// A book, with the year it was published and whether it has been read.
pub type Book {
Book(title: String, year: Int, read: Bool)
}
/// The same book, marked as read.
pub fn mark_read(book: Book) -> Book {
Book(..book, read: True)
}
/// A one-line description, like `[x] Dune (1965)`.
pub fn to_string(book: Book) -> String {
let mark = case book.read {
True -> "[x] "
False -> "[ ] "
}
mark <> book.title <> " (" <> int.to_string(book.year) <> ")"
}import gleam/io
import shelf/book.{Book}
pub fn main() -> Nil {
let dune = Book(title: "Dune", year: 1965, read: False)
let emma = Book(title: "Emma", year: 1815, read: False) |> book.mark_read
io.println(book.to_string(dune))
io.println(book.to_string(emma))
}mark_read usa la sintassi di aggiornamento dei record (lezione 4.3): restituisce un libro nuovo, uguale al vecchio tranne che per read.
Ricapitolando
- Il modulo con il nome del progetto (
src/shelf.gleam) è il principale:gleam runesegue il suomain. Tienilo sottile. - Gli altri moduli vanno sotto
src/<progetto>/:src/shelf/book.gleamsi importa conimport shelf/booke si usa comebook. I nomi dei moduli sono globali, e la cartella li protegge dalle collisioni. - Nomi al singolare, moduli divisi per argomento (mai
utilsotypes), e senza frammentare. - I moduli sotto
<progetto>/internal/sono interni: non finiscono nella documentazione. gleam docs buildtrasforma i commenti///e////in documentazione HTML.gleam export escriptcrea un unico file eseguibile, che richiede solo Erlang.
Nella prossima lezione, la sfida finale: lo scaffale completo, con i libri salvati in JSON e i comandi dalla riga di comando.