Cosa fa la parola chiave `forall` in Haskell / GHC?


312

Sto cominciando a capire come forallviene utilizzata la parola chiave nei cosiddetti "tipi esistenziali" come questo:

data ShowBox = forall s. Show s => SB s

Questo è solo un sottoinsieme, tuttavia, di come forallviene utilizzato e semplicemente non riesco a pensare al suo uso in cose come questa:

runST :: forall a. (forall s. ST s a) -> a

O spiegando perché questi sono diversi:

foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

O tutto il RankNTypesresto ...

Tendo a preferire un inglese chiaro e privo di gergo piuttosto che i tipi di lingua che sono normali negli ambienti accademici. La maggior parte delle spiegazioni che cerco di leggere su questo (quelle che posso trovare attraverso i motori di ricerca) hanno questi problemi:

  1. Sono incompleti. Spiegano una parte dell'uso di questa parola chiave (come "tipi esistenziali") che mi fa sentire felice finché non leggo il codice che lo usa in un modo completamente diverso (comerunST , fooe barsopra).
  2. Sono densamente pieni di ipotesi secondo cui ho letto le ultime notizie su qualunque ramo della matematica discreta, teoria delle categorie o algebra astratta sia popolare questa settimana. (Se non leggo mai più le parole "consulta il documento qualunque per i dettagli dell'implementazione", sarà troppo presto.)
  3. Sono scritti in modi che spesso trasformano anche semplici concetti in grammatica e semantica tortuosamente distorte e fratturate.

Così...

Alla domanda reale. Qualcuno può spiegare completamente la forallparola chiave in un inglese semplice e chiaro (o, se esiste da qualche parte, indicare una spiegazione così chiara che ho perso) che non presume che io sia un matematico intriso di gergo?


Modificato per aggiungere:

Ci sono state due risposte di spicco tra quelle di qualità superiore di seguito, ma sfortunatamente posso scegliere solo una delle migliori. La risposta di Norman fu dettagliata e utile, spiegando le cose in un modo che mostrava alcune delle basi teoriche di foralle allo stesso tempo mostrandomi alcune delle implicazioni pratiche di esso. la risposta di Yairchu riguardato un'area che nessun altro ha menzionato (variabili di tipo con ambito) e ha illustrato tutti i concetti con codice e una sessione GHCi. Se fosse possibile selezionare entrambi i migliori, lo farei. Sfortunatamente non posso e, dopo aver esaminato da vicino entrambe le risposte, ho deciso che Yairchu è leggermente in contrasto con quello di Norman a causa del codice illustrativo e della spiegazione allegata. Questo è un po 'ingiusto, tuttavia, perché in realtà avevo bisogno di entrambe le risposte per capirlo al punto che forallnon mi lascia un debole senso di terrore quando lo vedo in una firma di tipo.


7
La wiki di Haskell sembra essere abbastanza amica dei principianti su questo argomento.
jhegedus,

Risposte:


263

Cominciamo con un esempio di codice:

foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
    postProcess val
    where
        val :: b
        val = maybe onNothin onJust mval

Questo codice non viene compilato (errore di sintassi) nel semplice Haskell 98. Richiede un'estensione per supportare la forallparola chiave.

Fondamentalmente, ci sono 3 differenti utilizzi comuni per la forallparola (o almeno così sembra ), e ciascuno ha il proprio interno Haskell: ScopedTypeVariables, RankNTypes/ Rank2Types, ExistentialQuantification.

Il codice sopra riportato non ottiene un errore di sintassi con nessuno dei due abilitati, ma verifica solo i tipi con ScopedTypeVariablesabilitato.

Variabili di tipo con ambito:

Le variabili di tipo con ambito aiutano a specificare i tipi per le whereclausole del codice all'interno . Rende il nello stesso come in .bval :: bbfoob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b

Un punto confuso : potresti sentire che quando si omette il forallda un tipo, in realtà è ancora implicitamente lì. ( dalla risposta di Norman: "normalmente queste lingue omettono il forall da tipi polimorfici" ). Questa affermazione è corretta, ma si riferisce agli altri usi foralle non ScopedTypeVariablesall'uso.

Rank-N-Tipi:

