“Una prova è un programma; la formula che dimostra è un tipo per il programma "


37

Questo potrebbe essere un tipo di domanda filosofica, ma credo che ci sia una risposta obiettiva ad esso.

Se leggi l'articolo di Wikipedia su Haskell, puoi trovare quanto segue:

Il linguaggio è radicato nelle osservazioni di Haskell Curry e dei suoi discendenti intellettuali, secondo cui "una dimostrazione è un programma; la formula che dimostra è un tipo per il programma"

Ora, quello che sto chiedendo è: questo non si applica praticamente a quasi tutti i linguaggi di programmazione? Quale caratteristica (o insieme di caratteristiche) di Haskell lo rende conforme a questa affermazione? In altre parole, quali sono i modi evidenti in cui questa affermazione ha influenzato il design della lingua?


4
Qualcuno vuole spiegare perché i voti "vicini", per favore?

1
@Grigory Javadyan: non ho votato per chiudere, ma è probabilmente perché la domanda è border-off-topic per SO - domande filosofiche, obiettivamente rispondenti o meno, non sono generalmente appropriate qui. In questo caso, penso che sia giustificabile, perché la risposta ha profonde implicazioni pratiche su come viene effettivamente utilizzato Haskell.

2
@Grigory: se questa domanda era un vero problema con una soluzione reale (nel codice), può rimanere su SO. Votato per chiudere e passare a Programmatori.

9
Solo per aggiungere a questo perché sono un po 'stufo - le risposte a queste domande sono piene di riferimenti alla dura ricerca CS e in questo senso più "oggettiva" del 90% di SO. Inoltre, i criteri di sixlettervariable (che la soluzione necessita di codice) è follemente stretto per una vasta gamma di domande di programmazione autentiche che non sono né soggettive né off-topic. Odio davvero vedere riemergere il dibattito inclusista / delezionista su SO, ma se chiaramente i thread di programmazione come questo vengono urtati, allora mi preoccupo ...
sclv

2
Sono ambivalente su dove finisca, soprattutto perché non sono molto chiaro su quale tipo di contenuto dovrebbe essere su Programmers.SE vs. SO. Ma dirò che i programmatori sono descritti in diversi punti come "domande soggettive", cosa che questa domanda non è enfaticamente no . La mia risposta è informale e ondulata quanto potrebbe essere e potrei ancora eseguire il backup della maggior parte facilmente con riferimenti che anche i redattori di Wikipedia accetterebbero.
CA McCann,

Risposte:


38

Il concetto essenziale si applica universalmente in qualche modo, sì, ma raramente in modo utile.

Innanzitutto, dal punto di vista della teoria dei tipi ciò presuppone, si ritiene che i linguaggi "dinamici" abbiano un solo tipo, che contiene (tra le altre cose) metadati sulla natura del valore che il programmatore vede, incluso ciò che questi linguaggi dinamici chiamerebbero un "tipo" stesso (che non è la stessa cosa, concettualmente). È probabile che tali prove non siano interessanti, quindi questo concetto è principalmente rilevante per le lingue con sistemi di tipo statico.

Inoltre, molte lingue che presumibilmente hanno un "sistema di tipi statici" devono essere considerate pratiche nella pratica, in questo contesto, perché consentono l'ispezione e la conversione dei tipi in fase di esecuzione. In particolare, ciò significa qualsiasi linguaggio con supporto predefinito incorporato per "riflessione" o simili. C #, per esempio.

Haskell è insolito in quante informazioni si aspetta da un tipo - in particolare, le funzioni non possono dipendere da alcun valore diverso da quelli specificati come argomenti. In un linguaggio con variabili globali mutabili, d'altra parte, qualsiasi funzione può (potenzialmente) ispezionare quei valori e cambiare il comportamento di conseguenza. Quindi, una funzione Haskell con il tipo A -> Bpuò essere considerato come un programma proving in miniatura che Aimplica B; una funzione equivalente in molte altre lingue ci direbbe solo questo Ae qualunque cosa lo stato globale rientri nell'ambito di applicazione implica B.

Si noti che mentre Haskell ha supporto per cose come la riflessione e i tipi dinamici, l' uso di tali caratteristiche deve essere indicato nella firma del tipo di una funzione; allo stesso modo per l'uso dello stato globale. Nessuno dei due è disponibile per impostazione predefinita.

Ci sono modi per rompere le cose in Haskell così, ad esempio consentendo eccezioni runtime, o utilizzando le operazioni primitive non standard forniti dal compilatore, ma quelli dotati di una forte aspettativa che saranno utilizzati solo con la piena comprensione in modi che ha vinto' danneggiare il significato del codice esterno. In teoria si potrebbe dire lo stesso di altre lingue, ma in pratica con la maggior parte delle altre lingue è sia più difficile realizzare le cose senza "imbrogliare", sia meno malvisto a "imbrogliare". E ovviamente nelle vere lingue "dinamiche" il tutto rimane irrilevante.

Il concetto può essere portato molto oltre rispetto a quello di Haskell.


