Teoria dei tipi dipendenti e funzioni dei tipi "arbitrarie"
La mia prima risposta a questa domanda era alta sui concetti e scarsa sui dettagli e riflessa sulla domanda secondaria: "che cosa sta succedendo?"; questa risposta sarà la stessa ma focalizzata sulla domanda secondaria: "possiamo ottenere funzioni di tipo arbitrario?".
Un'estensione alle operazioni algebriche di somma e prodotto sono i cosiddetti "grandi operatori", che rappresentano la somma e il prodotto di una sequenza (o più in generale, la somma e il prodotto di una funzione su un dominio) normalmente scritti Σe Πrispettivamente. Vedi la Sigma Notation .
Quindi la somma
a₀ + a₁X + a₂X² + ...
potrebbe essere scritto
Σ[i ∈ ℕ]aᵢXⁱ
dove ac'è una sequenza di numeri reali, per esempio. Il prodotto verrebbe rappresentato in modo simile con Πinvece di Σ.
Quando guardi da lontano, questo tipo di espressione assomiglia molto a una funzione 'arbitraria' X; siamo ovviamente limitati a serie espressibili e alle loro funzioni analitiche associate. È questo un candidato per una rappresentazione in una teoria dei tipi? Decisamente!
La classe delle teorie dei tipi che hanno rappresentazioni immediate di queste espressioni è la classe delle teorie dei tipi "dipendenti": teorie con tipi dipendenti. Naturalmente abbiamo termini che dipendono da termini e in lingue come Haskell con funzioni e quantificazione dei tipi, termini e tipi a seconda dei tipi. In un'impostazione dipendente, abbiamo anche tipi a seconda dei termini. Haskell non è un linguaggio tipicamente dipendente, sebbene molte funzioni di tipi dipendenti possano essere simulate torturando un po 'il linguaggio .
Curry-Howard e tipi dipendenti
L'isomorfismo di Curry-Howard iniziò la vita come un'osservazione che i termini e le regole di giudizio di tipo del calcolo lambda semplicemente tipizzato corrispondono esattamente alla deduzione naturale (come formulata da Gentzen) applicata alla logica proposizionale intuizionistica, con i tipi che prendono il posto delle proposizioni e termini che sostituiscono le prove, nonostante i due siano stati inventati / scoperti indipendentemente. Da allora, è stata un'enorme fonte di ispirazione per i teorici dei tipi. Una delle cose più ovvie da considerare è se e come questa corrispondenza per la logica proposizionale possa essere estesa a predicazioni o logiche di ordine superiore. Le teorie del tipo dipendente inizialmente sorsero da questa via di esplorazione.
Per un'introduzione all'isomorfismo di Curry-Howard per il calcolo lambda tipicamente semplice, vedere qui . Ad esempio, se vogliamo dimostrare A ∧ Bdobbiamo dimostrare Ae dimostrare B; una dimostrazione combinata è semplicemente una coppia di dimostrazioni: una per ciascun congiunto.
In deduzione naturale:
Γ ⊢ A Γ ⊢ B
Γ ⊢ A ∧ B
e nel calcolo lambda semplicemente digitato:
Γ ⊢ a : A Γ ⊢ b : B
Γ ⊢ (a, b) : A × B
Corrispondenze simili esistono per ∨tipi e somma, →tipi di funzione e varie regole di eliminazione.
Una proposizione non dimostrabile (intuizionisticamente falsa) corrisponde a un tipo disabitato.
Tenendo presente l'analogia dei tipi come proposizioni logiche, possiamo iniziare a considerare come modellare i predicati nel mondo dei tipi. Ci sono molti modi in cui questo è stato formalizzato (vedi questa introduzione alla teoria del tipo intuitivo di Martin-Löf per uno standard ampiamente usato) ma l'approccio astratto di solito osserva che un predicato è come una proposizione con variabili a termine libero, o, in alternativa, una funzione che prende in considerazione le proposizioni. Se permettiamo alle espressioni di tipo di contenere termini, allora un trattamento nello stile del calcolo lambda si presenta immediatamente come una possibilità!
Considerando solo prove costruttive, di cosa costituisce una prova ∀x ∈ X.P(x)? Possiamo considerarlo come una funzione di prova, prendendo termini ( x) come prove delle loro proposizioni corrispondenti ( P(x)). Così i membri (prove) del tipo (proposizione) ∀x : X.P(x)sono 'funzioni dipendenti', che per ogni xa Xdanno un termine di tipo P(x).
Che dire ∃x ∈ X.P(x)? Abbiamo bisogno di ogni membro del X, xinsieme a una prova P(x). Quindi i membri (prove) del tipo (proposizione) ∃x : X.P(x)sono "coppie dipendenti": un termine distinto xin X, insieme a un termine di tipo P(x).
Notazione: userò
∀x ∈ X...
per dichiarazioni effettive sui membri della classe Xe
∀x : X...
per espressioni di tipo corrispondenti alla quantificazione universale sul tipo X. Allo stesso modo per ∃.
Considerazioni combinatorie: prodotti e somme
Oltre alla corrispondenza di tipi Curry-Howard con proposizioni, abbiamo la corrispondenza combinatoria di tipi algebrici con numeri e funzioni, che è il punto principale di questa domanda. Fortunatamente, questo può essere esteso ai tipi dipendenti descritti sopra!
Userò la notazione del modulo
|A|
rappresentare la "dimensione" di un tipo A, rendere esplicita la corrispondenza delineata nella domanda, tra tipi e numeri. Si noti che questo è un concetto al di fuori della teoria; Non pretendo che ci sia bisogno di tale operatore all'interno della lingua.
Contiamo i possibili membri (completamente ridotti, canonici) di tipo
∀x : X.P(x)
che è il tipo di funzioni dipendenti che portano termini xdi tipo Xa termini di tipo P(x). Ciascuna di tali funzioni deve avere un output per ogni termine di Xe questo output deve essere di un tipo particolare. Per ciascuno xdi essi X, quindi, ciò fornisce |P(x)|"scelte" di output.
La battuta finale è
|∀x : X.P(x)| = Π[x : X]|P(x)|
che ovviamente non ha molto senso se lo Xè IO (), ma è applicabile ai tipi algebrici.
Allo stesso modo, un termine di tipo
∃x : X.P(x)
è il tipo di coppie (x, p)con p : P(x), quindi dato qualsiasi xin Xpossiamo costruire una coppia appropriata con qualsiasi membro di P(x), dando |P(x)|"scelte".
Quindi,
|∃x : X.P(x)| = Σ[x : X]|P(x)|
con gli stessi avvertimenti.
Ciò giustifica la notazione comune per i tipi dipendenti nelle teorie che usano i simboli Πe Σ, e in effetti molte teorie confondono la distinzione tra "per tutti" e "prodotto" e tra "c'è" e "somma", a causa delle corrispondenze sopra menzionate.
Ci stiamo avvicinando!
Vettori: rappresentano le tuple dipendenti
Possiamo ora codificare espressioni numeriche come
Σ[n ∈ ℕ]Xⁿ
come espressioni di tipo?
Non proprio. Mentre possiamo considerare in modo informale il significato di espressioni come Xⁿin Haskell, dove Xc'è un tipo e nun numero naturale, è un abuso della notazione; questa è un'espressione di tipo contenente un numero: chiaramente non un'espressione valida.
D'altra parte, con i tipi dipendenti nell'immagine, i tipi contenenti numeri è proprio il punto; in effetti, le tuple dipendenti o "vettori" sono un esempio molto comunemente citato di come i tipi dipendenti possono fornire una sicurezza pragmatica a livello di tipo per operazioni come l'accesso all'elenco . Un vettore è solo un elenco con le informazioni a livello di tipo relative alla sua lunghezza: esattamente ciò che stiamo cercando per espressioni di tipo come Xⁿ.
Per la durata di questa risposta, lascia
Vec X n
essere il tipo di lunghezza- nvettori di Xvalori di tipo.
Tecnicamente nqui c'è, piuttosto che un numero naturale reale , una rappresentazione nel sistema di un numero naturale. Possiamo rappresentare i numeri naturali ( Nat) in stile Peano come zero ( 0) o il successore ( S) di un altro numero naturale, e per n ∈ ℕscrivere scrivo ˻n˼per indicare il termine in Natcui rappresenta n. Ad esempio, lo ˻3˼è S (S (S 0)).
Poi abbiamo
|Vec X ˻n˼| = |X|ⁿ
per qualsiasi n ∈ ℕ.
Tipi nativi: promozione di ℕ termini per i tipi
Ora possiamo codificare espressioni come
Σ[n ∈ ℕ]Xⁿ
come tipi. Questa particolare espressione darebbe origine a un tipo che è ovviamente isomorfo al tipo di elenchi di X, come identificato nella domanda. (Non solo, ma da un punto di vista teorico di categoria, la funzione di tipo - che è un funzione - che porta Xal tipo sopra è naturalmente isomorfa per il funzione di lista).
Un ultimo pezzo del puzzle per funzioni 'arbitrarie' è come codificare, per
f : ℕ → ℕ
espressioni come
Σ[n ∈ ℕ]f(n)Xⁿ
in modo che possiamo applicare coefficienti arbitrari a una serie di potenze.
Comprendiamo già la corrispondenza dei tipi algebrici con i numeri, permettendoci di mappare dai tipi ai numeri e digitare le funzioni alle funzioni numeriche. Possiamo anche andare dall'altra parte! - prendendo un numero naturale, c'è ovviamente un tipo algebrico definibile con quel numero di membri a termine, indipendentemente dal fatto che abbiamo o meno tipi dipendenti. Possiamo facilmente dimostrarlo al di fuori della teoria dei tipi per induzione. Ciò di cui abbiamo bisogno è un modo per mappare da numeri naturali a tipi, all'interno del sistema.
Una piacevole realizzazione è che, una volta che abbiamo tipi dipendenti, la prova per induzione e la costruzione per ricorsione diventano intimamente simili - in effetti sono la stessa cosa in molte teorie. Dal momento che possiamo dimostrare per induzione che esistono tipi che soddisfano i nostri bisogni, non dovremmo essere in grado di costruirli?
Esistono diversi modi per rappresentare i tipi a livello di termine. Userò qui una notazione immaginaria Haskellish con *per l'universo dei tipi, di per sé considerato un tipo in un ambiente dipendente. 1
Allo stesso modo, ci sono anche almeno tanti modi per ℕannotare '-eliminazione' quante sono le teorie del tipo dipendente. Userò una notazione di corrispondenza del modello Haskellish.
Abbiamo bisogno di una mappatura, αda Nata *, con la proprietà
∀n ∈ ℕ.|α ˻n˼| = n.
È sufficiente la seguente pseudodefinizione.
data Zero -- empty type
data Successor a = Z | Suc a -- Successor ≅ Maybe
α : Nat -> *
α 0 = Zero
α (S n) = Successor (α n)
Quindi vediamo che l'azione di αrispecchia il comportamento del successore S, rendendolo una specie di omomorfismo. Successorè una funzione di tipo che "aggiunge uno" al numero di membri di un tipo; vale a dire, |Successor a| = 1 + |a|per chiunque abbia auna dimensione definita.
Ad esempio α ˻4˼(che è α (S (S (S (S 0))))), è
Successor (Successor (Successor (Successor Zero)))
e i termini di questo tipo sono
Z
Suc Z
Suc (Suc Z)
Suc (Suc (Suc Z))
dandoci esattamente quattro elementi: |α ˻4˼| = 4.
Allo stesso modo, per chiunque n ∈ ℕ, abbiamo
|α ˻n˼| = n
come richiesto.
- Molte teorie richiedono che i membri di
*siano meri rappresentanti di tipi e un'operazione viene fornita come una mappatura esplicita dai termini di tipo *ai loro tipi associati. Altre teorie consentono agli stessi tipi letterali di essere entità a livello di termine.
Funzioni "arbitrarie"?
Ora abbiamo l'apparato per esprimere una serie di potenze completamente generali come un tipo!
La serie
Σ[n ∈ ℕ]f(n)Xⁿ
diventa il tipo
∃n : Nat.α (˻f˼ n) × (Vec X n)
dov'è ˻f˼ : Nat → Natuna rappresentazione adatta all'interno del linguaggio della funzione f. Possiamo vedere questo come segue.
|∃n : Nat.α (˻f˼ n) × (Vec X n)|
= Σ[n : Nat]|α (˻f˼ n) × (Vec X n)| (property of ∃ types)
= Σ[n ∈ ℕ]|α (˻f˼ ˻n˼) × (Vec X ˻n˼)| (switching Nat for ℕ)
= Σ[n ∈ ℕ]|α ˻f(n)˼ × (Vec X ˻n˼)| (applying ˻f˼ to ˻n˼)
= Σ[n ∈ ℕ]|α ˻f(n)˼||Vec X ˻n˼| (splitting product)
= Σ[n ∈ ℕ]f(n)|X|ⁿ (properties of α and Vec)
Quanto è 'arbitrario' questo? Con questo metodo non siamo limitati solo ai coefficienti interi, ma ai numeri naturali. A parte questo, fpuò essere qualsiasi cosa, dato un linguaggio Turing completo con tipi dipendenti, possiamo rappresentare qualsiasi funzione analitica con coefficienti numerici naturali.
Non ho studiato l'interazione di questo con, ad esempio, il caso fornito nella domanda List X ≅ 1/(1 - X)o quale possibile senso potrebbero avere "tipi" negativi e non interi in questo contesto.
Speriamo che questa risposta spinga in qualche modo a esplorare fino a che punto possiamo andare con funzioni di tipo arbitrario.