Nomi e modifiche
Il simbolo @= dichiara una variabile modificabile, e @ indica le operazioni che la cambiano.
Per capire un programma serve sapere quali operazioni modificano i dati. In Heroes queste operazioni si riconoscono dal simbolo @.
= definisce un valore fisso, @= uno modificabile
x = 5 # un nome per un valore, per sempre
v @= 0 # una cella che può cambiare
v @ v + 1 # la si cambia
= associa un nome a un valore che non potrai riassegnare. @= dichiara invece una cella, cioè una variabile modificabile, e @ le assegna un nuovo valore. Più avanti vedrai come @ indica anche i parametri che una funzione può modificare.
Una cella prende il tipo dal suo primo valore, come un nome definito con =, e puoi scriverlo sulla riga quando lo vuoi lì: v: i64 @= 0. Il valore può cambiare, ma il tipo resta quello. Se provi a modificare un nome definito con =, il compilatore segnala l’errore:
$ heroes check mutation.hero
error[not_mutable]: `start` was bound with `=`, which binds once, forever — a cell is declared `start @= <value>`
at mutation.hero:31:5
|
31 | start @ start + 1
| ^^^^^
fix (guess): declare `start` as a cell: `start @= <value>`
La correzione è indicata come guess, cioè una proposta da verificare, anziché certain, cioè certa. Potresti aver voluto dichiarare una cella oppure aver usato il nome sbagliato. Il compilatore può applicare automaticamente solo le correzioni certain.
Ogni nome visibile ha una sola definizione
Non puoi riutilizzare un nome finché la sua dichiarazione precedente è visibile in quel punto del programma:
$ heroes check mutation.hero
error[shadowed_binding]: `start` is already in scope, bound at line 30 — shadowing is an error here: a name means one thing for as long as it is visible
at mutation.hero:31:5
|
31 | start = r.pos + 1
| ^^^^^
Riutilizzare un nome già visibile si chiama shadowing: la nuova dichiarazione nasconde la precedente. Nei linguaggi che lo consentono, puoi così usare il valore sbagliato senza un errore di compilazione. Heroes rifiuta lo shadowing.
Ogni valore è una copia
test "values are copies"
a = [1, 2, 3]
b: [i64] @= a
b @ b.push(4)
assert a.len() == 3
assert b.len() == 4
b è una copia di a: aggiungere un elemento a b non modifica a. La regola vale per array, mappe, stringhe e record, anche quando contengono altri valori. Per evitare copie inutili, il compilatore può condividere la memoria finché non avviene una modifica. Questo dettaglio interno non cambia il comportamento delle copie nel programma.
In b @ b.push(4), push restituisce un nuovo array. @ assegna il risultato a b.
Quando una funzione deve davvero cambiare il suo argomento
Una funzione può anche modificare un argomento, per esempio la posizione di lettura in un testo. In questo caso il parametro deve avere il simbolo @:
# The `@` is the whole signature's news: this one moves.
function advance(@r: Reader)
r.pos @ r.pos + 1
Devi scrivere @ anche nella chiamata, altrimenti il programma non compila:
# Consumes spaces. The caller sees `@r` and knows the reader moved.
function skip_spaces(@r: Reader)
while !at_end(r) && here(r) == ' '
advance(@r)
$ heroes check mutation.hero
error[marker_mismatch]: `advance` changes this argument, so the call site writes `@` too
at mutation.hero:26:17
|
26 | advance(r)
| ^
fix (certain): mark the argument: `@x`
Leggendo advance(@r) sai che la funzione può modificare r. Non serve aprire la definizione di advance per scoprirlo.
Il comportamento è copia dentro, copia fuori: la funzione lavora su una propria copia e, quando termina, sostituisce il valore originale con quello aggiornato. Questo avviene anche con un return anticipato.
# Reads a run of letters and hands it back, leaving the reader after it.
function word_at(@r: Reader) -> str
start = r.pos
while !at_end(r) && here(r) >= 'a' && here(r) <= 'z'
advance(@r)
return r.text.slice(from: start, to: r.pos)
Una funzione può modificare il suo argomento e restituire un valore. Qui word_at avanza la posizione oltre la parola e restituisce la parola letta. La chiamata con il punto, r.word_at(), è vietata quando il primo parametro è @: devi scrivere word_at(@r) per rendere visibile la modifica.
Un nome che nessuno legge è un errore
$ heroes check mutation.hero
error[unused_binding]: `spare` is bound and never read — remove the binding, or read it
at mutation.hero:55:5
|
55 | spare = 5
| ^^^^^
Il compilatore segnala un nome dichiarato ma mai letto: potrebbe essere rimasto dopo una modifica o indicare un risultato dimenticato. Per scartare intenzionalmente un risultato, scrivi _ = f(x). Il simbolo _ non dichiara un nome e può essere riutilizzato.
Il programma
function main()
r: Reader @= Reader(text: "one two", pos: 0)
print(word_at(@r))
$ heroes run examples/gallery/05-mutation.hero
one
Poi: Generici e valori funzione: una forma sola che funziona per tanti tipi, e le funzioni passate in giro come valori. Torna all’elenco dei capitoli. ← Prima: Il flusso di controllo
Esegui un programma tuo: bastano quattro comandi. Oppure prendi tutto il linguaggio in un file solo, e tutti i programmi già scritti.