Cominciamo con quello mayb :: b -> (a -> b) -> Maybe a -> bequivale a mayb :: forall a b. b -> (a -> b) -> Maybe a -> b, tranne quandoScopedTypeVariables è abilitato.

Ciò significa che funziona per ogni ae b.

Diciamo che vuoi fare qualcosa del genere.

ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])

Quale deve essere il tipo di questo liftTup? Lo è liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b). Per capire perché, proviamo a codificarlo:

ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
    No instance for (Num [Char])
    ...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)

"Hmm .. perché GHC deduce che la tupla deve contenere due dello stesso tipo? Diciamo che non devono essere"

-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ghci> :l test.hs
    Couldnt match expected type 'x' against inferred type 'b'
    ...

Hmm. ecco GHC non ci permette di applicare liftFuncsulla vcausa v :: be liftFuncvuole una x. Vogliamo davvero che la nostra funzione ottenga una funzione che accetta ogni possibile x!

{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

Quindi non liftTupfunziona per tutti x, è la funzione che ottiene.

Quantificazione esistenziale:

Facciamo un esempio:

-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x

ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2

In che modo differisce dai gradi di tipo N?

ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
    Couldnt match expected type 'a' against inferred type '[Char]'
    ...

Con Rank-N-Tipi, forall asignifica che la tua espressione deve adattarsi a tutti i possibili as. Per esempio:

ghci> length ([] :: forall a. [a])
0

Un elenco vuoto funziona come un elenco di qualsiasi tipo.

Quindi, con Esistenziale-quantificazione, forallS datadefinizioni fanno sì che, il valore contenuto può essere di qualsiasi tipo adatto, non che deve essere di tutti i tipi adatti.


OK, ho ottenuto le mie sei ore e ora posso decodificare la tua risposta. :) Tra te e Norman ho avuto esattamente il tipo di risposta che stavo cercando. Grazie.
SOLO IL MIO OPINIONE corretta,