Si noti tuttavia che le eccezioni possono essere completamente integrate in un sistema di tipi.
gardenhead

18

Hai ragione sul fatto che la corrispondenza Curry-Howard è una cosa molto generale. Vale la pena familiarizzare con un po 'della sua storia: http://en.wikipedia.org/wiki/Curry-Howard_correspondence

Noterai che, come originariamente formulato, questa corrispondenza si applicava in particolare alla logica intuizionistica da un lato e al calcolo lambda tipicamente tipizzato (STLC) dall'altro.

Haskell classico - o '98 o versioni precedenti, erano strettamente legate allo STLC, e per la maggior parte c'era una traduzione molto semplice e diretta tra qualsiasi espressione data in Haskell e un termine corrispondente nello STLC (esteso con ricorsione e alcuni tipi primitivi). Quindi questo ha reso Curry-Howard molto esplicito. Oggi, grazie alle estensioni, tale traduzione è un affare un po 'più complicato.

Quindi, in un certo senso, la domanda è perché Haskell "desugar" nello STLC in modo così semplice. Due cose vengono in mente:

  • Tipi. A differenza di Scheme, che è anche una sorta di calcolo lambda zuccherato (tra le altre cose), Haskell è fortemente tipizzato. Ciò significa che non esistono termini nel classico Haskell che per definizione non possono essere termini ben tipizzati nell'STLC.
  • Purezza. Ancora una volta, a differenza di Scheme, ma come STLC, Haskell è un linguaggio puro, referenzialmente trasparente. Questo è abbastanza importante. Le lingue con effetti collaterali possono essere incorporate in lingue prive di effetti collaterali. Tuttavia, farlo è una trasformazione dell'intero programma, non semplicemente una desugaring locale. Quindi, per avere la corrispondenza diretta , è necessario iniziare con un linguaggio puramente funzionale.

C'è anche un modo importante in cui Haskell, come la maggior parte delle lingue, fallisce per quanto riguarda un'applicazione diretta della corrispondenza Curry-Howard. Haskell, come linguaggio turing completo, contiene la possibilità di ricorsione illimitata, e quindi di non terminazione. L'STLC non ha un operatore fixpoint, non è completo di turing ed è fortemente normalizzato , vale a dire che nessuna riduzione di un termine nell'STLC non riuscirà a terminare. La possibilità di ricorsione significa che si può "imbrogliare" Curry-Howard. Ad esempio, let x = x in xha il tipoforall a. a- cioè, dal momento che non ritorna mai, posso fingere che mi dia qualcosa! Dato che possiamo sempre farlo in Haskell, ciò significa che non possiamo "credere" completamente in alcuna prova corrispondente a un programma Haskell a meno che non abbiamo una prova separata che il programma stesso sta terminando.

Il lignaggio della programmazione funzionale prima di Haskell (in particolare la famiglia ML) era il risultato di una ricerca CS focalizzata sulla costruzione di linguaggi su cui potevi facilmente provare cose (tra le altre cose), ricerche molto consapevoli e originate dalla CH per cominciare. Al contrario, Haskell è servito sia come lingua ospite che come fonte d'ispirazione per una serie di assistenti di prova in fase di sviluppo, come Agda ed Epigram, che sono radicati negli sviluppi della teoria dei tipi molto legati al lignaggio di CH.


1
Potrebbe essere utile sottolineare che il non-termine mina la prova in certi modi che, sebbene ovviamente catastrofici dal punto di vista logico, conservano molte altre proprietà. In particolare, una funzione A -> B, data da A, produrrà a Bo niente. Non produrrà mai a C, e quale valore di tipo Bfornisce, o se diverge, dipende ancora esclusivamente da quello Afornito.

@camccann - un po 'pignolo, ma distinguerei tra bottom e "niente", che è più simile Void, no? La pigrizia lo rende sempre più complicato. Direi che una funzione produce A -> B sempre un valore di tipo B, ma quel valore potrebbe avere meno informazioni di quanto ci si aspetterebbe.
sclv,

Nitpicking è divertente! Quando dico "niente" intendo a livello di valore in un contesto di valutazione, mentre il fondo esiste davvero solo come un'astrazione, non qualcosa di tangibile. Un'espressione in fase di valutazione non "vedrà" mai un valore di fondo, ma solo termini che non utilizza (che potrebbe essere inferiore) e termini che utilizza (che hanno valori non di fondo). Cercare di usare il fondo "non succede mai" in un certo senso perché tentare di farlo termina la valutazione dell'intera espressione prima che si verifichi l'uso.

12

Per un'approssimazione del primo ordine, la maggior parte delle altre lingue (debolmente e / o di tipo uni) non supporta la delineazione rigorosa a livello di lingua tra un

  • proposizione (cioè un tipo)
  • una prova (ovvero un programma che dimostra come possiamo costruire la proposizione da un insieme di primitivi e / o altri costrutti di ordine superiore )

e la stretta relazione tra i due. Semmai, le migliori garanzie sono fornite da altre lingue simili

  • dato un vincolo limitato sull'input, insieme a qualunque cosa accada nell'ambiente in quel momento, possiamo produrre un valore con un vincolo limitato. (tipi statici tradizionali, cfr C / Java)
  • ogni costrutto è dello stesso tipo (tipi dinamici, cf ruby ​​/ python)

