Lezione 6 di 6 · 22 min di lettura
Riepilogo e sfida finale
I tipi del modulo in una pagina, tre domande di controllo, e un robot su una griglia costruito con varianti, record e fold.
Guarda quanta strada
In questo modulo hai smesso di usare solo i tipi che Gleam ti dava, e hai cominciato a costruire i tuoi. Adesso sai:
- raggruppare valori al volo con le tuple, e restituirne più d’uno da una funzione;
- definire tipi con varianti, e lasciare al compilatore il controllo di ogni
case; - costruire record con i campi con un nome, leggerli, smontarli e aggiornarli;
- combinare varianti e dati, e scegliere i tipi in modo che gli stati senza senso non si possano scrivere;
- scrivere tipi generici, e rappresentare un valore che potrebbe mancare con
Option.
La pagina da tenere accanto
| Cosa | Come si scrive |
|---|---|
| Tupla | #(3, "three"), tipo #(Int, String) |
| Elemento di una tupla | pair.0, pair.1 |
| Smontare una tupla | let #(a, b) = pair |
| Coppie da due liste | list.zip(a, b), list.unzip(pairs) |
| Tipo con varianti | pub type Season { Spring Summer Autumn Winter } |
| Record | pub type Person { Person(name: String, age: Int) } |
| Costruire | Person(name: "Ada", age: 36) |
| Leggere un campo | ada.name |
| Smontare | let Person(name:, ..) = ada, o in un case |
| Aggiornare | Person(..ada, age: 37) |
| Varianti con dati | Circle(radius: Float), Rectangle(width: Float, height: Float) |
| Alternative tra varianti | Cash(cents:) \| Card(cents:, ..) -> cents |
| Tipo generico | pub type Maybe(a) { Just(a) Nothing } |
| Valore che può mancare | Option(a): Some(x), None |
| Importare per nome | import gleam/option.{type Option, None, Some} |
| Alias | pub type Cents = Int |
Trovi tutto anche nel Codex (tasto K).
Quiz di controllo
Quiz
Con let #(a, b) = divmod(7, 2) e fn divmod(x: Int, y: Int) -> #(Int, Int) { #(x / y, x % y) }, quanto valgono a e b?
Quiz
Aggiungi una variante Triangle(base: Float, height: Float) al tipo Shape. Cosa succede alla funzione area, che ha un case con una clausola per ogni altra forma?
Quiz
Con pub type User { Guest LoggedIn(name: String) } e un user: User, cosa si scrive per ottenerne il nome?
La sfida: il robot
Un piccolo robot si muove su una griglia. Parte dalla casella (0, 0), guardando a nord, e riceve una lista di comandi: avanti di un certo numero di caselle, gira a sinistra, gira a destra. Andare verso nord aumenta y, verso sud la diminuisce; verso est aumenta x, verso ovest la diminuisce. Girare cambia solo la direzione, non la posizione.
Esercizio · sul tuo computer
Il robot
Nel progetto exercises crea src/robot.gleam con tre tipi:
Direction, con le quattro direzioniNorth,East,South,West;Robot, un record conx,y(interi) efacing(unaDirection);Command, con le variantiForward(steps: Int),TurnLefteTurnRight.
Poi le funzioni:
turn_lefteturn_right, da unaDirectionalla nuova direzione;run(robot: Robot, command: Command) -> Robot, che esegue un comando e restituisce il robot aggiornato (usa la sintassiRobot(..robot, ...));describe(robot: Robot) -> String, come(2, 3) facing East, ecommand_name(command: Command) -> String, comeForward 3oTurn right;distance(from: Robot, to: Robot) -> Int, la distanza “a isolati” tra due posizioni: la differenza dellexpiù la differenza delley, in valore assoluto (int.absolute_value).
In main il robot riceve i comandi [Forward(3), TurnRight, Forward(2), TurnRight, Forward(5), TurnLeft, Forward(1)]. Esegui la lista con un list.fold che parte dal robot iniziale e, a ogni passo, stampa il comando e la nuova posizione. Alla fine stampa la distanza dal punto di partenza:
Start: (0, 0) facing North
Forward 3 -> (0, 3) facing North
Turn right -> (0, 3) facing East
Forward 2 -> (2, 3) facing East
Turn right -> (2, 3) facing South
Forward 5 -> (2, -2) facing South
Turn left -> (2, -2) facing East
Forward 1 -> (3, -2) facing East
Distance: 5Suggerimento: la funzione del fold fa tre cose. Calcola il nuovo robot con un let, lo stampa, e lo restituisce come nuovo accumulatore.
Mostra una soluzione (prima prova da solo!)
import gleam/int
import gleam/io
import gleam/list
pub type Direction {
North
East
South
West
}
pub type Robot {
Robot(x: Int, y: Int, facing: Direction)
}
pub type Command {
Forward(steps: Int)
TurnLeft
TurnRight
}
pub fn main() -> Nil {
let start = Robot(x: 0, y: 0, facing: North)
let commands = [
Forward(3),
TurnRight,
Forward(2),
TurnRight,
Forward(5),
TurnLeft,
Forward(1),
]
io.println("Start: " <> describe(start))
let finish =
list.fold(commands, start, fn(robot, command) {
let next = run(robot, command)
io.println(command_name(command) <> " -> " <> describe(next))
next
})
io.println("Distance: " <> int.to_string(distance(start, finish)))
}
fn run(robot: Robot, command: Command) -> Robot {
case command {
TurnLeft -> Robot(..robot, facing: turn_left(robot.facing))
TurnRight -> Robot(..robot, facing: turn_right(robot.facing))
Forward(steps:) ->
case robot.facing {
North -> Robot(..robot, y: robot.y + steps)
South -> Robot(..robot, y: robot.y - steps)
East -> Robot(..robot, x: robot.x + steps)
West -> Robot(..robot, x: robot.x - steps)
}
}
}
fn turn_right(direction: Direction) -> Direction {
case direction {
North -> East
East -> South
South -> West
West -> North
}
}
fn turn_left(direction: Direction) -> Direction {
case direction {
North -> West
West -> South
South -> East
East -> North
}
}
fn describe(robot: Robot) -> String {
"("
<> int.to_string(robot.x)
<> ", "
<> int.to_string(robot.y)
<> ") facing "
<> direction_name(robot.facing)
}
fn direction_name(direction: Direction) -> String {
case direction {
North -> "North"
East -> "East"
South -> "South"
West -> "West"
}
}
fn command_name(command: Command) -> String {
case command {
Forward(steps:) -> "Forward " <> int.to_string(steps)
TurnLeft -> "Turn left"
TurnRight -> "Turn right"
}
}
fn distance(from: Robot, to: Robot) -> Int {
int.absolute_value(to.x - from.x) + int.absolute_value(to.y - from.y)
}Qualche dettaglio da notare:
- Nessuna variabile cambia: ogni comando produce un robot nuovo, e il
foldpassa il più recente al comando successivo. Il robot di partenza,start, è ancora lì intatto alla fine, ed è per questo che si può calcolare la distanza. runha uncasedentro uncase: prima il comando, poi, perForward, la direzione. Ogni combinazione possibile è gestita, e il compilatore lo verifica.- Stampare dentro la funzione del
foldfunziona, ma mescola il calcolo con l’output. Un’alternativa più pulita: calcolare prima la lista di tutte le posizioni (conlist.scan, che è comefoldma restituisce tutti gli accumulatori intermedi) e stamparla dopo.
Cosa succede nel Modulo 5
In questo modulo abbiamo sempre scritto noi i dati: la lista delle persone, i comandi del robot. Ma i programmi veri ricevono dati dall’esterno, e i dati esterni possono essere sbagliati: un numero che non è un numero, un comando sconosciuto, un file che non c’è. Come fa un linguaggio senza eccezioni a gestire gli errori?
Nel prossimo modulo scopriremo il tipo Result, con le sue varianti Ok ed Error, il modulo gleam/result per concatenare operazioni che possono fallire, e la parola chiave use, che rende questo codice sorprendentemente leggibile. E potremo finalmente scrivere un robot che legge i comandi da un testo come "F3 R F2".