2
In realtà, fai ScopedTypeVariablessembrare peggio di quello che è. Se scrivi il tipo b -> (a -> b) -> Maybe a -> bcon questa estensione, sarà comunque esattamente equivalente a forall a b. b -> (a -> b) -> Maybe a -> b. Tuttavia, se si vuole fare riferimento alla stessa b (e non l'hanno implicitamente quantificata) , allora avete bisogno di scrivere la versione esplicitamente quantificato. Altrimenti, STVsarebbe un'estensione estremamente invadente.
nominolo

1
@nominolo: non intendevo sminuire ScopedTypeVariablese non penso che sia male. Imho, è uno strumento molto utile per il processo di programmazione, e in particolare per i principianti di Haskell, e sono grato che esista.
yairchu,

2
Questa è una domanda (e una risposta) piuttosto vecchia, ma potrebbe valere la pena aggiornarla per riflettere il fatto che i tipi esistenziali possono essere espressi usando i GADT in un modo che (almeno per me) rende la quantificazione molto più facile da capire.
dfeuer

1
Personalmente penso che sia più facile spiegare / comprendere la notazione esistenziale in termini di traduzione nel modulo GADT che da sola, ma sei sicuramente libero di pensare diversamente.
dfeuer

117

Qualcuno può spiegare completamente la parola chiave forall in un inglese semplice e chiaro?

No. (Beh, forse Don Stewart può.)

Ecco gli ostacoli a una spiegazione semplice e chiara o forall:

  • È un quantificatore. Devi avere almeno un po 'di logica (calcolo predicato) per aver visto un quantificatore universale o esistenziale. Se non hai mai visto il calcolo del predicato o non ti senti a tuo agio con i quantificatori (e ho visto gli studenti durante gli esami di dottorato che non sono a proprio agio), allora per te non c'è una spiegazione facile forall.

  • È un quantificatore di tipo . Se non hai visto il Sistema F e ti sei allenato a scrivere tipi polimorfici, troverai forallconfuso. L'esperienza con Haskell o ML non è sufficiente, perché normalmente queste lingue omettono i foralltipi polimorfici. (Nella mia mente, questo è un errore di progettazione del linguaggio.)

  • In Haskell in particolare, forallviene utilizzato in modi che trovo confusi. (Non sono un teorico dei tipi, ma il mio lavoro mi mette in contatto con molta teoria dei tipi e mi sento abbastanza a mio agio con esso.) Per me, la principale fonte di confusione è che forallviene utilizzata per codificare un tipo che Io stesso preferirei scrivere exists. È giustificato da un po 'di isomorfismo di tipo che coinvolge quantificatori e frecce, e ogni volta che voglio capirlo, devo cercare le cose e capire da solo l'isomorfismo.

    Se non ti senti a tuo agio con l'idea dell'isomorfismo di tipo, o se non hai alcuna pratica pensando agli isomorfismi di tipo, questo uso forallti ostacolerà.

  • Mentre il concetto generale di forallè sempre lo stesso (vincolante per introdurre una variabile di tipo), i dettagli di usi diversi possono variare in modo significativo. L'inglese informale non è un ottimo strumento per spiegare le variazioni. Per capire davvero cosa sta succedendo, hai bisogno di un po 'di matematica. In questo caso la matematica pertinente può essere trovata nel testo introduttivo Tipi e linguaggi di programmazione di Benjamin Pierce , che è un ottimo libro.

Per quanto riguarda i tuoi esempi particolari,

  • runST dovrebbe farti male alla testa. I tipi di rango superiore (sempre a sinistra di una freccia) si trovano raramente in natura. Vi incoraggio a leggere il documento che ha introdotto runST: "Lazy Functional State Threads" . Questo è davvero un buon documento e ti darà un'intuizione molto migliore per il tipo di runSTin particolare e per i tipi di rango superiore in generale. La spiegazione prende diverse pagine, è molto ben fatta e non proverò a condensarla qui.

  • Tener conto di

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))

    Se chiamo bar, posso semplicemente scegliere qualsiasi tipo ache mi piace e posso passare una funzione da tipo aa tipo a. Ad esempio, posso passare la funzione (+1)o la funzione reverse. Puoi pensare al foralldetto "Posso scegliere il tipo ora". (La parola tecnica per scegliere il tipo è un'istanza .)

    Le restrizioni alla chiamata foosono molto più rigorose: l'argomento foo deve essere una funzione polimorfica. Con quel tipo, le uniche funzioni a cui posso passare foosono ido una funzione che diverge sempre o errori, come undefined. Il motivo è che con foo, forallè alla sinistra della freccia, così come il chiamante di foonon riesco a scegliere ciò che aè - piuttosto è l' implementazione di fooquello che può scegliere ciò che aè. Perché forallè alla sinistra della freccia, piuttosto che sopra la freccia come in bar, l'istanza ha luogo nel corpo della funzione piuttosto che nel sito della chiamata.

Riepilogo: una spiegazione completa della forallparola chiave richiede matematica e può essere compresa solo da qualcuno che ha studiato la matematica. Anche le spiegazioni parziali sono difficili da capire senza matematica. Ma forse le mie spiegazioni parziali, non matematiche, aiutano un po '. Vai a leggere Launchbury e Peyton Jones su runST!


Addendum: gergo "sopra", "sotto", "a sinistra di". Questi non hanno nulla a che fare con le testuali modi tipi sono scritti e tutto a che fare con gli alberi astratto-sintassi. Nella sintassi astratta, a forallprende il nome di una variabile di tipo e quindi c'è un tipo completo "sotto" il forall. Una freccia accetta due tipi (argomento e tipo di risultato) e forma un nuovo tipo (il tipo di funzione). Il tipo di argomento è "a sinistra della" freccia; è il figlio sinistro della freccia nell'albero della sintassi astratta.

Esempi:

  • In forall a . [a] -> [a], il forall è sopra la freccia; ciò che è alla sinistra della freccia è [a].

  • Nel

    forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                  -> Block n e x -> f -> Fact x f

    il tipo tra parentesi si chiamerebbe "un forall alla sinistra di una freccia". (Sto usando tipi come questo in un ottimizzatore su cui sto lavorando.)


In realtà ho ottenuto il sopra / sotto / a sinistra di senza doverci pensare. Sono un ottuso, sì, ma un vecchio ottuso che ha dovuto combattere prima con quella roba. (Scrivere un compilatore ASN.1 tra gli altri .;) Grazie per l'addendum, però.
SOLO IL MIO OPINIONE corretta,

12
@SOLO grazie ma sto scrivendo per i posteri. Ho incontrato più di un programmatore che pensa che dentro forall a . [a] -> [a], il forall sia alla sinistra della freccia.
Norman Ramsey,

OK, esaminando la tua risposta in dettaglio, ora, devo ringraziarti, Norman, dal profondo del mio cuore. Molte cose sono andate a posto con un clic forte ora, e le cose che ancora non capisco riconosco almeno che non sono destinato a capirlo e passerò semplicemente forallin quelle circostanze come, effettivamente, linea rumore. Guarderò quel documento a cui hai collegato (grazie anche per il link!) E vedrò se è nel mio regno di comprensione. Complimenti.
SOLO IL MIO OPINIONE corretta,

10
Ho letto a sinistra e ho guardato, letteralmente, a sinistra. Quindi non mi è stato molto chiaro fino a quando non hai detto "albero di analisi".
Paul Nathan,

Grazie al puntatore al libro di Pierce. Ha una spiegazione molto chiara del Sistema F. Spiega perché existsnon è mai stato implementato. (Non fa parte del Sistema F!) In Haskell, parte del Sistema F è resa implicita, ma forallè una delle cose che non possono essere spazzate completamente sotto il tappeto. È come se iniziassero con Hindley-Milner, il che avrebbe permesso foralldi essere impliciti, e poi avrebbero optato per un sistema di tipo più potente, confondendo quelli di noi che studiavano il "forall" di FOL e "esiste" e si fermavano lì.
T_S_

50

La mia risposta originale:

Qualcuno può spiegare completamente la parola chiave forall in un inglese chiaro e semplice

Come indica Norman, è molto difficile dare una spiegazione inglese chiara e chiara di un termine tecnico dalla teoria dei tipi. Ci stiamo provando tutti comunque.

C'è solo una cosa da ricordare su 'forall': lega i tipi a un certo ambito . Una volta capito, tutto è abbastanza facile. È l'equivalente di "lambda" (o una forma di "let") a livello di tipo - Norman Ramsey usa la nozione di "sinistra" / "sopra" per trasmettere questo stesso concetto di scopo nella sua eccellente risposta .

La maggior parte degli usi di 'forall' sono molto semplici e li potete trovare introdotti nel Manuale dell'utente GHC, S7.8 ., In particolare l'eccellente S7.8.5 su forme annidate di 'forall'.

In Haskell, di solito lasciamo fuori il raccoglitore per i tipi, quando il tipo è universalmente quanitificato, in questo modo:

length :: forall a. [a] -> Int

è equivalente a:

length :: [a] -> Int

Questo è tutto.

Poiché ora puoi associare le variabili di tipo a un determinato ambito, puoi avere ambiti diversi dal livello superiore (" universalmente quantificato "), come il tuo primo esempio, in cui la variabile di tipo è visibile solo all'interno della struttura dei dati. Ciò consente tipi nascosti (" tipi esistenziali "). Oppure possiamo avere un nidificazione arbitraria di associazioni ("tipi di grado N").

Per comprendere a fondo i sistemi di tipi, dovrai imparare un po 'di gergo. Questa è la natura dell'informatica. Tuttavia, gli usi semplici, come sopra, dovrebbero poter essere colti in modo intuitivo, tramite analogia con "let" a livello di valore. Un'ottima introduzione è Launchbury e Peyton Jones .


5
tecnicamente, length :: forall a. [a] -> Intnon equivale a length :: [a] -> Intquando ScopedTypeVariablesè abilitato. Quando forall a.c'è, influenza lengthla whereclausola (se ne ha una) e cambia il significato delle variabili di tipo che vi sono indicate a.
yairchu,

2
Infatti. ScopedTypeVariables complica un po 'la storia.
Don Stewart,

3
@DonStewart, può "legare i tipi ad un certo ambito" meglio definito come "lega i tipi di variabili ad un certo ambito" nella tua spiegazione?
Romildo,

31

Sono densamente pieni di ipotesi secondo cui ho letto le ultime notizie su qualunque ramo della matematica discreta, teoria delle categorie o algebra astratta sia popolare questa settimana. (Se non leggo mai più le parole "consulta il documento qualunque per i dettagli dell'implementazione", sarà troppo presto.)

E che dire della semplice logica del primo ordine? forallè abbastanza chiaramente in riferimento alla quantificazione universale , e in quel contesto anche il termine esistenziale ha più senso, anche se sarebbe meno imbarazzante se ci fosse una existsparola chiave. Il fatto che la quantificazione sia effettivamente universale o esistenziale dipende dal posizionamento del quantificatore rispetto a dove vengono utilizzate le variabili su quale lato di una freccia di funzione ed è tutto un po 'confuso.

Quindi, se ciò non aiuta, o se semplicemente non ti piace la logica simbolica, da una prospettiva di programmazione più funzionale puoi pensare alle variabili di tipo semplicemente come parametri di tipo (impliciti) per la funzione. Le funzioni che assumono parametri di tipo in questo senso sono tradizionalmente scritte usando una lambda maiuscola per qualsiasi motivo, che scriverò qui come/\ .

Quindi, considera la idfunzione:

id :: forall a. a -> a
id x = x

Possiamo riscriverlo come lambdas, spostando il "parametro type" fuori dalla firma del tipo e aggiungendo annotazioni di tipo inline:

id = /\a -> (\x -> x) :: a -> a

Ecco la stessa cosa fatta per const:

const = /\a b -> (\x y -> x) :: a -> b -> a

Quindi la tua barfunzione potrebbe essere qualcosa del genere:

bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)

