Quale parte di Hindley-Milner non capisci?


851

Io giuro ha usato per essere una T-shirt in vendita con le parole immortali:


Quale parte di

Hindley-Milner

si fa a non capire?


Nel mio caso, la risposta sarebbe ... tutto!

In particolare, vedo spesso notazioni come queste nei documenti di Haskell, ma non ho idea di cosa significhi. Non ho idea di quale ramo della matematica dovrebbe essere.

Riconosco ovviamente le lettere dell'alfabeto greco e simboli come "∉" (che di solito significa che qualcosa non è un elemento di un insieme).

D'altra parte, non ho mai visto prima "⊢" ( Wikipedia afferma che potrebbe significare "partizione" ). Non ho familiarità con l'uso del vincolo qui. (Di solito, indica una frazione, ma questo non sembra essere il caso qui.)

Se qualcuno potesse almeno dirmi da dove cominciare a cercare di capire cosa significhi questo mare di simboli, sarebbe utile.


8
Se siete alla ricerca di una buona spiegazione dell'algoritmo, il migliore che ho trovato finora è nel capitolo 30 della di Shriram Krishnamurthi Linguaggi di programmazione: applicazione e l'interpretazione (! CC con licenza).
laslowh

2
@laslowh Grazie! Lo sto leggendo. Versione più recente: cs.brown.edu/courses/cs173/2012/book/book.pdf
SnowOnion

Risposte:


652
  • La barra orizzontale indica che "[sopra] implica [sotto]".
  • Se ci sono molteplici espressioni in [sopra], quindi prendere in considerazione le ANDed insieme; tutto quanto [sopra] deve essere vero per garantire il [sotto].
  • :significa ha tipo
  • significa è dentro . (Allo stesso modo significa "non è in".)
  • Γviene solitamente utilizzato per fare riferimento a un ambiente o un contesto; in questo caso può essere pensato come un insieme di annotazioni di tipo, accoppiando un identificatore con il suo tipo. Pertanto x : σ ∈ Γsignifica che l'ambiente Γinclude il fatto che xha tipo σ.
  • può essere letto come dimostra o determina. Γ ⊢ x : σsignifica che l'ambiente Γdetermina che xha tipo σ.
  • ,è un modo per includere ipotesi aggiuntive specifiche in un ambiente Γ.
    Pertanto, Γ, x : τ ⊢ e : τ'significa che l'ambiente Γ, con l'ipotesi aggiuntiva e prevalente che xha tipoτ , dimostra che eha tipo τ'.

Come richiesto: precedenza dell'operatore, dal più alto al più basso:

  • Infissa e mixfix operatori specifiche della lingua, ad esempio λ x . e, ∀ α . σe τ → τ', let x = e0 in e1e spazi per l'applicazione di funzione.
  • :
  • e
  • , (Sinistra-associativa)
  • spazio bianco che separa più proposizioni (associative)
  • la barra orizzontale

19
Quali sono le regole di precedenza degli operatori?
Randomblue,

:e sono molto simili, nel senso che significano che una cosa è contenuta in un'altra cosa - un insieme contiene elementi e un tipo contiene valori, in un certo senso. La differenza cruciale è che x ∈ Ssignifica che un insieme Scontiene letteralmente un elemento x, mentre Γ ⊢ x : Tsignifica che si xpuò dedurre dal tipo di abitazione Tnel contesto Γ. Considerando questo, la regola Var dice: »Se x è letteralmente contenuto nel contesto, può (banalmente) inferire da esso«.
David

@Randomblue ho fatto esplicita la precedenza dei simboli con l'aggiunta di parentesi in tutto il mondo, ad esempio (Γ,(x:τ))⊢(x:σ), vedere overleaf.com/read/ddmnkzjtnqbd#/61990222
SnowOnion

327

Questa sintassi, sebbene possa sembrare complicata, in realtà è abbastanza semplice. L'idea di base viene dalla logica formale: l'intera espressione è un'implicazione con la metà superiore costituita dai presupposti e la metà inferiore il risultato. Cioè, se sai che le espressioni superiori sono vere, puoi concludere che anche le espressioni inferiori sono vere.

