Was macht das Schlüsselwort "forall" in Haskell / GHC?

312

Ich fange an zu verstehen, wie das forallSchlüsselwort in sogenannten "existentiellen Typen" wie diesem verwendet wird:

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

Dies ist jedoch nur eine Teilmenge der forallVerwendung, und ich kann mich einfach nicht auf die Verwendung in solchen Dingen konzentrieren:

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

Oder erklären, warum diese unterschiedlich sind:

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

Oder das ganze RankNTypesZeug ...

Ich bevorzuge klares, jargonfreies Englisch gegenüber den im akademischen Umfeld normalen Sprachen. Die meisten Erklärungen, die ich dazu zu lesen versuche (diejenigen, die ich über Suchmaschinen finden kann), haben folgende Probleme:

  1. Sie sind unvollständig. Sie erklären , einen Teil der Verwendung dieses Schlüsselwort (wie „existentielle Typen“) , die mich glücklich fühlen , bis ich Code gelesen , dass Verwendungen es in eine ganz andere Art und Weise (wie runST, foound baroben).
  2. Sie sind vollgepackt mit Annahmen, dass ich die neuesten Informationen zu den Bereichen diskreter Mathematik, Kategorietheorie oder abstrakter Algebra gelesen habe, die diese Woche beliebt sind. (Wenn ich nie die Worte lesen „konsultieren Sie das Papier , was für Einzelheiten der Umsetzung“ wieder, wird es zu früh.)
  3. Sie sind so geschrieben, dass selbst einfache Konzepte häufig in gewundene und gebrochene Grammatik und Semantik umgewandelt werden.

So...

Weiter zur eigentlichen Frage. Kann jemand das forallSchlüsselwort vollständig in klarem, einfachem Englisch erklären (oder, falls es irgendwo existiert, auf eine so klare Erklärung verweisen, die ich verpasst habe), die nicht davon ausgeht, dass ich ein im Fachjargon versierter Mathematiker bin?


Bearbeitet, um hinzuzufügen:

Es gab zwei herausragende Antworten von den höherwertigen unten, aber leider kann ich nur eine als die beste auswählen. Normans Antwort war detailliert und nützlich und erklärte die Dinge auf eine Weise, die einige der theoretischen Grundlagen forallzeigte und mir gleichzeitig einige der praktischen Implikationen davon zeigte. Yairchus Antwortdeckte einen Bereich ab, den sonst niemand erwähnte (Variablen vom Typ Scoped) und illustrierte alle Konzepte mit Code und einer GHCi-Sitzung. Wäre es möglich, beide als am besten auszuwählen, würde ich. Leider kann ich nicht und nachdem ich beide Antworten genau durchgesehen habe, habe ich beschlossen, dass Yairchus aufgrund des illustrativen Codes und der beigefügten Erklärung die von Norman leicht übertrifft. Dies ist jedoch etwas unfair, da ich wirklich beide Antworten brauchte, um dies bis zu einem Punkt zu verstehen, an dem forallich kein leichtes Gefühl der Angst habe, wenn ich es in einer Typensignatur sehe.

NUR MEINE RICHTIGE MEINUNG
quelle
7
Das Haskell-Wiki scheint zu diesem Thema ziemlich anfängerfreundlich zu sein.
Jhegedus

Antworten:

263

Beginnen wir mit einem Codebeispiel:

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

Dieser Code wird in einfachem Haskell 98 nicht kompiliert (Syntaxfehler). Zur Unterstützung des forallSchlüsselworts ist eine Erweiterung erforderlich .

Grundsätzlich gibt es 3 verschiedene gemeinsame Verwendungen für das forallSchlüsselwort (oder zumindest so dass es scheint ), und jeder hat seine eigene Haskell extension: ScopedTypeVariables, RankNTypes/ Rank2Types, ExistentialQuantification.

Der obige Code erhält bei beiden aktivierten keinen Syntaxfehler, sondern nur Typprüfungen bei ScopedTypeVariablesaktivierten.

Gültigkeitsbereichstypvariablen:

Typvariablen mit Gültigkeitsbereich helfen dabei, Typen für Code in whereKlauseln anzugeben . Es macht das bim val :: bselben wie das bin foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b.

Ein verwirrender Punkt : Sie können hören, dass wenn Sie das forallvon einem Typ weglassen, es tatsächlich immer noch implizit vorhanden ist. ( aus Normans Antwort: "Normalerweise lassen diese Sprachen das Forall von polymorphen Typen weg" ). Diese Behauptung ist richtig, aber es bezieht sich auf die anderen Verwendungen von forall, und nicht auf die ScopedTypeVariablesVerwendung.

Rang-N-Typen:

Beginnen wir damit, mayb :: b -> (a -> b) -> Maybe a -> bwas äquivalent zu ist mayb :: forall a b. b -> (a -> b) -> Maybe a -> b, außer wenn ScopedTypeVariablesaktiviert ist.

Dies bedeutet, dass es für alle aund funktioniert b.

Angenommen, Sie möchten so etwas tun.

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

Was muss die Art davon sein liftTup? Es ist liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b). Um zu sehen warum, versuchen wir es zu codieren:

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 ... warum schließt GHC, dass das Tupel zwei des gleichen Typs enthalten muss? Sagen wir mal, dass sie es nicht sein müssen."

-- 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. so hier nicht GHC lassen Sie uns nicht anwenden liftFuncauf , vweil v :: bund liftFunceine will x. Wir möchten wirklich, dass unsere Funktion eine Funktion erhält, die alles Mögliche akzeptiert x!

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

Es funktioniert also nicht liftTupfür alle x, sondern für die Funktion, die es erhält.

Existenzielle Quantifizierung:

Verwenden wir ein Beispiel:

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

Wie unterscheidet sich das von Rang-N-Typen?

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

Mit Rang-N-Typen forall abedeutet dies, dass Ihr Ausdruck zu allen möglichen as passen muss . Zum Beispiel:

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

Eine leere Liste funktioniert als Liste eines beliebigen Typs.

Also mit Existentielle-Quantifizierung, foralls in dataDefinitionen das bedeuten, der Wert enthalten kann der sein , jede geeigneten Art, nicht , dass es muss von seinen allen geeigneten Typen.

Yairchu
quelle
OK, ich habe meine sechs Stunden und kann jetzt Ihre Antwort entschlüsseln. :) Zwischen dir und Norman habe ich genau die Antwort bekommen, nach der ich gesucht habe. Vielen Dank.
NUR MEINE korrekte MEINUNG
2
Eigentlich lässt du es ScopedTypeVariablesschlimmer erscheinen als es ist. Wenn Sie den Typ b -> (a -> b) -> Maybe a -> bmit dieser Erweiterung schreiben , entspricht er immer noch genau dem forall a b. b -> (a -> b) -> Maybe a -> b. Wenn Sie jedoch beziehen mögen das gleiche b (und hat es nicht implizit quantifiziert) dann müssen Sie die explizit quantifizierte Version schreiben. Andernfalls STVwäre eine äußerst aufdringliche Erweiterung.
Nominolo
1
@nominolo: Ich wollte nicht erniedrigen ScopedTypeVariablesund ich denke nicht, dass es schlecht ist. Imho, es ist ein sehr hilfreiches Werkzeug für den Programmierprozess und insbesondere für Haskell-Anfänger, und ich bin dankbar, dass es existiert.
Yairchu
2
Dies ist eine ziemlich alte Frage (und Antwort), aber es könnte sich lohnen, sie zu aktualisieren, um der Tatsache Rechnung zu tragen, dass existenzielle Typen mithilfe von GADTs so ausgedrückt werden können, dass (zumindest für mich) die Quantifizierung viel einfacher zu verstehen ist.
dfeuer
1
Ich persönlich denke, es ist einfacher, die existenzielle Notation in Bezug auf ihre Übersetzung in die GADT-Form zu erklären / zu verstehen als allein, aber Sie können sicherlich anders denken.
Feuer
117

Kann jemand das Schlüsselwort forall vollständig in klarem, einfachem Englisch erklären?

Nein. (Vielleicht kann Don Stewart das.)

Hier sind die Hindernisse für eine einfache, klare Erklärung oder forall:

  • Es ist ein Quantifizierer. Sie müssen mindestens eine kleine Logik (Prädikatenrechnung) haben, um einen universellen oder existenziellen Quantifizierer zu sehen. Wenn Sie noch nie einen Prädikatenkalkül gesehen haben oder mit Quantifizierern nicht vertraut sind (und ich habe Studenten während der Doktorandenprüfungen gesehen, die sich nicht wohl fühlen), dann gibt es für Sie keine einfache Erklärung dafür forall.

  • Es ist eine Art quantifier. Wenn Sie System F noch nicht gesehen und einige Übungen zum Schreiben polymorpher Typen erhalten haben, werden Sie forallverwirrend sein. Erfahrung mit Haskell oder ML reicht nicht aus, da diese Sprachen normalerweise die forallpolymorphen Typen weglassen . (In meinen Augen ist dies ein Fehler beim Sprachdesign.)

  • Insbesondere in Haskell forallwird es auf eine Weise verwendet, die ich verwirrend finde. (Ich bin kein Typentheoretiker, aber meine Arbeit bringt mich mit viel Typentheorie in Kontakt , und ich bin damit ziemlich vertraut.) Für mich ist die Hauptursache für Verwirrung, dass forallein Typ verwendet wird, der diesen Typ codiert Ich selbst würde lieber mit schreiben exists. Es ist gerechtfertigt durch ein kniffliges Stück Typisomorphismus mit Quantifizierern und Pfeilen, und jedes Mal, wenn ich es verstehen will, muss ich nachschlagen und den Isomorphismus selbst herausarbeiten.

    Wenn Sie mit der Idee des forallTypisomorphismus nicht vertraut sind oder wenn Sie keine Übung haben, über Typisomorphismen nachzudenken, wird diese Verwendung Sie behindern.

  • Während das allgemeine Konzept von forallimmer dasselbe ist (Bindung zur Einführung einer Typvariablen), können die Details der verschiedenen Verwendungen erheblich variieren. Informelles Englisch ist kein sehr gutes Werkzeug, um die Variationen zu erklären. Um wirklich zu verstehen, was los ist, braucht man etwas Mathematik. In diesem Fall finden Sie die relevante Mathematik in Benjamin Pierces Einführungstext Typen und Programmiersprachen , der ein sehr gutes Buch ist.