Si noti che il tipo della funzione assegnata barcome argomento dipende dal barparametro type. Considera se hai avuto qualcosa del genere invece:

bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)

Qui bar2sta applicando la funzione a qualcosa di tipo Char, quindi dando bar2qualsiasi parametro di tipo diverso daChar causerà un errore di tipo.

D'altra parte, ecco come foopotrebbe apparire:

foo = (\f -> (f Char 't', f Bool True))

Diversamente bar, in foorealtà non accetta alcun parametro di tipo! Prende una funzione che a sua volta accetta un parametro di tipo, quindi applica quella funzione a due diversi tipi.

Quindi, quando vedi una forallfirma in un tipo, pensala come un'espressione lambda per le firme dei tipi . Proprio come i normali lambda, l'ambito di estensione si forallestende il più possibile a destra, fino a racchiudere la parentesi, e proprio come le variabili legate in un normale lambda, le variabili di tipo legate da a forallsono solo nell'ambito nell'ambito dell'espressione quantificata.


Post scriptum : Forse potresti chiederti : ora che stiamo pensando alle funzioni che accettano parametri di tipo, perché non possiamo fare qualcosa di più interessante con quei parametri piuttosto che metterli in una firma di tipo? La risposta è che possiamo!

Una funzione che unisce variabili di tipo a un'etichetta e restituisce un nuovo tipo è un costruttore di tipi , che potresti scrivere in questo modo:

