Abusare dell'algebra dei tipi di dati algebrici: perché funziona?


289

L'espressione "algebrica" ​​per i tipi di dati algebrici sembra molto suggestiva per qualcuno con un background in matematica. Vorrei provare a spiegare cosa intendo.

Dopo aver definito i tipi di base

  • Prodotto
  • Unione +
  • Singleton X
  • Unità 1

e usando la scorciatoia per X•Xe 2Xper X+Xeccetera, possiamo quindi definire espressioni algebriche per esempio elenchi collegati

data List a = Nil | Cons a (List a)L = 1 + X • L

e alberi binari:

data Tree a = Nil | Branch a (Tree a) (Tree a)T = 1 + X • T²

Ora, il mio primo istinto di matematico è impazzire con queste espressioni e cercare di risolvere per Le T. Potrei farlo attraverso ripetute sostituzioni, ma sembra molto più facile abusare orribilmente della notazione e fingere di poterlo riorganizzare a piacimento. Ad esempio, per un elenco collegato:

L = 1 + X • L

(1 - X) • L = 1

L = 1 / (1 - X) = 1 + X + X² + X³ + ...

dove ho usato l'espansione della serie di potenze 1 / (1 - X)in modo totalmente ingiustificato per ottenere un risultato interessante, vale a dire che un Ltipo è o Nilo contiene 1 elemento o contiene 2 elementi o 3, ecc.

Diventa più interessante se lo facciamo per alberi binari:

T = 1 + X • T²

X • T² - T + 1 = 0

T = (1 - √(1 - 4 • X)) / (2 • X)

T = 1 + X + 2 • X² + 5 • X³ + 14 • X⁴ + ...

di nuovo, usando l'espansione della serie di potenza (eseguita con Wolfram Alpha ). Questo esprime il fatto non ovvio (per me) che esiste un solo albero binario con 1 elemento, 2 alberi binari con due elementi (il secondo elemento può essere sul ramo sinistro o destro), 5 alberi binari con tre elementi ecc. .

Quindi la mia domanda è: cosa ci faccio qui? Queste operazioni sembrano ingiustificate (qual è esattamente la radice quadrata di un tipo di dati algebrico comunque?) Ma portano a risultati sensati. il quoziente di due tipi di dati algebrici ha qualche significato nell'informatica o è solo un inganno notazionale?

E, forse più interessante, è possibile estendere queste idee? Esiste una teoria dell'algebra dei tipi che consente, ad esempio, funzioni arbitrarie sui tipi o che i tipi richiedono una rappresentazione in serie di potenze? Se riesci a definire una classe di funzioni, allora la composizione delle funzioni ha qualche significato?


19
Potresti trovare questo interessante / pertinente: blog.lab49.com/archives/3011
shang

4
Non se memorizza i dati in ogni nodo. O sembra Branch x (Branch y Nil Nil) Nilo sembra Branch x Nil (Branch y Nil Nil).
Chris Taylor,

4
@nlucaroni: bottom è un valore, non un tipo. Un vero tipo zero non avrebbe alcun valore di quel tipo, cosa impossibile in Haskell se non si ignorano i fondi. Se prendi in considerazione i valori di fondo, i tipi che contengono solo i fondi diventano il tipo di unità che non è utile per la maggior parte del tempo e anche molte altre cose si rompono.
CA McCann

3
Sono d'accordo che è pratica comune di Haskell, è ancora sciocca. Vale a dire, significa che usiamo il "fondo" in modo diverso, quindi lo fanno nella logica e nella teoria dei tipi che mi sembrano cattive. Guardare lo stesso dal codice puro non li rende uguali: "Affrontare la Squadra Imbarazzante" chiarisce che la semantica di Haskell ha tutta una serie di "valori cattivi" di cui il looping per sempre e il lancio di un'eccezione non sono chiaramente gli stessi . Sostituire l'uno con l'altro non è un ragionamento equazionale valido. Haskell ha un vocabolario per descrivere questi valori cattivi undefined, throwecc Dovremmo usarlo.
Philip JF,