Was Ihre speziellen Beispiele betrifft,

  • runST sollte deinen Kopf verletzen. Höherrangige Typen (ganz links von einem Pfeil) kommen in freier Wildbahn selten vor. Ich ermutige Sie, das folgende Papier zu lesen runST: "Lazy Functional State Threads" . Dies ist ein wirklich gutes Papier, und es gibt Ihnen eine viel bessere Intuition für den Typ runSTim Besonderen und für höherrangige Typen im Allgemeinen. Die Erklärung dauert mehrere Seiten, ist sehr gut gemacht und ich werde hier nicht versuchen, sie zu verdichten.

  • Erwägen

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

    Wenn ich anrufe bar, kann ich einfach einen beliebigen Typ auswählen aund ihm eine Funktion von Typ azu Typ übergeben a. Zum Beispiel kann ich die Funktion (+1)oder die Funktion übergeben reverse. Sie können sich das forallals "Ich darf jetzt den Typ auswählen" vorstellen. (Das technische Wort für die Auswahl des Typs ist instanziierend .)

    Die Einschränkungen beim Aufrufen foosind viel strenger: Das Argument foo muss eine polymorphe Funktion sein. Mit dieser Art kann die einzigen Funktionen , die ich passieren zu foowerden idoder eine Funktion, die immer divergiert oder Fehler, wie undefined. Der Grund dafür ist , dass mit fooder forallist auf der linken Seite des Pfeils, so wie der Aufrufer fooich zu nicht holen , was aes-ist vielmehr ist die Umsetzung der , foodass zu holen bekommt , was aist. Da forallsich bardie Instanziierung links vom Pfeil und nicht wie in über dem Pfeil befindet , erfolgt sie im Hauptteil der Funktion und nicht an der Anrufstelle.

Zusammenfassung: Eine vollständige Erklärung des forallSchlüsselworts erfordert Mathematik und kann nur von jemandem verstanden werden, der die Mathematik studiert hat. Selbst teilweise Erklärungen sind ohne Mathematik schwer zu verstehen. Aber vielleicht helfen meine teilweisen, nicht mathematischen Erklärungen ein wenig. Lesen Sie Launchbury und Peyton Jones weiter runST!


Nachtrag: Jargon "oben", "unten", "links von". Diese haben nichts mit der Art und Weise zu tun, wie Typen in Textform geschrieben werden, und alles, was mit Bäumen mit abstrakter Syntax zu tun hat. In der abstrakten Syntax forallnimmt a den Namen einer Typvariablen an, und dann gibt es einen vollständigen Typ "unter" dem Forall. Ein Pfeil nimmt zwei Typen (Argument und Ergebnistyp) und bildet einen neuen Typ (den Funktionstyp). Der Argumenttyp ist "links vom" Pfeil. Es ist das linke Kind des Pfeils im abstrakten Syntaxbaum.

Beispiele:

  • In forall a . [a] -> [a]befindet sich der Forall über dem Pfeil. Was links vom Pfeil ist, ist [a].

  • Im

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

    Der Typ in Klammern wird als "forall links von einem Pfeil" bezeichnet. (Ich verwende solche Typen in einem Optimierer, an dem ich arbeite.)

