Heroes / Guida / capitolo 3

Record e funzioni

Raggruppare i dati in record e chiamare le funzioni con argomenti espliciti.

Un record raggruppa campi con nomi e tipi. Quando lo passi a una funzione, la funzione riceve una copia indipendente, come accade con un numero.

Come si dichiara

# A position on the integer grid.
record Point
    x: i64
    y: i64
examples/gallery/01-points.hero, righe 6–9

Ogni campo dichiara un nome e un tipo su una riga indentata. Può contenere anche un altro record: Rect contiene due valori di tipo punto:

# A rectangle, by two corners.
record Rect
    corner: Point
    extent: Point
examples/gallery/01-points.hero, righe 11–14

Come si costruisce: ogni campo ha il suo nome

C’è un modo solo di fare un record, e nomina ogni campo:

function main()
    a = Point(x: 0, y: 0)
    b = Point(x: 3, y: 4)
    print("distance squared: ", a.dist2(b: b))
    print("midpoint x: ", a.midpoint(b: b).x)
examples/gallery/01-points.hero, righe 45–49

Point(3, 4) non è una forma valida: mancano i nomi dei campi. Il compilatore segnala l’errore e suggerisce la correzione:

$ heroes check points.hero
error[missing_label]: `Point`'s fields are always named — this one is `x`
  at points.hero:46:15
     |
  46 |     a = Point(0, 0)
     |               ^
  fix (certain): write `x: `

Con i nomi dei campi, Point(y: 4, x: 3) e Point(x: 3, y: 4) indicano lo stesso punto. L’ordine degli argomenti non cambia quale valore viene assegnato a ciascun campo.

Le funzioni, e dove i nomi diventano obbligatori

function dist2(a: Point, b: Point) -> i64
    dx = a.x - b.x
    dy = a.y - b.y
    return dx * dx + dy * dy
examples/gallery/01-points.hero, righe 21–24

La dichiarazione di una funzione specifica il tipo di ogni parametro e, dopo ->, il tipo del risultato. Nel corpo, dx = … associa un nome a un valore e il compilatore ne deduce il tipo. Per conoscere i tipi in ingresso e in uscita basta quindi leggere la dichiarazione.

Quando due parametri della stessa funzione hanno lo stesso tipo, la chiamata deve nominarli. La regola si applica a dist2, che riceve due Point:

$ heroes check points.hero
error[needs_label]: two of `dist2`'s parameters are `Point`, so every one of them is named at the call site — this one is `b`
  at points.hero:48:41
     |
  48 |     print("distance squared: ", a.dist2(b))
     |                                         ^
  fix (certain): write `b: `

Il tipo da solo non distingue due argomenti dello stesso tipo. I nomi rendono esplicito il ruolo di ciascuno. Se i tipi sono diversi, il compilatore può distinguerli e le etichette sono facoltative.

Chiamate con il punto

x.f(y) equivale a f(x, y). La sintassi con il punto permette di scrivere una normale chiamata di funzione. Non definisce un metodo, cioè una funzione associata a un tipo; il compilatore stabilisce quale funzione chiamare prima dell’esecuzione.

# Reads as a sentence, and it is one function call: `translate(r, by)`.
function translate(r: Rect, by: Point) -> Rect
    moved = Point(x: r.corner.x + by.x, y: r.corner.y + by.y)
    return Rect(corner: moved, extent: r.extent)
examples/gallery/01-points.hero, righe 33–36

unit.translate(p).area() e area(translate(unit, p)) eseguono le stesse chiamate. Puoi scegliere la forma più leggibile; quella con il punto permette di leggere le operazioni da sinistra a destra.

I test stanno accanto a quello che provano

test "the 3-4-5 triangle"
    assert dist2(a: Point(x: 0, y: 0), b: Point(x: 3, y: 4)) == 25

test "a rectangle keeps its size when it moves"
    unit = Rect(corner: Point(x: 0, y: 0), extent: Point(x: 2, y: 3))
    assert unit.translate(Point(x: 10, y: 10)).area() == unit.area()
examples/gallery/01-points.hero, righe 38–43

Un blocco test si dichiara fuori dalle funzioni, accanto al codice da verificare. La compilazione ordinaria lo ignora. Qui il secondo test verifica che spostare un rettangolo non ne cambi l’area.

$ heroes test examples/gallery/01-points.hero
ok   "the 3-4-5 triangle"
ok   "a rectangle keeps its size when it moves"
2 tests, all passed

I limiti delle funzioni

Heroes non prevede ereditarietà, interfacce, overloading, cioè più funzioni con lo stesso nome, né valori predefiniti per i parametri. Non puoi definire funzioni con un numero variabile di argomenti. Questi limiti riducono le regole necessarie per capire una chiamata.

Poi: Variant e match, dove un valore può avere più forme e dimenticarne una ferma la compilazione. Torna all’elenco dei capitoli. ← Prima: Errori

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