simboli

Un'altra cosa da tenere a mente è che alcune lettere hanno significati tradizionali; in particolare, Γ rappresenta il "contesto" in cui ti trovi, ovvero quali sono i tipi di altre cose che hai visto. Quindi qualcosa del genere Γ ⊢ ...significa "l'espressione ...quando conosci i tipi di ogni espressione in Γ.

Il simbolo essenzialmente significa che puoi provare qualcosa. Così Γ ⊢ ...è una frase che dice "Posso dimostrare ...in un contesto Γ. Queste dichiarazioni sono anche chiamate giudizi di tipo.

Un'altra cosa da tenere a mente: in matematica, proprio come ML e Scala, x : σsignifica che xha tipo σ. Puoi leggerlo proprio come quello di Haskell x :: σ.

Cosa significa ogni regola

Quindi, sapendo questo, la prima espressione diventa facile da capire: se sappiamo che x : σ ∈ Γ(cioè xha un qualche tipo σin qualche contesto Γ), allora sappiamo che Γ ⊢ x : σ(cioè Γ, xha un tipoσ ). Quindi davvero, questo non ti sta dicendo nulla di super interessante; ti dice solo come usare il tuo contesto.

Le altre regole sono anche semplici. Ad esempio, prendi [App]. Questa regola ha due condizioni: e₀è una funzione da un tipo τa un certo tipo τ'ed e₁è un valore di tipo τ. Ora si sa che tipo che si ottiene applicando e₀allae₁ ! Spero che questa non sia una sorpresa :).

La prossima regola ha qualche nuova nuova sintassi. In particolare, Γ, x : τsignifica solo il contesto costituito Γe il giudizio x : τ. Quindi, se sappiamo che la variabile xha un tipo di τe che l'espressione eha un tipo τ', conosciamo anche il tipo di una funzione che accetta xe restituisce e. Questo ci dice solo cosa fare se abbiamo capito quale tipo prende una funzione e quale tipo restituisce, quindi non dovrebbe essere sorprendente.

Il prossimo ti spiega come gestire le letdichiarazioni. Se sai che alcune espressioni e₁hanno un tipo τlungo quanto xun tipo σ, allora letun'espressione che si lega localmente xa un valore di tipo σavrà e₁un tipo τ. In realtà, questo ti dice semplicemente che un'istruzione let ti consente essenzialmente di espandere il contesto con una nuova associazione, che è esattamente cosalet fa!

La [Inst]regola si occupa della digitazione secondaria. Si dice che se si dispone di un valore di tipo σ'ed è un sottotipo di σ( rappresenta una relazione ordinamento parziale) allora che l'espressione è anche di tipo σ.

L'ultima regola riguarda i tipi generalizzanti. A parte: una variabile libera è una variabile che non viene introdotta da un'istruzione let o lambda all'interno di un'espressione; questa espressione ora dipende dal valore della variabile libera dal suo contesto. La regola dice che se c'è qualche variabile αche non è "libera" in qualsiasi cosa nel tuo contesto, allora è sicuro dire che qualsiasi espressione di cui conosci il tipo e : σavrà quel tipo per qualsiasi valore di α.

Come usare le regole

Quindi, ora che hai capito i simboli, cosa fai con queste regole? Bene, puoi usare queste regole per capire il tipo di vari valori. Per fare questo, guarda la tua espressione (diciamo f x y) e trova una regola che ha una conclusione (la parte inferiore) che corrisponde alla tua affermazione. Chiamiamo la cosa che stai cercando di trovare il tuo "obiettivo". In questo caso, guarderesti la regola che finisce in e₀ e₁. Quando hai trovato questo, ora devi trovare le regole che provano tutto al di sopra della linea di questa regola. Queste cose generalmente corrispondono ai tipi di sottoespressioni, quindi essenzialmente si ricorre su parti dell'espressione. Lo fai solo fino a quando non finisci il tuo albero delle prove, il che ti dà una prova del tipo della tua espressione.

Quindi tutte queste regole fanno è specificare esattamente - e nei soliti dettagli matematicamente pedanti: P - come capire i tipi di espressioni.

