Come capisco le regole di Hindley-Milner?
Hindley-Milner è un insieme di regole sotto forma di calcolo sequenziale (non deduzione naturale) che dimostra che possiamo dedurre il tipo (più generale) di un programma dalla costruzione del programma senza dichiarazioni esplicite sul tipo.
I simboli e la notazione
Innanzitutto, spieghiamo i simboli e discutiamo della precedenza dell'operatore
- 𝑥 è un identificatore (informalmente, un nome variabile).
- : significa è un tipo di (informalmente, un'istanza o "is-a").
- σ (sigma) è un'espressione che è una variabile o una funzione.
- quindi 𝑥: 𝜎 viene letto " 𝑥 is-a 𝜎 "
- ∈ significa "è un elemento di"
- 𝚪 (Gamma) è un ambiente.
- ⊦ (il segno asserzione) mezzi asserisce (o si rivela, ma contestualmente "asserisce" legge meglio.)
- 𝚪 ⊦ 𝑥 : 𝜎 viene quindi letto "𝚪 afferma che 𝑥, is-a 𝜎 "
- 𝑒 è un'istanza (elemento) reale di tipo 𝜎 .
- 𝜏 (tau) è un tipo: base, variabile ( 𝛼 ), funzionale 𝜏 → 𝜏 ' o prodotto 𝜏 × 𝜏' (il prodotto non viene utilizzato qui)
- 𝜏 → 𝜏 ' è un tipo funzionale in cui 𝜏 e 𝜏' sono tipi potenzialmente diversi.
𝜆𝑥.𝑒 significa 𝜆 (lambda) è una funzione anonima che accetta un argomento, 𝑥 e restituisce un'espressione, 𝑒 .
let 𝑥 = 𝑒₀ in 𝑒₁ significa in espressione, 𝑒₁ , sostituire 𝑒₀ ovunque appare 𝑥 .
⊑ significa che l'elemento precedente è un sottotipo (informalmente - sottoclasse) di quest'ultimo elemento.
- 𝛼 è una variabile di tipo.
- ∀ 𝛼.𝜎 è un tipo, ∀ (per tutti) argomenti variabili, 𝛼 , che restituisce 𝜎 espressione
- ∉ libero (𝚪) non significa un elemento delle variabili di tipo libero di 𝚪 definite nel contesto esterno. (Le variabili associate sono sostituibili.)
Tutto sopra la linea è la premessa, tutto sotto è la conclusione ( Per Martin-Löf )
Precedenza, per esempio
Ho preso alcuni degli esempi più complessi dalle regole e ho inserito parentesi ridondanti che mostrano la precedenza:
- 𝑥: 𝜎 ∈ 𝚪 potrebbe essere scritto (𝑥: 𝜎) ∈ 𝚪
𝚪 ⊦ 𝑥 : 𝜎 potrebbe essere scritto 𝚪 ⊦ ( 𝑥 : 𝜎 )
𝚪 ⊦ let 𝑥 = 𝑒₀ in 𝑒₁ : 𝜏
equivale a 𝚪 ⊦ (( let ( 𝑥 = 𝑒₀ ) in 𝑒₁ ): 𝜏 )
𝚪 ⊦ 𝜆𝑥.𝑒 : 𝜏 → 𝜏 ' equivale a 𝚪 ⊦ (( 𝜆𝑥.𝑒 ): ( 𝜏 → 𝜏' ))
Quindi, ampi spazi che separano le affermazioni di asserzione e altre precondizioni indicano un insieme di tali precondizioni, e infine la linea orizzontale che separa la premessa dalla conclusione porta alla fine dell'ordine di precedenza.
Le regole
Ciò che segue qui sono le interpretazioni inglesi delle regole, ciascuna seguita da una riaffermazione libera e una spiegazione.
Variabile
Dato che 𝑥 è un tipo di 𝜎 (sigma), un elemento di 𝚪 (Gamma),
concludere 𝚪 afferma che 𝑥 è un 𝜎.
Detto in altro modo, in 𝚪, sappiamo che 𝑥 è di tipo 𝜎 perché 𝑥 è di tipo 𝜎 in 𝚪.
Questa è fondamentalmente una tautologia. Un nome identificativo è una variabile o una funzione.
Applicazione funzionale
Dato 𝚪 asserzioni 𝑒₀ è un tipo funzionale e 𝚪 asserisce 𝑒₁ è una 𝜏
conclusione 𝚪 asserisce che applicare la funzione 𝑒₀ a 𝑒₁ è un tipo 𝜏 '
Per riaffermare la regola, sappiamo che l'applicazione della funzione restituisce il tipo 𝜏 'perché la funzione ha il tipo 𝜏 → 𝜏' e ottiene un argomento di tipo 𝜏.
Ciò significa che se sappiamo che una funzione restituisce un tipo e la applichiamo a un argomento, il risultato sarà un'istanza del tipo che sappiamo restituisce.
Funzione Astrazione
Dato 𝚪 e 𝑥 di tipo 𝜏 asserisce 𝑒 è un tipo, 𝜏 '
conclude 𝚪 asserisce una funzione anonima, 𝜆 di 𝑥 espressione di ritorno, 𝑒 è di tipo 𝜏 → 𝜏'.
Ancora una volta, quando vediamo una funzione che accetta 𝑥 e restituisce un'espressione 𝑒, sappiamo che è di tipo 𝜏 → 𝜏 'perché 𝑥 (a 𝜏) afferma che 𝑒 è un 𝜏'.
Se sappiamo che 𝑥 è di tipo 𝜏 e quindi un'espressione 𝑒 è di tipo 𝜏 ', allora una funzione di 𝑥 espressione di ritorno 𝑒 è di tipo 𝜏 → 𝜏'.
Lascia la dichiarazione variabile
Dato 𝚪 asserzioni 𝑒₀, di tipo 𝜎, e 𝚪 e 𝑥, di tipo 𝜎, asserzioni 𝑒₁ di tipo 𝜏
concludono 𝚪 let
asserzioni 𝑥 = 𝑒₀ in
𝑒₁ di tipo 𝜏
Liberamente, 𝑥 è legato a 𝑒₀ in 𝑒₁ (a 𝜏) perché 𝑒₀ è un 𝜎 e 𝑥 è un 𝜎 che afferma che 𝑒₁ è a.
Ciò significa che se abbiamo un'espressione 𝑒₀ che è un 𝜎 (essendo una variabile o una funzione) e un nome, 𝑥, anche un 𝜎 e un'espressione 𝑒₁ di tipo 𝜏, allora possiamo sostituire 𝑒₀ con 𝑥 ovunque appaia all'interno di 𝑒₁.
la creazione di istanze
Dato 𝚪 asserzioni 𝑒 di tipo 𝜎 'e 𝜎' è un sottotipo di 𝜎
concludere 𝚪 asserzioni 𝑒 è di tipo 𝜎
Un'espressione, 𝑒 è di tipo genitore 𝜎 perché l'espressione 𝑒 è il sottotipo 𝜎 'e 𝜎 è il tipo genitore di 𝜎'.
Se un'istanza è di un tipo che è un sottotipo di un altro tipo, allora è anche un'istanza di quel super-tipo - il tipo più generale.
Generalizzazione
Dato 𝚪 asserzioni 𝑒 è 𝜎 e 𝛼 non è un elemento delle variabili libere di 𝚪,
concludere 𝚪 asserzioni 𝑒, digitare per tutte le espressioni degli argomenti 𝛼 restituendo un'espressione 𝜎
Quindi, in generale, 𝑒 viene digitato 𝜎 per tutte le variabili argomento (𝛼) che restituiscono 𝜎, perché sappiamo che 𝑒 è 𝜎 e 𝛼 non è una variabile libera.
Ciò significa che possiamo generalizzare un programma per accettare tutti i tipi per argomenti non già associati nell'ambito di contenimento (variabili che non sono locali). Queste variabili associate sono sostituibili.
Mettere tutto insieme
Dati alcuni presupposti (come nessuna variabile libera / non definita, un ambiente noto) conosciamo i tipi di:
- elementi atomici dei nostri programmi (variabile),
- valori restituiti dalle funzioni (Funzione Application),
- costrutti funzionali (Funzione Astrazione),
- let bindings (Let Variable Declarations),
- tipi di istanze padre (istanza) e
- tutte le espressioni (generalizzazione).
Conclusione
Queste regole combinate ci consentono di dimostrare il tipo più generale di un programma affermato, senza richiedere annotazioni di tipo.