Wir alle wissen (oder sollten wissen), dass Haskell standardmäßig faul ist. Nichts wird ausgewertet, bis es ausgewertet werden muss. Wann muss also etwas bewertet werden? Es gibt Punkte, an denen Haskell streng sein muss. Ich nenne diese "Strenge Punkte", obwohl dieser spezielle Begriff nicht so weit verbreitet ist, wie ich gedacht hatte. Nach mir:
Eine Reduktion (oder Bewertung) in Haskell erfolgt nur an Strengepunkten.
Die Frage ist also: Was genau sind Haskells Strengepunkte? Meine Intuition sagt , dass main
, seq
/ Knall - Muster, Pattern - Matching, und jede IO
Aktion durchgeführt über main
die primären Strikt Punkte, aber ich weiß wirklich nicht , warum ich das wissen.
(Wenn sie nicht als "Strenge Punkte" bezeichnet werden, wie heißen sie dann?)
Ich kann mir vorstellen, dass eine gute Antwort eine Diskussion über WHNF und so weiter beinhalten wird. Ich stelle mir auch vor, dass es den Lambda-Kalkül berühren könnte.
Bearbeiten: zusätzliche Gedanken zu dieser Frage.
Da ich über diese Frage nachgedacht habe, denke ich, dass es klarer wäre, der Definition eines Strengepunkts etwas hinzuzufügen. Strenge Punkte können unterschiedliche Kontexte und unterschiedliche Tiefen (oder Strenge) haben. Zurück zu meiner Definition, dass "Reduktion in Haskell nur an Strengepunkten auftritt", fügen wir dieser Definition diese Klausel hinzu: "Ein Strengepunkt wird nur ausgelöst, wenn sein umgebender Kontext bewertet oder reduziert wird."
Lassen Sie mich versuchen, Ihnen die Art von Antwort zu geben, die ich möchte. main
ist ein strenger Punkt. Es wird speziell als primärer Strengepunkt seines Kontextes bezeichnet: das Programm. Wenn das Programm ( main
der Kontext) ausgewertet wird, wird der Strengepunkt von main aktiviert. Die Tiefe des Mains ist maximal: Es muss vollständig ausgewertet werden. Main besteht normalerweise aus E / A-Aktionen, die auch Strengepunkte sind und deren Kontext ist main
.
Jetzt versuchen Sie: Diskutieren seq
und Mustervergleich in diesen Begriffen. Erklären Sie die Nuancen der Funktionsanwendung: Wie streng ist sie? Wie ist es nicht Was ist mit deepseq
? let
und case
Aussagen? unsafePerformIO
? Debug.Trace
? Top-Level-Definitionen? Strenge Datentypen? Knallmuster? Wie viele dieser Elemente können nur in Form von Seq oder Pattern Matching beschrieben werden?
quelle
seq
und Mustervergleich ausreichend sind, wobei der Rest in Bezug auf diese definiert wird. Ich denke, Pattern Matching gewährleistetIO
zum Beispiel die Strenge der Handlungen.+
bei den eingebauten numerischen Typen, erzwingen ebenfalls Strenge, und ich gehe davon aus, dass dies auch für reine FFI-Aufrufe gilt.Antworten:
Ein guter Anfang ist das Verständnis dieses Papiers: Eine natürliche Semantik für Lazy Evalution (Launchbury). Hier erfahren Sie, wann Ausdrücke für eine kleine Sprache ausgewertet werden, die dem Kern von GHC ähnelt. Die verbleibende Frage ist dann, wie das vollständige Haskell dem Kern zugeordnet werden kann. Der größte Teil dieser Übersetzung wird vom Haskell-Bericht selbst bereitgestellt. In GHC nennen wir diesen Prozess "Desugaring", weil er syntaktischen Zucker entfernt.
Nun, das ist nicht die ganze Geschichte, denn GHC enthält eine ganze Reihe von Optimierungen zwischen Desugaring und Codegenerierung, und viele dieser Transformationen werden den Kern neu anordnen, sodass die Dinge zu unterschiedlichen Zeiten bewertet werden (insbesondere die Strenge-Analyse führt dazu, dass die Dinge bewertet werden vorhin). Um wirklich zu verstehen, wie Ihr Programm bewertet wird, müssen Sie sich den von GHC erstellten Core ansehen.
Vielleicht erscheint Ihnen diese Antwort etwas abstrakt (ich habe weder Knallmuster noch Seq erwähnt), aber Sie haben nach etwas Genauem gefragt , und dies ist ungefähr das Beste, was wir tun können.
quelle
Ich würde diese Frage wahrscheinlich wie folgt umformulieren : Unter welchen Umständen wird Haskell einen Ausdruck bewerten? (Vielleicht eine "zu schwache Kopf-Normalform" anheften.)
In erster Näherung können wir dies wie folgt spezifizieren:
Aus Ihrer intuitiven Liste fallen Haupt- und E / A-Aktionen in die erste Kategorie, und Sequenz- und Mustervergleich fallen in die zweite Kategorie. Ich denke jedoch, dass die erste Kategorie eher Ihrer Vorstellung von "Strenge" entspricht, da wir auf diese Weise bewirken, dass die Bewertung in Haskell zu beobachtbaren Effekten für Benutzer wird.
Es ist eine große Aufgabe, alle Details spezifisch anzugeben, da Haskell eine große Sprache ist. Es ist auch ziemlich subtil, weil Concurrent Haskell die Dinge spekulativ bewerten kann, obwohl wir am Ende das Ergebnis nicht verwenden: Dies ist eine dritte Generation von Dingen, die eine Bewertung verursachen. Die zweite Kategorie ist recht gut untersucht: Sie möchten die Strenge der beteiligten Funktionen betrachten. Auch die erste Kategorie kann als eine Art "Strenge" angesehen werden, obwohl dies ein wenig zwielichtig ist, weil
evaluate x
undseq x $ return ()
tatsächlich verschiedene Dinge sind! Sie können es richtig behandeln, wenn Sie der E / A-Monade eine Art Semantik geben (die explizite Übergabe einesRealWorld#
Tokens funktioniert in einfachen Fällen), aber ich weiß nicht, ob es einen Namen für diese Art der geschichteten Strenge-Analyse im Allgemeinen gibt.quelle
C hat das Konzept von Sequenzpunkten , die für bestimmte Operationen garantieren, dass ein Operand vor dem anderen ausgewertet wird. Ich denke, das ist das am nächsten existierende Konzept, aber der im Wesentlichen äquivalente Begriff Strengepunkt (oder möglicherweise Kraftpunkt ) entspricht eher dem Haskell-Denken.
Ihr Denken über
!
/$!
undseq
ist also im Wesentlichen richtig, aber der Mustervergleich unterliegt subtileren Regeln. Sie können~
natürlich immer verwenden , um einen faulen Mustervergleich zu erzwingen. Ein interessanter Punkt aus demselben Artikel:Lassen Sie uns das Kaninchenloch hinuntergehen und in den Dokumenten nach Optimierungen suchen, die von GHC durchgeführt wurden:
Mit anderen Worten, strenger Code kann als Optimierung überall generiert werden , da das Erstellen von Thunks unnötig teuer ist, wenn die Daten immer benötigt werden (und / oder nur einmal verwendet werden dürfen).
(Ein Begriff ist in Kopf Normalform , wenn es kein Beta-redex in Position Kopf 1 . Ein redex ein Kopf redex ist , wenn es nur von Lambda Abstraktoren von nicht-Redexe ubervorangestellt ist 2 .) Also , wenn Sie beginnen einen Thunk zu zwingen, Sie arbeiten in WHNF; Wenn keine Schläge mehr zu erzwingen sind, sind Sie in normaler Form. Ein weiterer interessanter Punkt:
Was natürlich impliziert , dass zwar jede
IO
ausgeführte Aktion ausmain
tut Kraft Bewertung, die unter Berücksichtigung sollte klar sein , dass Haskell Programme tun, in der Tat, Dinge zu tun. Alles, was die in definierte Reihenfolge durchlaufenmain
muss, muss in normaler Form vorliegen und unterliegt daher einer strengen Bewertung.CA McCann hat es jedoch in den Kommentaren richtig verstanden: Das einzige, was besonders
main
ist,main
ist, dass es als speziell definiert wird; Der Mustervergleich auf dem Konstruktor ist ausreichend, um die von derIO
Monade auferlegte Sequenz sicherzustellen . Nur in dieser Hinsicht sindseq
Mustervergleiche von grundlegender Bedeutung.quelle
Show
Instanz für den zu druckenden Wert.Haskell ist AFAIK keine reine faule Sprache, sondern eine nicht strenge Sprache. Dies bedeutet, dass Begriffe nicht unbedingt zum letztmöglichen Zeitpunkt bewertet werden.
Eine gute Quelle für Haskells Modell der "Faulheit" finden Sie hier: http://en.wikibooks.org/wiki/Haskell/Laziness
Grundsätzlich ist es wichtig, den Unterschied zwischen einem Thunk und der schwachen Header-Normalform WHNF zu verstehen.
Mein Verständnis ist, dass Haskell Berechnungen im Vergleich zu imperativen Sprachen rückwärts durchzieht. Dies bedeutet, dass es in Abwesenheit von "seq" - und Knallmustern letztendlich eine Art Nebeneffekt ist, der die Bewertung eines Thunks erzwingt, was wiederum zu vorherigen Bewertungen führen kann (wahre Faulheit).
Da dies zu einem schrecklichen Platzleck führen würde, findet der Compiler dann heraus, wie und wann Thunks im Voraus ausgewertet werden müssen, um Platz zu sparen. Der Programmierer kann diesen Prozess dann unterstützen, indem er Anmerkungen zur Strenge (en.wikibooks.org/wiki/Haskell/Strictness, www.haskell.org/haskellwiki/Performance/Strictness) gibt, um die Speicherplatznutzung in Form verschachtelter Thunks weiter zu reduzieren.
Ich bin kein Experte für die operative Semantik von Haskell, daher lasse ich den Link einfach als Ressource.
Einige weitere Ressourcen:
http://www.haskell.org/haskellwiki/Performance/Laziness
http://www.haskell.org/haskellwiki/Haskell/Lazy_Evaluation
quelle
Faul heißt nicht nichts zu tun. Immer wenn Ihr Programmmuster mit einem
case
Ausdruck übereinstimmt , wertet es etwas aus - gerade genug. Andernfalls kann nicht herausgefunden werden, welche RHS verwendet werden soll. Sie sehen keine Fallausdrücke in Ihrem Code? Keine Sorge, der Compiler übersetzt Ihren Code in eine abgespeckte Form von Haskell, deren Verwendung schwer zu vermeiden ist.Für einen Anfänger ist eine Faustregel
let
faul,case
ist weniger faul.quelle
case
Auswertung in GHC Core zwar immer erzwungen wird, dies jedoch in regulärem Haskell nicht der Fall ist. Versuchen Sie es zum Beispielcase undefined of _ -> 42
.case
in GHC Core wertet sein Argument gegenüber WHNF aus, währendcase
in Haskell sein Argument so weit wie nötig auswertet , um den entsprechenden Zweig auszuwählen. In Hammars Beispiel ist das überhaupt nicht so, abercase 1:undefined of x:y:z -> 42
es wird tiefer ausgewertet als WHNF.case something of (y,x) -> (x,y)
gar nicht auswertensomething
. Dies gilt für alle Produkttypen.something
müsste zu WHNF ausgewertet werden, um den Tupelkonstruktor zu erreichen.Dies ist keine vollständige Antwort, die auf Karma abzielt, sondern nur ein Teil des Puzzles. In dem Maße, in dem es um Semantik geht, sollten Sie berücksichtigen, dass es mehrere Bewertungsstrategien gibt, die dieselbe Semantik bieten . Ein gutes Beispiel hier - und das Projekt spricht auch dafür, wie wir normalerweise über die Haskell-Semantik denken - war das Eager Haskell-Projekt, das die Bewertungsstrategien unter Beibehaltung derselben Semantik radikal veränderte : http://csg.csail.mit.edu/ pubs / haskell.html
quelle
Der Glasgow Haskell-Compiler übersetzt Ihren Code in eine Lambda-Kalkül-ähnliche Sprache namens Core . In dieser Sprache wird etwas ausgewertet, wenn Sie das Muster durch eine
case
Anweisung abgleichen. Wenn also eine Funktion aufgerufen wird, wird der äußerste Konstruktor und nur dieser (wenn keine erzwungenen Felder vorhanden sind) ausgewertet. Alles andere ist in einem Thunk eingemacht. (Thunks werden durchlet
Bindungen eingeführt).Natürlich ist dies nicht genau das, was in der realen Sprache passiert. Der Compiler konvertiert Haskell auf sehr raffinierte Weise in Core und macht so viele Dinge wie möglich faul und alles, was immer benötigt wird, faul. Darüber hinaus gibt es Werte und Tupel ohne Box, die immer streng sind.
Wenn Sie versuchen, eine Funktion von Hand zu bewerten, können Sie im Grunde denken:
quelle