Was bedeutet Weak Head Normal Form (WHNF)? Was bedeutet Leiter Normalform (HNF) und Normalform (NF) bedeuten?
In der realen Welt sagt Haskell :
Die bekannte seq-Funktion wertet einen Ausdruck in der sogenannten Kopfnormalform (abgekürzt HNF) aus. Es stoppt, sobald es den äußersten Konstruktor (den „Kopf“) erreicht. Dies unterscheidet sich von der Normalform (NF), bei der ein Ausdruck vollständig ausgewertet wird.
Sie werden auch hören, wie Haskell-Programmierer sich auf die Normalform des schwachen Kopfes (WHNF) beziehen. Bei normalen Daten entspricht die schwache Kopfnormalform der Kopfnormalform. Der Unterschied ergibt sich nur für Funktionen und ist zu abstrus, um uns hier zu beschäftigen.
Ich habe einige Ressourcen und Definitionen gelesen ( Haskell Wiki und Haskell Mail List und Free Dictionary ), aber ich verstehe es nicht. Kann jemand vielleicht ein Beispiel geben oder eine Laiendefinition liefern?
Ich vermute, es wäre ähnlich wie:
WHNF = thunk : thunk
HNF = 0 : thunk
NF = 0 : 1 : 2 : 3 : []
Wie macht seq
und ($!)
beziehen sich auf WHNF und HNF?
Aktualisieren
Ich bin immer noch verwirrt. Ich weiß, dass einige der Antworten besagen, HNF zu ignorieren. Aus dem Lesen der verschiedenen Definitionen geht hervor, dass es keinen Unterschied zwischen regulären Daten in WHNF und HNF gibt. Es scheint jedoch einen Unterschied zu geben, wenn es um eine Funktion geht. Wenn es keinen Unterschied gab, warum?seq
notwendig für foldl'
?
Ein weiterer Punkt der Verwirrung stammt aus dem Haskell-Wiki, das besagt, dass es seq
sich auf WHNF reduziert und dem folgenden Beispiel nichts antut. Dann sagen sie, dass sie verwenden müssenseq
, um die Bewertung zu erzwingen. Erzwingt das nicht HNF?
Häufiger Code für überlaufende Neulinge:
myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)
Menschen, die seq und schwache Kopfnormalform (whnf) verstehen, können sofort verstehen, was hier schief geht. (acc + x, len + 1) ist bereits in whnf, daher tut seq, das einen Wert auf whnf reduziert, nichts dagegen. Dieser Code baut Thunks auf, genau wie das ursprüngliche Foldl-Beispiel, sie befinden sich nur in einem Tupel. Die Lösung besteht nur darin, die Komponenten des Tupels zu erzwingen, z
myAverage = uncurry (/) . foldl' (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)
quelle
Antworten:
Ich werde versuchen, eine Erklärung in einfachen Worten zu geben. Wie andere bereits betont haben, gilt die normale Kopfform nicht für Haskell, daher werde ich sie hier nicht berücksichtigen.
Normalform
Ein Ausdruck in normaler Form wird vollständig ausgewertet, und es kann kein Unterausdruck weiter ausgewertet werden (dh er enthält keine nicht ausgewerteten Thunks).
Diese Ausdrücke sind alle in normaler Form:
Diese Ausdrücke sind nicht in normaler Form:
Normale Form des schwachen Kopfes
Ein Ausdruck in schwacher Kopfnormalform wurde für den äußersten Datenkonstruktor oder die Lambda-Abstraktion (den Kopf ) ausgewertet . Unterausdrücke können ausgewertet worden sein oder nicht . Daher liegt jeder Ausdruck in normaler Form auch in normaler Form mit schwachem Kopf vor, obwohl das Gegenteil im Allgemeinen nicht zutrifft.
Um festzustellen, ob ein Ausdruck eine schwache Kopfnormalform hat, müssen wir nur den äußersten Teil des Ausdrucks betrachten. Wenn es sich um einen Datenkonstruktor oder ein Lambda handelt, liegt die normale Kopfform vor. Wenn es sich um eine Funktionsanwendung handelt, ist dies nicht der Fall.
Diese Ausdrücke sind in schwacher Kopfnormalform:
Wie bereits erwähnt, liegen alle oben aufgeführten Normalformausdrücke auch in schwacher Kopfnormalform vor.
Diese Ausdrücke haben keine schwache Kopfnormalform:
Stapel läuft über
Das Auswerten eines Ausdrucks in eine normale Form mit schwachem Kopf kann erfordern, dass andere Ausdrücke zuerst in WHNF ausgewertet werden. Um beispielsweise
1 + (2 + 3)
WHNF zu bewerten , müssen wir zuerst bewerten2 + 3
. Wenn die Auswertung eines einzelnen Ausdrucks zu zu vielen dieser verschachtelten Auswertungen führt, ist das Ergebnis ein Stapelüberlauf.Dies geschieht, wenn Sie einen großen Ausdruck erstellen, der keine Datenkonstruktoren oder Lambdas erzeugt, bis ein großer Teil davon ausgewertet wurde. Diese werden häufig durch diese Art der Verwendung von Folgendem verursacht
foldl
:Beachten Sie, dass es ziemlich tief gehen muss, bevor es den Ausdruck in eine normale Form mit schwachem Kopf bringen kann.
Sie fragen sich vielleicht, warum Haskell die inneren Ausdrücke nicht im Voraus reduziert? Das liegt an Haskells Faulheit. Da generell nicht davon ausgegangen werden kann, dass jeder Unterausdruck benötigt wird, werden Ausdrücke von außen nach innen ausgewertet.
(GHC verfügt über einen Strenge-Analysator, der einige Situationen erkennt, in denen immer ein Unterausdruck erforderlich ist, und dieser dann vorab auswerten kann. Dies ist jedoch nur eine Optimierung, und Sie sollten sich nicht darauf verlassen, um Überläufe zu vermeiden.)
Diese Art des Ausdrucks ist dagegen völlig sicher:
Um zu vermeiden, dass diese großen Ausdrücke erstellt werden, wenn wir wissen, dass alle Unterausdrücke ausgewertet werden müssen, möchten wir die Auswertung der inneren Teile im Voraus erzwingen.
seq
seq
ist eine spezielle Funktion, mit der die Auswertung von Ausdrücken erzwungen wird. Seine Semantikseq x y
bedeutet, dass jedes Mal, wenny
es als schwache Kopfnormalform bewertet wird, auch als schwache Kopfnormalform bewertetx
wird.Es wird unter anderem in der Definition von verwendet
foldl'
der strengen Variante von verwendetfoldl
.Jede Iteration von
foldl'
zwingt den Akkumulator zu WHNF. Es wird daher vermieden, einen großen Ausdruck aufzubauen, und es wird daher ein Überlaufen des Stapels vermieden.Wie im Beispiel in HaskellWiki erwähnt, werden Sie dadurch nicht in allen Fällen gerettet, da der Akkumulator nur für WHNF ausgewertet wird. In diesem Beispiel ist der Akkumulator ein Tupel, sodass nur die Auswertung des Tupelkonstruktors erzwungen wird und nicht
acc
oderlen
.Um dies zu vermeiden, müssen wir dafür sorgen, dass die Auswertung des Tupelkonstruktors die Auswertung von
acc
und erzwingtlen
. Wir tun dies mitseq
.quelle
\x -> 1 + 1
ist WHNF aber nicht HNF.seq
ihre Argumente anrufen ?:set +s
. Sie können dann sehen, dassfoldl' f
am Ende mehr Thunks als zugewiesen werdenfoldl' f'
.Der Abschnitt über Thunks und Schwachkopf-Normalform in der Beschreibung von Faulheit in Haskell Wikibooks enthält eine sehr gute Beschreibung von WHNF zusammen mit dieser hilfreichen Darstellung:
quelle
Haskell-Programme sind Ausdrücke und werden durch Ausführen einer Auswertung ausgeführt .
Um einen Ausdruck auszuwerten, ersetzen Sie alle Funktionsanwendungen durch ihre Definitionen. Die Reihenfolge, in der Sie dies tun, spielt keine große Rolle, ist aber dennoch wichtig: Beginnen Sie mit der äußersten Anwendung und fahren Sie von links nach rechts fort. Dies wird als verzögerte Bewertung bezeichnet .
Beispiel:
Die Auswertung wird beendet, wenn keine Funktionsanwendungen mehr zu ersetzen sind. Das Ergebnis ist in normaler Form (oder in reduzierter normaler Form) , RNF). Unabhängig davon, in welcher Reihenfolge Sie einen Ausdruck auswerten, erhalten Sie immer dieselbe normale Form (jedoch nur, wenn die Auswertung endet).
Es gibt eine etwas andere Beschreibung für die verzögerte Bewertung. Es heißt nämlich, dass Sie alles nur auf schwache Kopfnormalform bewerten sollten . Es gibt genau drei Fälle, in denen sich ein Ausdruck in WHNF befindet:
constructor expression_1 expression_2 ...
(+) 2
odersqrt
\x -> expression
Mit anderen Worten, der Kopf des Ausdrucks (dh die äußerste Funktionsanwendung) kann nicht weiter ausgewertet werden, aber das Funktionsargument kann nicht bewertete Ausdrücke enthalten.
Beispiele für WHNF:
Anmerkungen
quelle
seq
infoldl'
Kraft?seq expr1 expr2
wird der erste Ausdruckexpr1
für WHNF ausgewertet , bevor der zweite Ausdruck ausgewertet wirdexpr2
.Eine gute Erklärung mit Beispielen finden Sie unter http://foldoc.org/Weak+Head+Normal+Form. Die Kopfnormalform vereinfacht sogar die Bits eines Ausdrucks innerhalb einer Funktionsabstraktion, während die "schwache" Kopfnormalform bei Funktionsabstraktionen stoppt .
Aus der Quelle, wenn Sie haben:
das ist in schwacher Kopfnormalform, aber nicht in Kopfnormalform ... weil die mögliche Anwendung in einer Funktion steckt, die noch nicht ausgewertet werden kann.
Die tatsächliche Kopfnormalform wäre schwierig effizient umzusetzen. Es würde erfordern, innerhalb von Funktionen herumzustöbern. Der Vorteil einer normalen Form mit schwachem Kopf besteht also darin, dass Sie Funktionen weiterhin als undurchsichtigen Typ implementieren können und daher besser mit kompilierten Sprachen und Optimierungen kompatibel sind.
quelle
Der WHNF möchte nicht, dass der Lambdas-Körper bewertet wird
seq
will, dass sein erstes Argument in WHNF ist, alsobewertet zu
stattdessen, was würde HNF verwenden
quelle
Nehmen wir im Grunde an, Sie haben eine Art Thunk
t
.Wenn wir
t
nun WHNF oder NHF bewerten möchten, die bis auf Funktionen gleich sind, würden wir feststellen, dass wir so etwas bekomment1 : t2
wot1
undt2
sind thunks. In diesem Fallt1
wäre Ihr0
(oder besser gesagt ein Thunk,0
kein zusätzliches Unboxing zu geben)seq
und$!
WHNF bewerten. Beachten Sie, dassquelle