Heroes / Guida / capitolo 10

Le mappe

La ricerca di una chiave restituisce un valore oppure un errore.

Una mappa associa delle chiavi a dei valori. Il suo tipo si scrive {K: V}, dove K è il tipo delle chiavi e V quello dei valori. Una ricerca può trovare la chiave oppure fallire: il tipo del risultato rappresenta entrambi i casi.

m[k] è un V?, non un V

Scrivi la ricerca da sola e il compilatore ti dice che cosa hai in mano:

$ heroes check maps.hero
error[type_mismatch]: expected `i64`, found `i64?`
  at maps.hero:9:12
    |
  9 |     return scores[name]
    |            ^^^^^^^^^^^^
  fix (guess): `.must()` — abort on the error case, giving `i64`

i64? è il tipo fallibile del capitolo 5: un numero intero oppure un errore. Se la chiave manca, l’errore ha codice missing_key. Prima di usare il numero devi gestire questa possibilità.

Quattro modi di gestire una ricerca

# The four ways to read a `V?`, on one line each (§4.6).
function score_of(scores: {str: i64}, name: str) -> i64
    return scores[name].default(0)
examples/gallery/10-maps.hero, righe 7–9

.default(v) restituisce il valore trovato oppure v se la chiave manca. In questo esempio il risultato ha tipo i64.

function known(scores: {str: i64}, name: str) -> bool
    return !scores[name].is_err()
examples/gallery/10-maps.hero, righe 11–12

.is_err() restituisce true se la chiave manca. Non estrae il valore. Per verificare che la chiave sia presente, l’esempio nega il risultato con !.

# `.is_err()` asks without extracting; the `match` extracts without aborting.
function report(scores: {str: i64}, name: str) -> str
    got = scores[name]

    if got.is_err()
        return name + " has no score"
    return name + " scored " + got.must().to_str()
examples/gallery/10-maps.hero, righe 14–20

.must() restituisce il valore trovato e termina il programma se la chiave manca. Qui la riga precedente ha già verificato che la chiave esiste. Usa questa forma quando hai verificato che la ricerca riesce.

# The general way, and the only one that names the error: `match` on `.ok`/`.err`.
function describe(scores: {str: i64}, name: str) -> str
    return match scores[name]
        .ok v  => v.to_str()
        .err e => e.code
examples/gallery/10-maps.hero, righe 22–26

match gestisce entrambi i risultati e permette di leggere anche il contenuto dell’errore. Entrambi i rami sono obbligatori.

Come se ne costruisce una

# A map is built as a literal. There is no insert in the built-in inventory
# (§4.20) — `push` is for arrays — so this table is written, not accumulated.
function table() -> {str: i64}
    return {"ada": 10, "grace": 9, "alan": 8}
examples/gallery/10-maps.hero, righe 28–31

Puoi scrivere tutte le coppie insieme oppure dichiarare una mappa modificabile con @= e aggiungerle una alla volta. m[k] @ v inserisce il valore per la chiave k, sostituendo quello precedente se esiste. Una mappa vuota richiede il tipo esplicito: m: {str: i64} @= {}.

Una mappa non ha un ordine

keys(m) restituisce le chiavi senza un ordine garantito. L’ordine può cambiare tra due esecuzioni. Se ti serve un ordine stabile, usa sort:

    w = make()

    for name in sort(keys(w.rooms))
        room = room_at(w, name).must()

        for direction in sort(keys(room.exits))
            there = room.exits[direction].must()
examples/adventure/world.hero, righe 80–86

Questo test visita le stanze e le uscite in un ordine fisso, così l’output resta uguale a ogni esecuzione. Anche il compilatore di Heroes ha avuto un problema simile: alcune liste seguivano l’ordine delle mappe, rendendo diverse due compilazioni dello stesso programma. La correzione ha aggiunto le chiamate a sort mancanti. Il resoconto è nella sezione sul compilatore scritto in Heroes. Il confronto tra mappe non dipende invece dall’ordine: due mappe con le stesse coppie chiave-valore risultano uguali con ==.

Tutto quanto, in esecuzione

test "a missing key is a type, not a crash"
    scores = table()
    assert score_of(scores, "ada") == 10
    assert score_of(scores, "nobody") == 0
    assert known(scores, "grace")
    assert known(scores, "nobody") == false
    assert scores.len() == 3
examples/gallery/10-maps.hero, righe 41–47
function total_of(scores: {str: i64}, names: [str]) -> i64
    sum: i64 @= 0

    for name in names
        sum @ sum + score_of(scores, name)

    return sum
examples/gallery/10-maps.hero, righe 33–39
function main()
    print(total_of(table(), ["ada", "grace", "nobody"]))
examples/gallery/10-maps.hero, righe 62–63
$ heroes run examples/gallery/10-maps.hero
19

Il risultato è 10 + 9 + 0. Il terzo nome non esiste nella mappa; score_of usa esplicitamente zero come valore predefinito.

Poi: Valori fino in fondo, per costruire alberi con valori indipendenti e senza puntatori. Torna all’elenco dei capitoli. ← Prima: Le stringhe

Esegui un programma tuo: bastano quattro comandi. Oppure prendi tutto il linguaggio in un file solo, e tutti i programmi già scritti.