Norman Ramsey
quelle
Eigentlich habe ich das oben / unten / links von bekommen, ohne darüber nachdenken zu müssen. Ich bin ein Trottel, ja, aber ein alter Trottel, der schon einmal mit diesem Zeug ringen musste. (Schreiben eines ASN.1-Compilers unter anderem.) Vielen Dank für den Nachtrag.
NUR MEINE RICHTIGE MEINUNG
12
@NUR danke, aber ich schreibe für die Nachwelt. Ich habe mehr als einen Programmierer getroffen, der denkt, dass sich forall a . [a] -> [a]der Forall links vom Pfeil befindet.
Norman Ramsey
OK, ich gehe Ihre Antwort im Detail durch, jetzt muss ich Ihnen, Norman, von ganzem Herzen danken. Viele Sachen hat mit einem lauten jetzt in Platz gefallen klicken, und die Sachen , die ich , dass ich immer noch nicht verstehen , zumindest erkennen , dass ich nicht gemeint , es zu verstehen und wird nur übergehen forallunter diesen Umständen als effektiv, Linie Lärm. Ich werde mir das Papier ansehen, auf das Sie verlinkt haben (danke auch für den Link!) Und sehen, ob es in meinem Bereich des Verständnisses liegt. Ein großes Lob.
NUR MEINE RICHTIGE MEINUNG
10
Ich las links und schaute buchstäblich nach links. Es war mir also sehr unklar, bis Sie "Analysebaum" sagten.
Paul Nathan
Dank des Zeigers auf Pierces Buch. Es hat eine sehr klare Erklärung für System F. Es erklärt, warum existses nie implementiert wurde. (Es ist nicht Teil von System F!) In Haskell wird ein Teil von System F implizit gemacht, ist aber foralleines der Dinge, die nicht vollständig unter den Teppich gekehrt werden können. Es ist, als ob sie mit Hindley-Milner angefangen hätten, was forallimplizit gemacht werden könnte, und sich dann für ein leistungsfähigeres Typsystem entschieden hätten, was diejenigen von uns verwirrte, die FOLs "forall" und "exist" studierten und dort anhielten.
T_S_
50

Meine ursprüngliche Antwort:

Kann jemand das Schlüsselwort forall vollständig in klarem, einfachem Englisch erklären?

Wie Norman angibt, ist es sehr schwierig, eine klare englische Erklärung eines Fachbegriffs aus der Typentheorie zu geben. Wir versuchen es alle.

Bei 'forall' ist nur eines wirklich zu beachten: Es bindet Typen an einen bestimmten Bereich . Sobald Sie das verstanden haben, ist alles ziemlich einfach. Es ist das Äquivalent von 'Lambda' (oder einer Form von 'Let') auf Typebene - Norman Ramsey verwendet den Begriff "links" / "oben", um dasselbe Konzept des Geltungsbereichs in seiner ausgezeichneten Antwort zu vermitteln .

Die meisten Anwendungen von 'forall' sind sehr einfach, und Sie finden sie im GHC-Benutzerhandbuch, S7.8 ., Insbesondere das ausgezeichnete S7.8.5 für verschachtelte Formen von 'forall'.

In Haskell lassen wir das Bindemittel normalerweise für Typen weg, wenn der Typ universell quanitifiziert ist, wie folgt:

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

ist äquivalent zu:

length :: [a] -> Int

Das ist es.