Either = /\a b -> ...

Ma avremmo bisogno di una notazione completamente nuova, perché il modo in cui un tale tipo è scritto, come Either a b, è già suggestivo di "applica la funzioneEither a questi parametri".

D'altra parte, una funzione che ha una sorta di "pattern match" sui suoi parametri di tipo, restituendo valori diversi per tipi diversi, è un metodo di una classe di tipo . Una leggera espansione della mia /\sintassi sopra suggerisce qualcosa del genere:

fmap = /\ f a b -> case f of
    Maybe -> (\g x -> case x of
        Just y -> Just b g y
        Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
    [] -> (\g x -> case x of
        (y:ys) -> g y : fmap [] a b g ys 
        []     -> [] b) :: (a -> b) -> [a] -> [b]

Personalmente, penso di preferire la sintassi reale di Haskell ...

Una funzione che "modella" i suoi parametri di tipo e restituisce un tipo arbitrario esistente è una famiglia di tipi o una dipendenza funzionale: nel primo caso, sembra già molto simile a una definizione di funzione.


1
Un'interessante interpretazione qui. Questo mi dà un altro angolo di attacco al problema che potrebbe rivelarsi fruttuoso a lungo termine. Grazie.
SOLO IL MIO OPINIONE corretta,

@KennyTM: O del resto λ, ma l'estensione della sintassi unicode di GHC non lo supporta perché λ è una lettera , un fatto sfortunato che si applicherebbe ipoteticamente anche alle mie ipotetiche astrazioni big-lambda. Quindi /\ per analogia a \ . Suppongo che avrei potuto usare solo ma stavo cercando di evitare il calcolo del predicato ...
CA McCann,

29

Ecco una spiegazione veloce e sporca in termini semplici che probabilmente hai già familiarità.

La forallparola chiave è davvero usata solo in un modo in Haskell. Significa sempre la stessa cosa quando la vedi.

Quantificazione universale

Un tipo universalmente quantificato è un tipo di modulo forall a. f a. Un valore di quel tipo può essere considerato come una funzione che accetta un tipo a come argomento e restituisce un valore di tipo f a. Tranne che in Haskell questi argomenti di tipo vengono passati implicitamente dal sistema di tipi. Questa "funzione" deve fornire lo stesso valore indipendentemente dal tipo che riceve, quindi il valore è polimorfico .

Ad esempio, considera il tipo forall a. [a]. Un valore di quel tipo accetta un altro tipo ae ti restituisce un elenco di elementi dello stesso tipo a. Esiste solo una possibile implementazione, ovviamente. Dovrebbe darti la lista vuota perché apotrebbe essere assolutamente di qualsiasi tipo. L'elenco vuoto è l'unico valore di elenco polimorfico nel suo tipo di elemento (poiché non ha elementi).

O il tipo forall a. a -> a. Il chiamante di tale funzione fornisce sia un tipo ache un valore di tipo a. L'implementazione deve quindi restituire un valore dello stesso tipo a. C'è solo una possibile implementazione di nuovo. Dovrebbe restituire lo stesso valore che è stato dato.

Quantificazione esistenziale

Un tipo quantificato esistenzialmente avrebbe la forma exists a. f a, se Haskell supportasse quella notazione. Un valore di quel tipo può essere pensato come una coppia (o un "prodotto") costituito da un tipo ae un valore di tipo f a.

Ad esempio, se hai un valore di tipo exists a. [a], hai un elenco di elementi di un certo tipo. Potrebbe essere di qualsiasi tipo, ma anche se non sai di cosa si tratta, potresti fare molto in un simile elenco. È possibile invertirlo, oppure è possibile contare il numero di elementi o eseguire qualsiasi altra operazione dell'elenco che non dipende dal tipo di elementi.

OK, quindi aspetta un minuto. Perché Haskell usa forallper indicare un tipo "esistenziale" come il seguente?

data ShowBox = forall s. Show s => SB s

Può essere fonte di confusione, ma sta davvero descrivendo il tipo di costruttore di dati SB :

SB :: forall s. Show s => s -> ShowBox

Una volta costruito, puoi pensare a un valore di tipo ShowBoxcome costituito da due cose. È un tipo sinsieme a un valore di tipo s. In altre parole, è un valore di un tipo esistenzialmente quantificato. ShowBoxpotrebbe davvero essere scritto come exists s. Show s => sse Haskell sostenesse quella notazione.

runST e amici

Detto questo, come sono diversi?

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

Per prima cosa prendiamo bar. Prende un tipo ae una funzione di tipo a -> ae produce un valore di tipo (Char, Bool). Potremmo scegliere Intcome ae dare ad esempio una funzione di tipo Int -> Int. Ma fooè diverso. Richiede che l'implementazione di foosia in grado di passare qualsiasi tipo voglia alla funzione che gli diamo. Quindi l'unica funzione che potremmo ragionevolmente dare è id.

Ora dovremmo essere in grado di affrontare il significato del tipo di runST:

runST :: forall a. (forall s. ST s a) -> a

Quindi runSTdeve essere in grado di produrre un valore di tipo a, indipendentemente dal tipo che diamo a. Per fare ciò, usa un argomento di tipo forall s. ST s ache certamente deve in qualche modo produrre il a. Inoltre, deve essere in grado di produrre un valore di tipo aindipendentemente dal tipo di implementazione che runSTdecide di assegnare s.

OK, e allora? Il vantaggio è che questo pone un limite al chiamante runSTin quanto il tipo anon può coinvolgere affatto il tipo s. Non puoi passargli un valore di tipo ST s [s], ad esempio. Ciò che ciò significa in pratica è che l'implementazione di runSTè gratuita per eseguire la mutazione con il valore del tipo s. Il tipo garantisce che questa mutazione sia locale per l'implementazione di runST.

Il tipo di runSTè un esempio di un tipo polimorfico di grado 2 perché il tipo del suo argomento contiene un forallquantificatore. Anche il tipo di cui foosopra è di rango 2. Un tipo polimorfico ordinario, come quello di bar, è di rango 1, ma diventa di rango 2 se i tipi di argomenti devono essere polimorfici, con il proprio forallquantificatore. E se una funzione accetta argomenti di rango 2, il suo tipo è rango di 3 e così via. In generale, un tipo che accetta argomenti polimorfici di rango nha rango n + 1.


11

Qualcuno può spiegare completamente la parola chiave forall in un inglese chiaro e semplice (o, se esiste da qualche parte, indicare una spiegazione così chiara che ho perso) che non presume che io sia un matematico intriso di gergo?

Proverò a spiegare solo il significato e forse l'applicazione forallnel contesto di Haskell e dei suoi sistemi di tipi.

Ma prima che tu capisca che vorrei indirizzarti a un discorso molto accessibile e piacevole di Runar Bjarnason intitolato " Vincoli liberati, vincoli delle libertà ". Il discorso è pieno di esempi tratti da casi di utilizzo nel mondo reale, nonché di esempi in Scala per supportare questa affermazione, anche se non menzionaforall . Proverò a spiegare la forallprospettiva di seguito.

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

È molto importante digerire e credere a questa affermazione per procedere con la seguente spiegazione, quindi ti esorto a guardare il discorso (almeno in parte).

Ora un esempio molto comune, che mostra l'espressività del sistema di tipi Haskell è questo tipo di firma:

foo :: a -> a

Si dice che, dato questo tipo di firma, esiste una sola funzione in grado di soddisfare questo tipo e quella è la identityfunzione o ciò che è più popolarmente conosciuto id.

Nelle fasi iniziali di apprendimento di Haskell, mi sono sempre chiesto le seguenti funzioni:

foo 5 = 6

foo True = False

entrambi soddisfano la firma del tipo sopra, quindi perché la gente di Haskell afferma che è idsolo quello che soddisfa la firma del tipo?

Questo perché c'è un implicito forallnascosto nella firma del tipo. Il tipo effettivo è:

id :: forall a. a -> a

Quindi, torniamo ora alla dichiarazione: vincoli liberano, le libertà vincolano

Traducendolo nel sistema dei tipi, questa affermazione diventa:

Un vincolo a livello di tipo, diventa una libertà a livello di termine

e

Una libertà a livello di tipo, diventa un vincolo a livello di termine


Proviamo a provare la prima affermazione:

Un vincolo a livello di tipo.

Quindi ponendo un vincolo alla nostra firma del tipo

foo :: (Num a) => a -> a

diventa una libertà a livello di termine ci dà la libertà o la flessibilità di scrivere tutto questo

foo 5 = 6
foo 4 = 2
foo 7 = 9
...

Lo stesso può essere osservato vincolando a con qualsiasi altra tabella dei tipi ecc

Quindi ora ciò che questo tipo di firma: si foo :: (Num a) => a -> atraduce in è:

a , st a -> a, a  Num

Questa è nota come quantificazione esistenziale, che traduce lì esistono alcuni casi aper i quali una funzione viene alimentata con qualcosa di tipoa rendimenti qualcosa dello stesso tipo, e quei casi appartengono tutti alla serie di numeri.

Quindi possiamo vedere l'aggiunta di un vincolo (che adovrebbe appartenere all'insieme dei Numeri), libera il termine livello per avere molteplici possibili implementazioni.