Ora, questo dovrebbe sembrare familiare se hai mai usato Prolog: essenzialmente stai calcolando l'albero di prova come un interprete Prolog umano. C'è una ragione per cui Prolog si chiama "programmazione logica"! Questo è anche importante poiché il primo modo in cui sono stato introdotto l'algoritmo di inferenza HM è stato implementandolo in Prolog. Questo è in realtà sorprendentemente semplice e rende chiaro ciò che sta accadendo. Dovresti sicuramente provarlo.

Nota: probabilmente ho fatto degli errori in questa spiegazione e mi piacerebbe che qualcuno li segnalasse. In realtà lo tratterò in classe tra un paio di settimane, quindi sarò più sicuro di allora: P.


5
\ alpha è una variabile di tipo non libero, non una normale variabile. Quindi, per spiegare la regola della generalizzazione, si deve spiegare molto di più.
nponeccop,

2
@nponeccop: Hmm, buon punto. Non ho mai visto quella regola particolare prima. Potresti aiutarmi a spiegarlo correttamente?
Tikhon Jelvis,

8
@TikhonJelvis: In realtà è piuttosto semplice, ti permette di generalizzare (supponendo Γ = {x : τ}) λy.x : σ → τa ∀ σ. σ → τ, ma non a ∀ τ. σ → τ, perché τè una variabile libera in Γ. L'articolo di Wikipedia su HM lo spiega abbastanza bene.
Vitus,

7
Credo che la parte della risposta relativa [Inst]sia un po 'imprecisa. Finora questa è solo la mia comprensione, ma i sigmi nelle regole [Inst]e [Gen]non si riferiscono a tipi, ma a schemi di tipi . Quindi l' operatore è un ordinamento parziale non correlato alla sotto-tipizzazione come lo conosciamo dalle lingue OO. È legato a valori polimorfici come id = λx. x. La sintassi completa per tale funzione sarebbe id = ∀x. λx. x. Ora, ovviamente, possiamo avere un id2 = ∀xy. λx. x, dove ynon viene utilizzato. Quindi id2 ⊑ id, questo è ciò che [Inst]dice la regola.
Ionuț G. Stan,

71

se qualcuno potesse almeno dirmi da dove cominciare cercando di capire cosa significhi questo mare di simboli

Vedi " Fondamenti pratici dei linguaggi di programmazione ", capitoli 2 e 3, sullo stile della logica attraverso giudizi e derivazioni. L'intero libro è ora disponibile su Amazon.

capitolo 2

Definizioni induttive

Le definizioni induttive sono uno strumento indispensabile nello studio dei linguaggi di programmazione. In questo capitolo svilupperemo il quadro di base delle definizioni induttive e forniremo alcuni esempi del loro uso. Una definizione induttiva consiste in un insieme di regole per derivare giudizi , o affermazioni , di una varietà di forme. I giudizi sono dichiarazioni su uno o più oggetti sintattici di un tipo specificato. Le regole specificano le condizioni necessarie e sufficienti per la validità di una sentenza, e quindi determinano appieno il suo significato.

2.1 Sentenze

Iniziamo con la nozione di un giudizio o affermazione su un oggetto sintattico. Faremo uso di molte forme di giudizio, inclusi esempi come questi:

  • n nat - n è un numero naturale
  • n = n1 + n2 - n è la somma di n1 e n2
  • τ type - τ è un tipo
  • e : τ - espressione e ha tipo τ
  • ev - espressione e ha valore v

Un giudizio afferma che uno o più oggetti sintattici hanno una proprietà o sono in relazione l'uno con l'altro. La proprietà o relazione stessa è chiamata forma di giudizio e il giudizio che un oggetto o quegli oggetti hanno quella proprietà o posizione in quella relazione si dice che sia un'istanza di quella forma di giudizio. Una forma di giudizio è anche chiamata predicato e gli oggetti che costituiscono un'istanza ne sono i soggetti . Scriviamo una J per il giudizio che afferma che J detiene una . Quando non è importante sottolineare l'argomento del giudizio (il testo si interrompe qui)


53

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

Diagramma logico VAR

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

Schema logico APP

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