Da Sie jetzt Typvariablen an einen bestimmten Bereich binden können, können Sie andere Bereiche als die oberste Ebene haben (" universell quantifiziert" ") haben, wie in Ihrem ersten Beispiel, in dem die Typvariable nur innerhalb der Datenstruktur sichtbar ist. Dies ermöglicht versteckte Typen (" existenzielle Typen "). Oder wir können willkürliche Verschachtelungen von Bindungen haben ("Rang N-Typen").

Um Typensysteme genau zu verstehen, müssen Sie etwas Jargon lernen. Das ist die Natur der Informatik. Einfache Verwendungen wie oben sollten jedoch in Analogie zu 'let' auf der Wertebene intuitiv erfasst werden können. Eine gute Einführung ist Launchbury und Peyton Jones .

Don Stewart
quelle
4
technisch length :: forall a. [a] -> Intist nicht gleichbedeutend mit length :: [a] -> Intwann ScopedTypeVariablesaktiviert ist. Wenn das vorhanden forall a.ist, wirkt es sich auf lengthdie whereKlausel aus (falls vorhanden) und ändert die Bedeutung der darin genannten Typvariablen a.
Yairchu
2
Tatsächlich. ScopedTypeVariables erschweren die Geschichte ein wenig.
Don Stewart
3
@DonStewart, kann "es bindet Typen an einen bestimmten Bereich" in Ihrer Erklärung besser formuliert als "es bindet Typvariablen an einen bestimmten Bereich"?
Romildo
31

Sie sind vollgepackt mit Annahmen, dass ich die neuesten Informationen zu den Bereichen diskreter Mathematik, Kategorietheorie oder abstrakter Algebra gelesen habe, die diese Woche beliebt sind. (Wenn ich die Worte "Konsultieren Sie das Papier, um Einzelheiten zur Implementierung zu erfahren" nie wieder lese, ist es zu früh.)

Äh, und was ist mit einfacher Logik erster Ordnung? forallist ziemlich klar in Bezug auf die universelle Quantifizierung , und in diesem Zusammenhang ist der Begriff existenziell auch sinnvoller, obwohl es weniger umständlich wäre, wenn es ein existsSchlüsselwort gäbe . Ob die Quantifizierung effektiv universell oder existenziell ist, hängt von der Platzierung des Quantifizierers in Bezug darauf ab, wo die Variablen auf welcher Seite eines Funktionspfeils verwendet werden, und alles ist etwas verwirrend.

Also, wenn das nicht hilft, oder wenn Sie einfach nicht wie symbolische Logik, von einer funktionalen Programmierung-ish Perspektive Sie von Typ - Variablen als nur sein (impliziten) denken können , Typ Parameter an die Funktion. Funktionen, die Typparameter in diesem Sinne verwenden, werden traditionell aus irgendeinem Grund mit einem Großbuchstaben geschrieben, als das ich hier schreiben werde/\ .

Betrachten Sie also die idFunktion:

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

Wir können es als Lambdas umschreiben, indem wir den "Typparameter" aus der Typensignatur verschieben und Inline-Typanmerkungen hinzufügen:

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

Hier ist das Gleiche wie const:

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

Ihre barFunktion könnte also ungefähr so ​​aussehen:

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

Beachten Sie, dass der Typ der Funktion, die barals Argument angegeben wird, vom barTypparameter abhängt . Überlegen Sie, ob Sie stattdessen so etwas hatten:

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

Hier bar2wird die Funktion auf etwas vom Typ angewendet Char, sodass die Angabe bar2eines anderen Typparameters als Chareinen Typfehler verursacht.

Auf der anderen Seite fookönnte Folgendes aussehen:

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

Im Gegensatz zu bar, foonehmen nicht wirklich alle Parameter des Typs überhaupt! Es wird eine Funktion verwendet, die selbst einen Typparameter verwendet, und diese Funktion wird dann auf zwei verschiedene Typen angewendet.

Wenn Sie also eine forallTypensignatur sehen, stellen Sie sie sich einfach als Lambda-Ausdruck für Typensignaturen vor . Genau wie bei regulären Lambdas forallerstreckt sich der Geltungsbereich von so weit wie möglich nach rechts, bis hin zur Klammer, und genau wie bei Variablen, die in einem regulären Lambda gebunden forallsind , sind die durch a gebundenen Typvariablen nur innerhalb des quantifizierten Ausdrucks im Geltungsbereich.


Post scriptum : Vielleicht fragen Sie sich - jetzt, wo wir über Funktionen nachdenken, die Typparameter verwenden, warum können wir mit diesen Parametern nichts Interessanteres tun, als sie in eine Typensignatur einzufügen? Die Antwort ist, dass wir können!

Eine Funktion, die Typvariablen mit einer Bezeichnung zusammenfügt und einen neuen Typ zurückgibt, ist ein Typkonstruktor , den Sie wie folgt schreiben können:

Either = /\a b -> ...

Aber wir würden eine völlig neue Notation brauchen, da die Art und Weise, wie ein solcher Typ geschrieben wird Either a b, bereits darauf hindeutet, "die Funktion anzuwenden"Either auf diese Parameter ".

Andererseits ist eine Funktion, die in ihren Typparametern eine Art "Musterübereinstimmung" aufweist und unterschiedliche Werte für unterschiedliche Typen zurückgibt, eine Methode einer Typklasse . Eine leichte Erweiterung meiner /\obigen Syntax deutet auf Folgendes hin:

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]

Persönlich denke ich, ich bevorzuge Haskells tatsächliche Syntax ...

Eine Funktion , die „-Muster matches“ seine Art Parameter und gibt einen beliebigen Typ existiert , ist eine Art Familie oder funktionale Abhängigkeit --in ersteren Fall ist es sogar sieht schon viel wie eine Funktionsdefinition.

CA McCann
quelle
1
Eine interessante Einstellung hier. Dies gibt mir einen weiteren Angriffswinkel auf das Problem, der sich langfristig als fruchtbar erweisen könnte. Vielen Dank.
NUR MEINE RICHTIGE MEINUNG
@KennyTM: Oder im Übrigen λ, aber die Unicode-Syntaxerweiterung von GHC unterstützt dies nicht, da λ ein Buchstabe ist , eine unglückliche Tatsache, die hypothetisch auch für meine hypothetischen Big-Lambda-Abstraktionen gelten würde. Daher /\ in Analogie zu \ . Ich nehme an, ich hätte es einfach benutzen können, aber ich habe versucht, Prädikatenrechnung zu vermeiden ...
CA McCann
29