Veniamo ora alla seconda affermazione e a quella che in realtà porta la spiegazione di forall:

Una libertà a livello di tipo, diventa un vincolo a livello di termine

Quindi ora liberiamo la funzione a livello di tipo:

foo :: forall a. a -> a

Ora questo si traduce in:

a , a -> a

il che significa che l'implementazione di questo tipo di firma dovrebbe essere tale da essere a -> a per tutte le circostanze.

Quindi ora questo inizia a vincolarci a livello di termine. Non possiamo più scrivere

foo 5 = 7

perché questa implementazione non sarebbe soddisfacente se la mettessimo acome a Bool. apuò essere una Charo di una [Char]o di un tipo di dati personalizzato. In ogni caso dovrebbe restituire qualcosa del tipo simile. Questa libertà a livello di tipo è ciò che è noto come Quantificazione Universale e l'unica funzione che può soddisfare questo è

foo a = a

che è comunemente nota come identityfunzione


Quindi forallè un libertylivello di tipo, il cui scopo effettivo è quello di constraintermine a livello di una particolare implementazione.


9

Il motivo per cui esistono diversi usi di questa parola chiave è che viene effettivamente utilizzato in almeno due estensioni di sistema di tipo diverso: tipi di livello superiore ed esistenziali.

Probabilmente è meglio solo leggere e comprendere queste due cose separatamente, piuttosto che cercare di ottenere una spiegazione del perché 'forall' è un po 'di sintassi appropriata in entrambi allo stesso tempo.