17
La mia mente è stata colpita da questa domanda
TheIronKnuckle,

Risposte:


138

Disclaimer: Gran parte di questo non funziona davvero bene quando si tiene conto di ⊥, quindi lo ignorerò palesemente per motivi di semplicità.

Alcuni punti iniziali:

  • Si noti che "unione" non è probabilmente il miglior termine per A + B qui - che è specificamente un disgiunta unione dei due tipi, in quanto le due parti si distinguono anche se i loro tipi sono gli stessi. Per quello che vale, il termine più comune è semplicemente "tipo di somma".

  • I tipi Singleton sono, in effetti, tutti i tipi di unità. Si comportano in modo identico sotto manipolazioni algebriche e, cosa ancora più importante, la quantità di informazioni presenti è ancora preservata.

  • Probabilmente vuoi anche un tipo zero. Haskell lo fornisce come Void. Non ci sono valori il cui tipo è zero, così come esiste un valore il cui tipo è uno.

Manca ancora un'operazione importante, ma ci tornerò tra un momento.

Come probabilmente avrai notato, Haskell tende a prendere in prestito concetti dalla teoria delle categorie e tutto quanto sopra ha un'interpretazione molto semplice in quanto tale:

  • Dati gli oggetti A e B in Hask , il loro prodotto A × B è il tipo unico (fino all'isomorfismo) che consente due proiezioni prima : A × B → A e snd : A × B → B, dove dati qualsiasi tipo C e funzioni f : C → A, g : C → B è possibile definire l'associazione f &&& g : C → A × B tale che prima ∘ (f &&& g) = f e similmente per g . La parametricità garantisce automaticamente le proprietà universali e la mia scelta di nomi tutt'altro che sottile dovrebbe darti l'idea. L' (&&&)operatore è definito Control.Arrow, a proposito.

  • Il doppio di quanto sopra è il coprodotto A + B con iniezioni inl : A → A + B e inr : B → A + B, dove dato qualsiasi tipo C e funzioni f : A → C, g : B → C, è possibile definire il copairing f ||| g : A + B → C tale che valgono le ovvie equivalenze. Ancora una volta, la parametricità garantisce automaticamente la maggior parte delle parti difficili. In questo caso, le iniezioni standard sono semplicemente Lefte Righted il copairing è la funzione either.

Molte delle proprietà dei tipi di prodotto e somma possono essere derivate da quanto sopra. Si noti che qualsiasi tipo singleton è un oggetto terminale di Hask e qualsiasi tipo vuoto è un oggetto iniziale.

Tornando alla suddetta operazione mancante, in una categoria chiusa cartesiana si hanno oggetti esponenziali che corrispondono alle frecce della categoria. Le nostre frecce sono funzioni, i nostri oggetti sono tipi con tipo *e il tipo A -> Bsi comporta effettivamente come B A nel contesto della manipolazione algebrica dei tipi. Se non è ovvio il motivo per cui questo dovrebbe valere, considera il tipo Bool -> A. Con solo due possibili input, una funzione di quel tipo è isomorfa a due valori di tipo A, ad es (A, A). Perché Maybe Bool -> Aabbiamo tre possibili input e così via. Inoltre, osserva che se riformuliamo la definizione di copairing sopra per usare la notazione algebrica, otteniamo l'identità C A × C B = CA + B .

Per quanto riguarda il motivo per cui tutto ciò ha senso - e in particolare perché è giustificato l'uso dell'espansione della serie di potenze - si noti che gran parte di quanto sopra si riferisce agli "abitanti" di un tipo (ovvero, valori distinti che hanno quel tipo) in ordine per dimostrare il comportamento algebrico. Per rendere esplicita quella prospettiva:

  • Il tipo di prodotto (A, B)rappresenta un valore ciascuno da Ae B, preso in modo indipendente. Quindi, per qualsiasi valore fisso a :: A, esiste un valore di tipo (A, B)per ogni abitante di B. Questo è ovviamente il prodotto cartesiano e il numero di abitanti del tipo di prodotto è il prodotto del numero di abitanti dei fattori.

  • Il tipo di somma Either A Brappresenta un valore da uno Ao B, con i rami sinistro e destro distinti. Come accennato in precedenza, questa è un'unione disgiunta e il numero di abitanti del tipo somma è la somma del numero di abitanti delle somme.

  • Il tipo esponenziale B -> Arappresenta una mappatura da valori di tipo Ba valori di tipo A. Per qualsiasi argomento fisso b :: B, qualsiasi valore di Apuò essere assegnato ad esso; un valore di tipo B -> Aseleziona una tale mappatura per ogni input, che equivale a un prodotto di tante copie Aquanti ne Bhanno gli abitanti, da cui l'espiazione.

Sebbene inizialmente sia allettante trattare i tipi come insiemi, in questo contesto in realtà non funziona molto bene - abbiamo l'unione disgiunta piuttosto che l'unione standard degli insiemi, non c'è un'interpretazione ovvia dell'intersezione o di molte altre operazioni dell'insieme, e noi di solito non importa di impostare l'appartenenza (lasciandolo al controllo del tipo).

D'altra parte, le costruzioni di cui sopra passano molto tempo a parlare del conteggio degli abitanti e all'enumerazione dei possibili valori di un tipo è un concetto utile qui. Questo ci porta rapidamente alla combinatoria enumerativa e se consulti l'articolo di Wikipedia collegato scoprirai che una delle prime cose che fa è definire "coppie" e "unioni" esattamente nello stesso senso dei tipi di prodotto e somma tramite generando funzioni , quindi fa lo stesso per "sequenze" identiche alle liste di Haskell usando esattamente la stessa tecnica che hai fatto.


Modifica: Oh, ed ecco un rapido bonus che penso dimostra il punto in modo sorprendente. In un commento hai menzionato che per un tipo di albero T = 1 + T^2puoi derivare l'identità T^6 = 1, che è chiaramente sbagliata. Tuttavia, T^7 = T fa presa, e una biiezione tra alberi e sette-uple di alberi può essere costruito direttamente, cf. "Sette alberi in uno" di Andreas Blass .

Modifica × 2: sull'argomento della costruzione "derivata di un tipo" menzionata in altre risposte, potresti anche goderti questo documento dello stesso autore che si basa ulteriormente sull'idea, comprese le nozioni di divisione e altre interessanti cose.


3
Questa è una grande spiegazione, in particolare come punto di partenza in cose come strictlypositive.org/diff.pdf
acfoltzer

26
@acfoltzer: grazie! :] E sì, è un ottimo documento che sviluppa queste idee. Sai, penso che almeno il 5% della mia reputazione totale sulla SO possa essere attribuito a "aiutare le persone a capire uno dei documenti di Conor McBride" ...
CA McCann