Hier ist eine schnelle und schmutzige Erklärung in einfachen Worten, mit der Sie wahrscheinlich bereits vertraut sind.

Das forallSchlüsselwort wird in Haskell wirklich nur auf eine Weise verwendet. Es bedeutet immer dasselbe, wenn Sie es sehen.

Universelle Quantifizierung

Ein universell quantifizierter Typ ist ein Typ der Form forall a. f a. Ein Wert dieses Typs kann als eine Funktion betrachtet werden , die einen Typ a als Argument verwendet und einen Wert vom Typ zurückgibt f a. Außer dass diese Typargumente in Haskell implizit vom Typsystem übergeben werden. Diese "Funktion" muss Ihnen unabhängig vom Typ den gleichen Wert geben, daher ist der Wert polymorph .

Betrachten Sie zum Beispiel den Typ forall a. [a]. Ein Wert dieses Typs nimmt einen anderen Typ an aund gibt Ihnen eine Liste von Elementen desselben Typs zurück a. Natürlich gibt es nur eine mögliche Implementierung. Es müsste Ihnen die leere Liste geben, da es asich um absolut jeden Typ handeln könnte. Die leere Liste ist der einzige Listenwert, der in seinem Elementtyp polymorph ist (da er keine Elemente enthält).

Oder der Typ forall a. a -> a. Der Aufrufer einer solchen Funktion gibt sowohl einen Typ aals auch einen Wert vom Typ an a. Die Implementierung muss dann einen Wert desselben Typs zurückgeben a. Es gibt wieder nur eine mögliche Implementierung. Es müsste den gleichen Wert zurückgeben, den es erhalten hat.

Existenzielle Quantifizierung

Ein existenziell quantifizierter Typ hätte die Form exists a. f a, wenn Haskell diese Notation unterstützen würde. Ein Wert dieses Typs kann als ein Paar (oder ein "Produkt") betrachtet werden, das aus einem Typ aund einem Wert des Typs besteht f a.

Wenn Sie beispielsweise einen Wert vom Typ haben exists a. [a], haben Sie eine Liste von Elementen eines Typs. Es könnte jeder Typ sein, aber selbst wenn Sie nicht wissen, was es ist, können Sie mit einer solchen Liste viel anfangen. Sie können es umkehren oder die Anzahl der Elemente zählen oder eine andere Listenoperation ausführen, die nicht vom Typ der Elemente abhängt.

OK, also warte eine Minute. Warum bezeichnet Haskell foralleinen "existenziellen" Typ wie den folgenden?

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

Es kann verwirrend sein, aber es beschreibt wirklich den Typ des Datenkonstruktors SB :

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

Einmal konstruiert, können Sie sich einen Wert vom Typ ShowBoxvorstellen, der aus zwei Dingen besteht. Es ist ein Typ szusammen mit einem Wert von Typ s. Mit anderen Worten, es ist ein Wert eines existenziell quantifizierten Typs. ShowBoxkönnte wirklich so geschrieben werden, als exists s. Show s => sob Haskell diese Notation unterstützt.

runST und Freunde

Wie unterscheiden sich diese?

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

Nehmen wir zuerst bar. Es nimmt einen Typ aund eine Funktion des Typs a -> aan und erzeugt einen Wert des Typs (Char, Bool). Wir konnten wählen , Intals das aund geben ihm eine Funktion vom Typ Int -> Intzum Beispiel. Ist fooaber anders. Es erfordert, dass die Implementierung in der fooLage ist, jeden gewünschten Typ an die von uns zugewiesene Funktion zu übergeben. Die einzige Funktion, die wir vernünftigerweise geben könnten, ist id.

Wir sollten jetzt in der Lage sein, die Bedeutung der Art von runST:

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

Muss runSTalso in der Lage sein, einen Wert vom Typ zu erzeugen a, egal welchen Typ wir geben a. Dazu wird ein Argument vom Typ verwendet, forall s. ST s adas sicherlich irgendwie das erzeugen muss a. Darüber hinaus muss es in der Lage sein, einen Wert vom Typ zu erzeugen, aunabhängig davon, für welchen Typ sich die Implementierung runSTentscheidet s.

In Ordnung und jetzt? Der Vorteil besteht darin, dass dies den Aufrufer dahingehend einschränkt, runSTdass der Typ den Typ aüberhaupt nicht einbeziehen kann s. Sie können ihm beispielsweise keinen Wert vom Typ übergeben ST s [s]. In der Praxis bedeutet dies, dass die Implementierung von runSTfrei ist, eine Mutation mit dem Wert des Typs durchzuführen s. Der Typ garantiert, dass diese Mutation lokal für die Implementierung von ist runST.