Diagramma logico ABS

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

Lasciare diagramma logico

Dato 𝚪 asserzioni 𝑒₀, di tipo 𝜎, e 𝚪 e 𝑥, di tipo 𝜎, asserzioni 𝑒₁ di tipo 𝜏
concludono 𝚪 letasserzioni 𝑥 = 𝑒₀ 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

Diagramma logico INST

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

Diagramma logico GEN

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.


1
un buon riassunto Aaron!
bhurlow,

48

La notazione deriva dalla deduzione naturale .

⊢ il simbolo si chiama tornello .

Le 6 regole sono molto semplici.

Var regola è piuttosto banale - dice che se il tipo per identificatore è già presente nel tuo ambiente di tipo, quindi per inferire il tipo lo prendi dall'ambiente così com'è.

Appla regola dice che se hai due identificatori e0e e1puoi inferirne i tipi, puoi inferire il tipo di applicazione e0 e1. La regola recita in questo modo se lo sai e0 :: t0 -> t1e e1 :: t0(lo stesso t0!), Allora l'applicazione è ben scritta e il tipo èt1 .

Abse Letsono regole per inferire i tipi di lambda-astrazione e let-in.

Inst la regola dice che è possibile sostituire un tipo con uno meno generale.


4
Questo è un calcolo sequenziale, non una deduzione naturale.
Roman Cheplyaka,

12
@RomanCheplyaka bene, la notazione è più o meno la stessa. L'articolo di Wikipedia contiene un interessante confronto tra le due tecniche: en.wikipedia.org/wiki/Natural_deduction#Sequent_calculus . Il calcolo sequenziale è nato in risposta diretta ai fallimenti della deduzione naturale, quindi se la domanda è "da dove proviene questa notazione", la "deduzione naturale" è tecnicamente una risposta più corretta.
Dan Burton,

2
@RomanCheplyaka Un'altra considerazione è che il calcolo sequenziale è puramente sintattico (ecco perché ci sono così tante regole strutturali) mentre questa notazione non lo è. La prima regola presuppone che il contesto sia un insieme mentre nel calcolo sequenziale è un costrutto sintattico più semplice.
nponeccop,

@Cheplyaka in realtà, no, ha qualcosa che sembra un "sequente" ma non è un calcolo sequenziale. Haper sviluppa una comprensione di questo nel suo libro di testo come un "giudizio di ordine superiore". Questa è davvero una deduzione naturale.
Philip JF,

15

Esistono due modi di pensare a e: σ. Uno è "l'espressione e ha tipo σ", un altro è "la coppia ordinata dell'espressione e e il tipo σ".

Visualizza Γ come conoscenza dei tipi di espressioni, implementata come un insieme di coppie di espressioni e tipo, e: σ.

Il tornello ⊢ significa che dalla conoscenza a sinistra, possiamo dedurre ciò che è a destra.

La prima regola [Var] può quindi essere letta:
se la nostra conoscenza Γ contiene la coppia e: σ, allora possiamo dedurre da Γ che e ha tipo σ.

La seconda regola [App] può essere letta:
se da Γ possiamo dedurre che e_0 ha il tipo τ → τ ', e da Γ possiamo dedurre che e_1 ha il tipo τ, allora da Γ possiamo dedurre che e_0 e_1 ha il digitare τ '.

È comune scrivere Γ, e: σ invece di Γ ∪ {e: σ}.

La terza regola [Abs] può quindi essere letta:
se da Γ esteso con x: τ possiamo dedurre che e ha il tipo τ ', allora da Γ possiamo dedurre che λx.e ha il tipo τ → τ'.

La quarta regola [Let] è lasciata come esercizio. :-)

La quinta regola [Inst] può essere letta:
se da Γ possiamo dedurre che e ha il tipo σ ', e σ' è un sottotipo di σ, allora da Γ possiamo dedurre che e ha il tipo σ.

La sesta e ultima regola [Gen] può essere letta:
se da Γ possiamo dedurre che e ha il tipo σ e α non è una variabile di tipo libera in nessuno dei tipi in Γ, allora da Γ possiamo dedurre che e ha tipo ∀α σ.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.