Si noti che per tipo , ci riferiamo a una proposizione , e quindi qualcosa che descrive molte più informazioni, quindi semplicemente int o bool . In Haskell, esiste una cultura permeabile di una funzione influenzata solo dai suoi argomenti , senza eccezioni *.

Per essere un po 'più rigoroso, l'idea generale è che applicando un rigido approccio intuitivo a (quasi) tutti i costrutti del programma (cioè possiamo solo provare ciò che possiamo costruire), e limitando l'insieme di costrutti primitivi in ​​un tale modo che abbiamo

  • proposizioni rigorose per tutti i primitivi linguistici
  • una serie limitata di meccanismi con cui i primitivi possono essere combinati

Le costruzioni di Haskell tendono a prestarsi molto bene al ragionamento sul loro comportamento. Se siamo in grado di costruire una dimostrazione (leggi: funzione) dimostrando che ciò Aimplica B, questo ha proprietà molto utili:

  • è sempre detiene (sempre che abbiamo un A, possiamo costruire un B)
  • questa implicazione si basa solo su A, e niente altro.

permettendoci così di ragionare efficacemente sugli invarianti locali / globali. Per tornare alla domanda originale; Le caratteristiche del linguaggio di Haskell che affascinano meglio questa mentalità sono:

  • Purezza / segmentazione degli effetti in costrutti espliciti (gli effetti sono entrambi considerati e digitati!)
  • Digitare Inference / Checking in Haskell compilers
  • La capacità di incorporare invarianti di controllo e / o flusso di dati nelle proposizioni / tipi che un programma si propone di dimostrare: (con polimorfismo, famiglie di tipi, GADT ecc.)
  • Integrità referenziale

Nessuno dei quali è assolutamente unico per Haskell (molte di queste idee sono incredibilmente vecchie). Tuttavia, quando combinato con una ricca serie di astrazioni nelle librerie standard (solitamente presenti nelle classi di tipi), vari livelli di sintassi e un rigoroso impegno per la purezza nella progettazione del programma, finiamo con un linguaggio che in qualche modo riesce ad essere sia abbastanza pratico per le applicazioni del mondo reale , ma allo stesso tempo risulta più facile ragionare sulle lingue più tradizionali.

Questa domanda merita una risposta sufficientemente profonda e non potrei assolutamente renderle giustizia in questo contesto. Suggerirei di leggere di più su wikipedia / in letteratura:

* NB: sto sorvolando / ignorando alcuni degli aspetti più complicati delle impurità di Haskell (eccezioni, non terminazione ecc.) Che potrebbero solo complicare l'argomento.


4

Quale caratteristica? Il sistema di tipi (essendo statico, puro, polimorfico). Un buon punto di partenza sono i "Teoremi gratis" di Wadler. Notevole effetto sul design della lingua? Il tipo IO, digitare le classi.


0

La Gerarchia di Kleene ci mostra che le prove non sono programmi.

La prima relazione ricorsiva è:

R1( Program , Iteration )  Program halts at Iteration.
R2( Theorem , Proof ) Proof proves a Theorem.

Le prime relazioni ricorsivamente enumerabili sono:

(exists x) R1( Program , x )  Program Halts.
(exists x) R2( Theorem , x)   Theorem is provable.

Quindi, un programma è un teorema e l'iterazione esistente in cui il programma si interrompe è come la prova che esiste che prova il teorema.

Program = Theorem
Iteration = Proof

Quando un programma è prodotto correttamente da una specifica, dobbiamo essere in grado di dimostrare che soddisfa la specifica e se possiamo dimostrare che un programma soddisfa una specifica, allora è la sintesi corretta del programma. Quindi eseguiamo la sintesi del programma se dimostriamo che il programma soddisfa le specifiche. Il teorema che il programma soddisfa la specifica è il programma in quanto il teorema si riferisce al programma che è sintetizzato.

Le false conclusioni di Martin Lof non hanno mai prodotto programmi per computer ed è sorprendente che la gente creda che si tratti di una metodologia di sintesi del programma. Non sono mai stati forniti esempi completi di un programma in fase di sintesi. Una specifica come "input di un tipo e output di un programma di quel tipo" non è una funzione. Esistono più di questi programmi e sceglierne uno a caso non è una funzione ricorsiva né una funzione. È solo un tentativo sciocco di mostrare la sintesi del programma con un programma sciocco che non rappresenta un vero programma per computer che calcola una funzione ricorsiva.


2
In che modo questa risposta pone la domanda, "quali sono i modi evidenti in cui questa affermazione ha influenzato il design della lingua?"
moscerino

1
@gnat: questa risposta affronta un presupposto sottostante alla domanda originale, vale a dire: " doesn't this really apply to pretty much all the programming languages?" Questa risposta afferma / mostra che il presupposto non è valido, quindi non ha senso affrontare il resto delle domande che si basano su una premessa errata .
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.