Der Typ von runSTist ein Beispiel für einen polymorphen Typ vom Rang 2, da der Typ seines Arguments einen forallQuantifizierer enthält . Der fooobige Typ hat ebenfalls Rang 2. Ein gewöhnlicher polymorpher Typ wie der von barist Rang 1, wird jedoch Rang 2, wenn die Argumenttypen polymorph sein müssen, mit einem eigenen forallQuantifizierer. Und wenn eine Funktion Rang-2-Argumente akzeptiert, ist ihr Typ Rang-3 und so weiter. Im Allgemeinen hat ein Typ, der polymorphe Argumente mit Rang verwendet, nRang n + 1.

Apocalisp
quelle
11

Kann jemand das Schlüsselwort forall vollständig in klarem, einfachem Englisch erklären (oder, falls es irgendwo existiert, auf eine so klare Erklärung verweisen, die ich verpasst habe), die nicht davon ausgeht, dass ich ein im Fachjargon versierter Mathematiker bin?

Ich werde versuchen, nur die Bedeutung und vielleicht die Anwendung forallvon Haskell und seinen Typsystemen zu erklären .

Aber bevor Sie verstehen, dass ich Sie zu einem sehr zugänglichen und netten Vortrag von Runar Bjarnason mit dem Titel " Constraints Liberate, Liberties Constrain " führen möchte . Der Vortrag ist voll von Beispielen aus realen Anwendungsfällen sowie Beispielen in Scala, um diese Aussage zu unterstützen, obwohl sie nicht erwähnt wird forall. Ich werde versuchen, die forallPerspektive unten zu erklären .

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

Es ist sehr wichtig, diese Aussage zu verdauen und zu glauben, um mit der folgenden Erklärung fortzufahren. Ich fordere Sie daher dringend auf, das Gespräch (zumindest Teile davon) zu verfolgen.

Ein sehr verbreitetes Beispiel, das die Ausdruckskraft des Haskell-Typsystems zeigt, ist diese Typensignatur:

foo :: a -> a

Es wird gesagt, dass es bei dieser Typensignatur nur eine Funktion gibt, die diesen Typ erfüllen kann, und das ist die identityFunktion oder was im Volksmund bekannt ist id.

In den Anfangsphasen, in denen ich Haskell lernte, habe ich mich immer gefragt, welche Funktionen es gibt:

foo 5 = 6

foo True = False

beide erfüllen die oben genannte Typensignatur. Warum behaupten dann die Haskell-Leute, dass idnur die Typensignatur erfüllt ist?

Dies liegt daran, dass forallin der Typensignatur ein implizites Element versteckt ist. Der tatsächliche Typ ist:

id :: forall a. a -> a

Kommen wir nun zu der Aussage zurück: Einschränkungen befreien sich, Freiheiten beschränken sich

Wenn Sie dies in das Typsystem übersetzen, lautet diese Anweisung:

Eine Einschränkung auf Typebene wird auf der Termebene zur Freiheit

und

Eine Freiheit auf Typebene wird zu einer Einschränkung auf Termebene


Versuchen wir, die erste Aussage zu beweisen:

Eine Einschränkung auf Typebene.

Setzen Sie also eine Einschränkung für unsere Typensignatur

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

wird eine Freiheit auf der Begriffsebene gibt uns die Freiheit oder Flexibilität, all dies zu schreiben

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

Gleiches kann beobachtet werden, indem amit jeder anderen Typklasse usw. Eingeschränkt wird

Nun bedeutet diese Typensignatur: foo :: (Num a) => a -> aübersetzt:

a , st a -> a, a  Num

Dies ist als existenzielle Quantifizierung bekannt, was bedeutet, dass es einige Instanzen gibt, afür die eine Funktion, wenn sie vom Typ gespeist wird, etwas vom agleichen Typ zurückgibt, und diese Instanzen gehören alle zur Menge der Zahlen.

Daher können wir sehen, dass das Hinzufügen einer Einschränkung (die azur Menge der Zahlen gehören sollte) die Begriffsebene für mehrere mögliche Implementierungen freigibt.


Kommen wir nun zu der zweiten Aussage und der, die tatsächlich die Erklärung von forall:

Eine Freiheit auf Typebene wird zu einer Einschränkung auf Termebene

Lassen Sie uns nun die Funktion auf Typebene freigeben:

foo :: forall a. a -> a

Dies bedeutet nun:

a , a -> a

Dies bedeutet, dass die Implementierung dieser Typensignatur so erfolgen sollte, dass sie a -> afür alle Umstände geeignet ist.

Dies schränkt uns nun auf der Begriffsebene ein. Wir können nicht mehr schreiben