3

Come è esistenziale esistenziale?

Con Existential-Quantification, foralls nelle datadefinizioni significa che, il valore contenuto può essere di qualsiasi tipo adatto, non che deve essere di tutti i tipi adatti. - la risposta di Yachiru

Una spiegazione del perché forallnelle datadefinizioni sono isomorfe a (exists a. a)(pseudo-Haskell) può essere trovata nei "tipi Haskell / esistenzialmente quantificati" dei wikibook .

Quello che segue è un breve riassunto testuale:

data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor

Quando si abbina / decostruisce il modello MkT x, di che tipo è x?

foo (MkT x) = ... -- -- what is the type of x?

xpuò essere di qualsiasi tipo (come indicato in forall), quindi il tipo è:

x :: exists a. a -- (pseudo-Haskell)

Pertanto, i seguenti sono isomorfi:

data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)

forall significa forall

La mia semplice interpretazione di tutto ciò è che " forallsignifica davvero" per tutti ". Una distinzione importante da fare è l'impatto forallsulla definizione rispetto all'applicazione della funzione .

A forallsignifica la definizione del valore o della funzione deve essere polimorfica.

Se la cosa definita è un valore polimorfico , significa che il valore deve essere valido per tutti i casi a, il che è abbastanza restrittivo.

Se la cosa definita è una funzione polimorfica , allora significa che la funzione deve essere valida per tutti a, il che non è così restrittivo perché solo perché la funzione è polimorfica non significa che il parametro applicato deve essere polimorfico. Cioè, se la funzione è valida per tutti a, al contrario si può applicare qualsiasi funzione adatta alla funzione. Tuttavia, il tipo di parametro può essere scelto solo una volta nella definizione della funzione.a