45

Gli alberi binari sono definiti dall'equazione T=1+XT^2nel semina dei tipi. Per costruzione, T=(1-sqrt(1-4X))/(2X)è definito dalla stessa equazione nel seminare di numeri complessi. Quindi, dato che stiamo risolvendo la stessa equazione nella stessa classe di struttura algebrica, in realtà non dovrebbe sorprendere vedere alcune somiglianze.

Il problema è che quando ragioniamo sui polinomi nel seminare di numeri complessi usiamo in genere il fatto che i numeri complessi formano un anello o addirittura un campo, quindi ci troviamo a utilizzare operazioni come la sottrazione che non si applicano ai semirings. Ma spesso possiamo eliminare le sottrazioni dai nostri argomenti se abbiamo una regola che ci consente di annullare da entrambi i lati di un'equazione. Questo è il tipo di cosa dimostrato da Fiore e Leinster che mostra che molti argomenti sugli anelli possono essere trasferiti a semirings.

Ciò significa che molte delle tue conoscenze matematiche sugli anelli possono essere trasferite in modo affidabile ai tipi. Di conseguenza, alcuni argomenti che coinvolgono numeri complessi o serie di potenze (nell'anello delle serie di potenze formali) possono passare ai tipi in modo completamente rigoroso.

Comunque c'è di più nella storia di questo. Una cosa è dimostrare che due tipi sono uguali (diciamo) mostrando che due serie di potenze sono uguali. Ma puoi anche dedurre informazioni sui tipi controllando i termini nelle serie di potenze. Non sono sicuro di quale dovrebbe essere la dichiarazione formale qui. (Raccomando il documento di Brent Yorgey sulle specie combinatorie per alcuni lavori strettamente correlati ma le specie non sono uguali ai tipi.)

Quello che trovo assolutamente strabiliante è che ciò che hai scoperto può essere esteso al calcolo. I teoremi sul calcolo possono essere trasferiti al semiring dei tipi. In effetti, anche gli argomenti sulle differenze finite possono essere trasferiti e scopri che i teoremi classici dell'analisi numerica hanno interpretazioni nella teoria dei tipi.

Divertiti!


Questa roba di contesto differenziazione / un buco è piuttosto interessante. Vediamo se ho questo diritto. Una coppia, con rappresentazione algebrica P = X^2, ha derivata dP = X + X, così Eithercome il contesto a un foro della coppia. È abbastanza bello. Potremmo "integrarci" Eitherper ottenere anche un paio. Ma se proviamo a "integrarci" Maybe(con il tipo M = 1 + X), allora dobbiamo avere ciò \int M = X + X^2 / 2che è privo di senso (che cos'è un mezzo tipo?) Questo significa che Maybenon è il contesto monoforo di nessun altro tipo?
Chris Taylor,

6
@ChrisTaylor: i contesti a un buco conservano le informazioni sulla posizione all'interno dei prodotti, vale a dire, (A,A)con un buco in esso è un Apo 'che ti dice da che parte si trova il buco. Un Asolo non ha alcun buco distinto da riempire, motivo per cui non puoi "integrarlo". Il tipo di informazioni mancanti in questo caso è, ovviamente, 2.
CA McCann

Ho scritto su come dare un senso a tipi come X^2/2 blog.sigfpe.com/2007/09/type-of-distinct-pairs.html
sigfpe

@ user207442, non hai fatto qualcosa anche sulla biiezione tra un albero e sette alberi? Mi sono collegato a un articolo su questo nella mia risposta, ma potrei giurare che ricordo di averlo letto per la prima volta sul tuo blog.
CA McCann

1
@ChrisTaylor Sulle differenze finite (in realtà "divise") c'è questo: strictpositive.org/CJ.pdf Ma a quel punto Conor non si rese conto che stava descrivendo le differenze. L'ho scritto anche se può essere difficile da seguire: blog.sigfpe.com/2010/08/… Scriverei un documento ma non sono molto bravo a finirli .
sigfpe,

22

Sembra che tutto ciò che stai facendo sia espandere la relazione di ricorrenza.

L = 1 + X  L
L = 1 + X  (1 + X  (1 + X  (1 + X  ...)))
  = 1 + X + X^2 + X^3 + X^4 ...

T = 1 + X  T^2
L = 1 + X  (1 + X  (1 + X  (1 + X  ...^2)^2)^2)^2
  = 1 + X + 2  X^2 + 5  X^3 + 14  X^4 + ...

E poiché le regole per le operazioni sui tipi funzionano come le regole per le operazioni aritmetiche, puoi usare mezzi algebrici per aiutarti a capire come espandere la relazione di ricorrenza (poiché non è ovvio).


1
"Dato che le regole per le operazioni sui tipi funzionano come le regole per le operazioni aritmetiche ..." - non lo fanno, però. Non c'è nozione di sottrazione di tipi, per non parlare di divisione e radici quadrate. Quindi suppongo che la mia domanda sia: quando puoi passare da una manipolazione algebrica supponendo che Xsia un elemento dei numeri reali a un'affermazione vera sui tipi, e inoltre, dove fa la corrispondenza (coefficiente del ntermine di laurea) <=> (numero di tipi che contengono nelementi) provengono?
Chris Taylor,

1
Ad esempio, dall'espressione per un albero ( T = 1 + T^2) posso derivare T^6 = 1(ovvero le soluzioni x^2 - x + 1 = 0sono le sime radici dell'unità) ma chiaramente non è vero che un tipo di prodotto costituito da sei alberi binari è equivalente all'unità ().
Chris Taylor,

3
@ChrisTaylor, ma c'è qualcosa che accade lì, in quanto v'è un isomorfismo tra T^7e T. cf. arxiv.org/abs/math/9405205
luqui

7
@ChrisTaylor, ecco qualcosa a cui pensare. Quando aggiungi nuove operazioni algebriche, speri di non rompere le proprietà di quelle esistenti. Se riesci a trovare la stessa risposta in due modi diversi, dovrebbero essere d'accordo. Pertanto, fornendo non v'è alcuna dichiarazione a tutti per L = 1 + X * L, è meglio che essere la stessa che si ottiene quando si espande serie, per la coerenza. Altrimenti potresti eseguire il risultato all'indietro per ottenere qualcosa di falso sui reali.
Luqui,

2
@ChrisTaylor Esiste davvero una nozione di divisione dei tipi, cerca "Tipi di quoziente" per ulteriori informazioni. Se corrisponde bene alla divisione polinomiale, non lo so. Capita di essere poco pratico, imho, ma è là fuori.
Doug McClean,

18

Non ho una risposta completa, ma queste manipolazioni tendono a "funzionare". Un documento pertinente potrebbe essere Oggetti di categorie come numeri complessi di Fiore e Leinster - me ne sono imbattuto mentre leggevo il blog di sigfpe su un argomento correlato ; il resto di quel blog è una miniera d'oro per idee simili e vale la pena dare un'occhiata!

Puoi anche differenziare i tipi di dati, tra l'altro - che ti darà la cerniera appropriata per il tipo di dati!


12
Il trucco di Zipper è fantastico. Vorrei averlo capito.
spruzzo

Puoi anche creare cerniere in Scheme usando continuazioni delimitate, che ti consentono di derivarle genericamente.
Jon Purdy,

10

L'Algebra of Communicating Processes (ACP) si occupa di tipi simili di espressioni per i processi. Offre addizione e moltiplicazione come operatori per scelta e sequenza, con elementi neutri associati. Sulla base di questi ci sono operatori per altri costrutti, come il parallelismo e l'interruzione. Vedi http://en.wikipedia.org/wiki/Algebra_of_Communicating_Processes . C'è anche un articolo online intitolato "Una breve storia dell'algebra di processo".

Sto lavorando per estendere i linguaggi di programmazione con gli ACP. Lo scorso aprile ho presentato un documento di ricerca a Scala Days 2012, disponibile all'indirizzo http://code.google.com/p/subscript/

Alla conferenza ho dimostrato un debugger che esegue una specifica ricorsiva parallela di una borsa:

Borsa = A; (Bag & a)

dove A e uno stanno per le azioni di input e output; il punto e virgola e la e commerciale indicano sequenza e parallelismo. Guarda il video su SkillsMatter, raggiungibile dal link precedente.

Una specifica di borsa più paragonabile a

L = 1 + X • L

sarebbe

B = 1 + X&B

ACP definisce il parallelismo in termini di scelta e sequenza usando gli assiomi; vedi l'articolo di Wikipedia. Mi chiedo a cosa serva l'analogia della borsa

L = 1 / (1-X)

La programmazione in stile ACP è utile per parser di testo e controller GUI. Specifiche come

searchCommand = clicked (searchButton) + key (Enter)

cancelCommand = clicked (cancelButton) + tasto (Escape)

può essere scritto in modo più conciso rendendo i due perfezionamenti "cliccati" e "chiave" impliciti (come ciò che Scala consente con le funzioni). Quindi possiamo scrivere:

searchCommand = searchButton + Enter

cancelCommand = cancelButton + Escape

I lati di destra ora contengono operandi che sono dati, piuttosto che processi. A questo livello non è necessario sapere quali raffinamenti impliciti trasformeranno questi operandi in processi; non si limiterebbero necessariamente ad azioni di input; si applicherebbero anche le azioni di output, ad esempio nella specifica di un robot di prova.

I processi ottengono in questo modo i dati come compagni; quindi conio il termine "item algebra".


6

Serie di calcoli e maclaurin con tipi

Ecco un'altra aggiunta minore: un'intuizione combinatoria sul perché i coefficienti in un'espansione in serie dovrebbero "funzionare", in particolare concentrandosi sulle serie che possono essere ricavate dal calcolo di Taylor-Maclaurin . NB: l'espansione della serie di esempio fornita dal tipo di elenco manipolato è una serie di Maclaurin.

Poiché altre risposte e commenti riguardano il comportamento delle espressioni di tipo algebrico (somme, prodotti ed esponenti), questa risposta elude quel dettaglio e si focalizzerà sul tipo di "calcolo".

In questa risposta potresti notare delle virgolette invertite. Ci sono due ragioni:

  • ci occupiamo di fornire interpretazioni da un dominio a entità di un altro e sembra appropriato delimitare tali concetti stranieri in questo modo.
  • alcune nozioni potranno essere formalizzate in modo più rigoroso, ma la forma e le idee sembrano più importanti (e occupano meno spazio per scrivere) rispetto ai dettagli.

Definizione di serie di Maclaurin

La serie Maclaurin di una funzione f : ℝ → ℝè definita come

f(0) + f'(0)X + (1/2)f''(0)X² + ... + (1/n!)f⁽ⁿ⁾(0)Xⁿ + ...

dove f⁽ⁿ⁾significa la nderivata th di f.

Per riuscire a dare un senso alla serie Maclaurin interpretata con i tipi, dobbiamo capire come possiamo interpretare tre cose in un contesto di tipo:

  • un derivato (possibilmente multiplo)
  • applicando una funzione a 0
  • termini come (1/n!)

e si scopre che questi concetti dell'analisi hanno controparti adatte nel mondo dei tipi.

Cosa intendo per "controparte adeguata"? Dovrebbe avere il sapore di un isomorfismo: se possiamo preservare la verità in entrambe le direzioni, i fatti derivati ​​in un contesto possono essere trasferiti all'altro.

Calcolo con tipi

Cosa significa la derivata di un'espressione di tipo? Si scopre che per una classe ampia e ben educata ("differenziabile") di espressioni e funzioni di tipo, esiste un'operazione naturale che si comporta in modo abbastanza simile da essere un'interpretazione adeguata!

Per rovinare la battuta finale, l'operazione analoga alla differenziazione è quella di creare "contesti a un buco". Questo è un posto eccellente per espandersi ulteriormente su questo particolare punto, ma il concetto di base di un contesto a un foro ( da/dx) è che rappresenta il risultato dell'estrazione di un singolo sotto-elemento di un particolare tipo ( x) da un termine (di tipo a), preservando tutte le altre informazioni, comprese quelle necessarie per determinare la posizione originale del sottoelemento. Ad esempio, un modo per rappresentare un contesto a un foro per un elenco è con due elenchi: uno per gli elementi precedenti a quello estratto e uno per gli elementi successivi.

La motivazione per identificare questa operazione con differenziazione deriva dalle seguenti osservazioni. Scriviamo da/dxper indicare il tipo di contesti a un foro per tipo acon foro di tipo x.

d1/dx = 0
dx/dx = 1
d(a + b)/dx = da/dx + db/dx
d(a × b)/dx = a × db/dx + b × da/dx
d(g(f(x))/dx = d(g(y))/dy[f(x)/a] × df(x)/dx

Qui 1e 0rappresentano i tipi con esattamente uno e esattamente zero abitanti, rispettivamente, +e ×rappresentano i tipi di somma e di prodotto come al solito. fe gsono usati per rappresentare funzioni di tipo, o formatori di espressioni di tipo, e [f(x)/a]significa l'operazione di sostituzione f(x)per ogni aespressione precedente.

Questo può essere scritto in uno stile senza punti, scrivendo f'per indicare la funzione derivata della funzione di tipo f, quindi:

(x ↦ 1)' = x ↦ 0
(x ↦ x)' = x ↦ 1
(f + g)' = f' + g'
(f × g)' = f × g' + g × f'
(g ∘ f)' = (g' ∘ f) × f'

che può essere preferibile.

NB le uguaglianze possono essere rese rigorose ed esatte se definiamo derivati ​​usando classi di isomorfismi di tipi e funzioni.

Ora, notiamo in particolare che le regole di calcolo relative alle operazioni algebriche di addizione, moltiplicazione e composizione (spesso chiamate regole di somma, prodotto e catena) si riflettono esattamente nell'operazione di "fare un buco". Inoltre, i casi base di "fare un buco" in un'espressione costante o il termine xstesso si comportano anche come differenziazione, quindi per induzione otteniamo un comportamento simile alla differenziazione per tutte le espressioni di tipo algebrico.

Ora possiamo interpretare la differenziazione, cosa significa la n"derivata" di un'espressione di tipo dⁿe/dxⁿ? È un tipo che rappresenta ncontesti di luogo: termini che, se "riempiti" con ntermini di tipo, xproducono un e. C'è un'altra osservazione chiave relativa a (1/n!)"venire dopo".

La parte invariante di un tipo di funzione: applicare una funzione a 0

Abbiamo già un'interpretazione per 0nel tipo di mondo: un tipo vuoto senza membri. Cosa significa, da un punto di vista combinatorio, applicare una funzione di tipo ad essa? In termini più concreti, supponendo che fsia una funzione di tipo, che f(0)aspetto ha? Bene, certamente non abbiamo accesso a niente di tipo 0, quindi tutte le costruzioni f(x)che richiedono un xnon sono disponibili. Ciò che rimane sono quei termini che sono accessibili in loro assenza, che possiamo chiamare la parte "invariante" o "costante" del tipo.

Per un esempio esplicito, prendi il Maybefunctor, che può essere rappresentato algebricamente come x ↦ 1 + x. Quando lo applichiamo a 0, otteniamo 1 + 0- è proprio come 1: l'unico valore possibile è il Nonevalore. Per un elenco, allo stesso modo, otteniamo solo il termine corrispondente all'elenco vuoto.

Quando lo riportiamo indietro e interpretiamo il tipo f(0)come un numero, può essere pensato come il conteggio di quanti termini di tipo f(x)(per qualsiasi x) possono essere ottenuti senza accesso a un x: cioè, il numero di termini "vuoti" .

Mettendolo insieme: completa interpretazione di una serie di Maclaurin

Temo di non riuscire a pensare a un'interpretazione diretta appropriata di (1/n!)un tipo.

Se consideriamo, tuttavia, il tipo f⁽ⁿ⁾(0)alla luce di quanto sopra, vediamo che può essere interpretato come il tipo di ncontesti -place per un termine di tipo f(x)che non contiene già un x - cioè, quando li "integriamo" nvolte , il termine risultante ha esattamente n x s, né più né meno. Quindi l'interpretazione del tipo f⁽ⁿ⁾(0)come un numero (come nei coefficienti della serie di Maclaurin di f) è semplicemente un conteggio di quanti ncontesti di questo spazio vuoto ci sono. Ci siamo quasi!

Ma dove (1/n!)finisce? Esaminare il processo di tipo "differenziazione" ci mostra che, se applicato più volte, preserva l '"ordine" in cui vengono estratti i sotter. Ad esempio, considera il termine (x₀, x₁)di tipo x × xe l'operazione di "fare un buco" due volte. Otteniamo entrambe le sequenze

(x₀, x₁)  ↝  (_₀, x₁)  ↝  (_₀, _₁)
(x₀, x₁)  ↝  (x₀, _₀)  ↝  (_₁, _₀)
(where _ represents a 'hole')

anche se entrambi provengono dallo stesso termine, perché ci sono 2! = 2modi per prendere due elementi da due, preservando l'ordine. In generale, ci sonon! modi per prendere nelementi da n. Quindi, al fine di ottenere un conteggio del numero di configurazioni di un tipo di funzione che hanno nelementi, dobbiamo contare il tipo f⁽ⁿ⁾(0)e dividere per n!, esattamente come nei coefficienti della serie Maclaurin.

Quindi dividere per n!risulta interpretabile semplicemente come se stesso.

Considerazioni finali: definizioni "ricorsive" e analiticità

Innanzitutto, alcune osservazioni:

  • se una funzione f: ℝ → ℝ ha una derivata, questa derivata è unica
  • allo stesso modo, se una funzione f: ℝ → ℝ è analitica, ha esattamente una serie polinomiale corrispondente

Poiché abbiamo la regola della catena, possiamo usare la differenziazione implicita , se formalizziamo derivati ​​di tipo come classi di isomorfismo. Ma la differenziazione implicita non richiede alcuna manovra aliena come sottrazione o divisione! Quindi possiamo usarlo per analizzare le definizioni di tipo ricorsivo. Per fare un esempio della tua lista, abbiamo

L(X) ≅ 1 + X × L(X)
L'(X) = X × L'(X) + L(X)

e quindi possiamo valutare

L'(0) = L(0) = 1

per ottenere il coefficiente di nella serie Maclaurin.

Ma poiché siamo fiduciosi che queste espressioni siano effettivamente rigorosamente 'differenziabili', anche se solo implicitamente, e poiché abbiamo la corrispondenza con le funzioni ℝ → ℝ, dove i derivati ​​sono certamente unici, possiamo essere certi che anche se otteniamo i valori usando ' operazioni illegali, il risultato è valido.

Allo stesso modo, per usare la seconda osservazione, a causa della corrispondenza (è un omomorfismo?) Con funzioni ℝ → ℝ, sappiamo che, purché siamo soddisfatti che una funzione abbia una serie di Maclaurin, se possiamo trovare una serie in tutti , i principi di cui sopra possono essere applicati per renderlo rigoroso.

Per quanto riguarda la tua domanda sulla composizione delle funzioni, suppongo che la regola della catena fornisca una risposta parziale.

Non sono sicuro a quanti ADT in stile Haskell si applichi, ma sospetto che siano molti se non tutti. Ho scoperto una prova davvero meravigliosa di questo fatto, ma questo margine è troppo piccolo per contenerlo ...

Ora, certamente questo è solo un modo per capire cosa sta succedendo qui e probabilmente ci sono molti altri modi.

Sommario: TL; DR

  • digitare 'differenziazione' corrisponde a ' fare un buco '.
  • applicare un funzione per 0ottenere i termini "vuoti" per quel funzione.
  • Le serie di potenze di maclaurina pertanto (in qualche modo) corrispondono rigorosamente all'enumerazione del numero di membri di un tipo di funzione con un certo numero di elementi.
  • la differenziazione implicita rende questo più a tenuta stagna.
  • unicità dei derivati ​​e unicità delle serie di potenze ci consente di confondere i dettagli e funziona.

6

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 + aX + aX² + ...

potrebbe essere scritto

Σ[i  ℕ]aX

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.

  1. 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.

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.