foo 5 = 7

weil diese Implementierung nicht befriedigen würde, wenn wir aals setzen Bool. akann ein Charoder ein [Char]oder ein benutzerdefinierter Datentyp sein. Unter allen Umständen sollte etwas Ähnliches zurückgegeben werden. Diese Freiheit auf Typebene ist die sogenannte universelle Quantifizierung und die einzige Funktion, die dies erfüllen kann, ist

foo a = a

das ist allgemein als die identityFunktion bekannt


Daher forallhandelt es sich um eine libertyauf Typebene, deren eigentlicher Zweck darin besteht, constraindie Begriffsebene einer bestimmten Implementierung zuzuordnen.

Abhiroop Sarkar
quelle
9

Der Grund, warum dieses Schlüsselwort unterschiedlich verwendet wird, besteht darin, dass es tatsächlich in mindestens zwei verschiedenen Typsystemerweiterungen verwendet wird: höherrangige Typen und Existentials.

Es ist wahrscheinlich am besten, diese beiden Dinge getrennt zu lesen und zu verstehen, anstatt zu versuchen, eine Erklärung dafür zu erhalten, warum 'forall' in beiden gleichzeitig eine angemessene Syntax ist.


quelle
3

Wie ist existenziell existenziell?

Mit Existentielle-Quantifizierung, foralls in dataDefinitionen das bedeutet, enthielt der Wert kann der sein , jede geeigneten Art, nicht , dass es muss von seinen allen geeigneten Typen. - Yachirus Antwort

Eine Erklärung, warum forallin dataDefinitionen isomorph zu (exists a. a)(Pseudo-Haskell) ist, findet sich in Wikibooks "Haskell / Existential quantified types". .

Das Folgende ist eine kurze wörtliche Zusammenfassung:

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

MkT xWelche Art von Pattern Matching / Dekonstruktion gibt es x?

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

xkann ein beliebiger Typ sein (wie im forall) angegeben, und daher ist der Typ:

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

Daher sind die folgenden isomorph:

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

forall bedeutet forall

Meine einfache Interpretation von all dem ist, dass " forallwirklich" für alle "bedeutet". Eine wichtige Unterscheidung zu machen , ist die Wirkung des forallauf der Definition im Vergleich Funktion Anwendung .

A forallbedeutet die Definition des Werts oder der Funktion polymorph sein muss.

Wenn es sich bei der zu definierenden Sache um einen polymorphen Wert handelt , bedeutet dies, dass der Wert für alle geeigneten Werte gültig sein muss a, was sehr restriktiv ist.

Wenn das zu definierende Objekt eine polymorphe Funktion ist , bedeutet dies, dass die Funktion für alle geeigneten gültig sein muss a, was nicht so einschränkend ist, denn nur weil die Funktion polymorph ist, bedeutet dies nicht, dass der angewendete Parameter polymorph sein muss. Das heißt, wenn die Funktion gültig für alle ist a, dann umgekehrt jede geeignete akann angewandt auf die Funktion. Der Typ des Parameters kann jedoch nur einmal in der Funktionsdefinition ausgewählt werden.

Wenn ein forallin der Funktion Parametertyp ist (dh ein Rank2Type) , dann bedeutet es , die angewandten Parameter sein muss wirklich polymorphe, mit der Idee , konsequent seinem forallMittel Definition polymorph ist. In diesem Fall kann der Typ des Parameters in der Funktionsdefinition mehrmals ausgewählt werden ( "und wird durch die Implementierung der Funktion ausgewählt", wie von Norman hervorgehoben ).

Daher der Grund, warum existenzielle dataDefinitionen erlaubt jeder a ist , weil die Daten Konstruktor eine polymorph Funktion :

MkT :: forall a. a -> T

Art von MkT :: a -> *

Das heißt a, auf die Funktion kann jede angewendet werden. Im Gegensatz zu beispielsweise einem polymorphen Wert :

valueT :: forall a. [a]

Art von WertT :: a

Dies bedeutet, dass die Definition von valueT polymorph sein muss. In diesem Fall valueTkann als leere Liste []aller Typen definiert werden.

[] :: [t]

Unterschiede

Obwohl die Bedeutung für forallin ExistentialQuantificationund konsistent ist RankNType, haben Existentiale einen Unterschied, da der dataKonstruktor beim Mustervergleich verwendet werden kann. Wie im ghc-Benutzerhandbuch dokumentiert :

Beim Mustervergleich führt jeder Mustervergleich einen neuen, eindeutigen Typ für jede existenzielle Typvariable ein. Diese Typen können weder mit einem anderen Typ vereinheitlicht werden, noch können sie dem Umfang der Musterübereinstimmung entgehen.

Louis Pan
quelle