Se a forallè all'interno del tipo di parametro della funzione (ovvero, a Rank2Type), significa che il parametro applicato deve essere veramente polimorfico, per essere coerente con l'idea della definizione dei forallmezzi è polimorfico. In questo caso, il tipo di parametro può essere scelto più di una volta nella definizione della funzione ( "ed è scelto dall'implementazione della funzione", come sottolineato da Norman )

Pertanto, il motivo per cui esistenziali datadefinizioni permette qualsiasi a è perché il costruttore dei dati è un polimorfico funzione di :

MkT :: forall a. a -> T

tipo di MkT :: a -> *

Ciò significa che qualsiasi apuò essere applicato alla funzione. Al contrario, diciamo, di un valore polimorfico :

valueT :: forall a. [a]

tipo di valore T :: a

Ciò significa che la definizione di valueT deve essere polimorfica. In questo caso, valueTpuò essere definito come un elenco vuoto []di tutti i tipi.

[] :: [t]

differenze

Anche se il significato di forallè coerente in ExistentialQuantificatione RankNType, gli esistenziali hanno una differenza poiché il datacostruttore può essere utilizzato nella corrispondenza dei modelli. Come documentato nella guida per l'utente di ghc :

Durante la corrispondenza dei modelli, ogni corrispondenza dei modelli introduce un nuovo tipo distinto per ogni variabile di tipo esistenziale. Questi tipi non possono essere unificati con nessun altro tipo, né possono sfuggire all'ambito della corrispondenza del modello.

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.