it-swarm.com.de

Was ist schwache Kopfnormalform?

Was bedeutet Schwache Kopfnormalform (WHNF)? Was bedeutet Kopf Normalform (HNF) und Normalform (NF) bedeuten?

Real World Haskell besagt:

Die vertraute 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), in der ein Ausdruck vollständig ausgewertet wird.

Sie werden auch Haskell-Programmierer hören, die sich auf die Normalform (WHNF) für schwache Köpfe beziehen. Bei normalen Daten ist die schwache Kopfnormalform dieselbe wie die 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 machen seq und ($!) beziehen sich auf WHNF und HNF?

Aktualisieren

Ich bin immer noch verwirrt. Ich weiß, dass einige der Antworten sagen, HNF zu ignorieren. Beim Lesen der verschiedenen Definitionen scheint es keinen Unterschied zwischen regulären Daten in WHNF und HNF zu geben. Allerdings scheint es einen Unterschied zu geben, wenn es um eine Funktion geht. Wenn es keinen Unterschied gab, warum ist seq notwendig für foldl'?

Ein weiterer Punkt der Verwirrung stammt aus dem Haskell-Wiki, das besagt, dass seq auf WHNF reduziert wird und im folgenden Beispiel nichts unternimmt. Dann sagen sie, dass sie seq verwenden müssen, um die Auswertung zu erzwingen. Zwingt das HNF nicht dazu?

Häufiger Neuling-Stack-Überlaufcode:

myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)

Menschen, die die normale Form von seq und schwachem Kopf (whnf) verstehen, können sofort verstehen, was hier falsch läuft. (acc + x, len + 1) ist bereits in whnf, also hat seq, das einen Wert auf whnf reduziert, nichts damit zu tun. Dieser Code baut Thunks auf, genau wie das ursprüngliche Foldl-Beispiel. Sie befinden sich nur in einem Tupel. Die Lösung besteht lediglich darin, die Komponenten des Tupels, z.

myAverage = uncurry (/) . foldl' 
          (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)

- Haskell Wiki auf Stackoverflow

278
user295190

Ich werde versuchen, eine Erklärung in einfachen Worten zu geben. Wie bereits erwähnt, trifft die normale Kopfform auf Haskell nicht zu, weshalb ich hier nicht darauf eingehen werde.

Normalform

Ein Ausdruck in normaler Form wird vollständig ausgewertet, und kein Unterausdruck kann weiter ausgewertet werden (d. H. Er enthält keine nicht ausgewerteten Thunks).

Diese Ausdrücke sind alle in normaler Form:

42
(2, "hello")
\x -> (x + 1)

Diese Ausdrücke sind nicht in normaler Form:

1 + 2                 -- we could evaluate this to 3
(\x -> x + 1) 2       -- we could apply the function
"he" ++ "llo"         -- we could apply the (++)
(1 + 1, 2 + 2)        -- we could evaluate 1 + 1 and 2 + 2

Schwacher Kopf normale Form

Ein Ausdruck in schwacher Kopfnormalform wurde für den äußersten Datenkonstruktor oder die Lambda-Abstraktion (Kopf) ausgewertet. Unterausdrücke können ausgewertet worden sein oder nicht. Daher hat jeder Ausdruck in normaler Form auch eine schwache Kopfnormalform, 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, weist es eine normale Kopfschwäche auf. Wenn es sich um eine Funktionsanwendung handelt, ist dies nicht der Fall.

Diese Ausdrücke haben eine schwache Kopfnormalform:

(1 + 1, 2 + 2)       -- the outermost part is the data constructor (,)
\x -> 2 + 2          -- the outermost part is a lambda abstraction
'h' : ("e" ++ "llo") -- the outermost part is the data constructor (:)

Wie bereits erwähnt, haben alle oben aufgeführten Normalformausdrücke auch eine Normalform mit schwachem Kopf.

Diese Ausdrücke haben keine schwache Kopfnormalform:

1 + 2                -- the outermost part here is an application of (+)
(\x -> x + 1) 2      -- the outermost part is an application of (\x -> x + 1)
"he" ++ "llo"        -- the outermost part is an application of (++)

Stapel läuft über

Die Auswertung eines Ausdrucks in schwacher Kopfnormalform erfordert möglicherweise, dass andere Ausdrücke zuerst als WHNF ausgewertet werden. Um beispielsweise 1 + (2 + 3) zu WHNF auszuwerten, müssen wir zuerst 2 + 3 Auswerten. Wenn die Auswertung eines einzelnen Ausdrucks zu vielen dieser verschachtelten Auswertungen führt, ist das Ergebnis ein Stapelüberlauf.

Dies passiert, 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 foldl verursacht:

foldl (+) 0 [1, 2, 3, 4, 5, 6]
 = foldl (+) (0 + 1) [2, 3, 4, 5, 6]
 = foldl (+) ((0 + 1) + 2) [3, 4, 5, 6]
 = foldl (+) (((0 + 1) + 2) + 3) [4, 5, 6]
 = foldl (+) ((((0 + 1) + 2) + 3) + 4) [5, 6]
 = foldl (+) (((((0 + 1) + 2) + 3) + 4) + 5) [6]
 = foldl (+) ((((((0 + 1) + 2) + 3) + 4) + 5) + 6) []
 = (((((0 + 1) + 2) + 3) + 4) + 5) + 6
 = ((((1 + 2) + 3) + 4) + 5) + 6
 = (((3 + 3) + 4) + 5) + 6
 = ((6 + 4) + 5) + 6
 = (10 + 5) + 6
 = 15 + 6
 = 21

Beachten Sie, wie tief es gehen muss, bevor es den Ausdruck in die normale Form eines schwachen Kopfes bringen kann.

Sie fragen sich vielleicht, warum Haskell die inneren Ausdrücke nicht vorzeitig reduziert? Das liegt an Haskells Faulheit. Da nicht generell davon ausgegangen werden kann, dass jeder Teilausdruck benötigt wird, werden Ausdrücke von außen nach innen ausgewertet.

(GHC verfügt über einen Strenge-Analysator, der Situationen erkennt, in denen immer ein Teilausdruck benötigt wird, und diesen vorab auswertet. Dies ist jedoch nur eine Optimierung, und Sie sollten sich nicht darauf verlassen, dass Sie vor Überläufen geschützt sind.).

Diese Art von Ausdruck ist dagegen völlig ungefährlich:

data List a = Cons a (List a) | Nil
foldr Cons Nil [1, 2, 3, 4, 5, 6]
 = Cons 1 (foldr Cons Nil [2, 3, 4, 5, 6])  -- Cons is a constructor, stop. 

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 erzwingen, dass die inneren Teile vorab ausgewertet werden.

seq

seq ist eine spezielle Funktion, mit der die Auswertung von Ausdrücken erzwungen wird. Die Semantik besagt, dass seq x y Bedeutet, dass jedes Mal, wenn y als Normalform für schwache Köpfe ausgewertet wird, x auch als Normalform für schwache Köpfe ausgewertet wird.

Es wird unter anderem in der Definition von foldl', Der strengen Variante von foldl, verwendet.

foldl' f a []     = a
foldl' f a (x:xs) = let a' = f a x in a' `seq` foldl' f a' xs

Jede Iteration von foldl' Zwingt den Akkumulator auf WHNF. Es wird daher vermieden, einen großen Ausdruck aufzubauen, und es wird daher ein Überlaufen des Stapels vermieden.

foldl' (+) 0 [1, 2, 3, 4, 5, 6]
 = foldl' (+) 1 [2, 3, 4, 5, 6]
 = foldl' (+) 3 [3, 4, 5, 6]
 = foldl' (+) 6 [4, 5, 6]
 = foldl' (+) 10 [5, 6]
 = foldl' (+) 15 [6]
 = foldl' (+) 21 []
 = 21                           -- 21 is a data constructor, stop.

Wie im Beispiel in HaskellWiki erwähnt, werden Sie dadurch jedoch nicht in allen Fällen gespart, da der Akku nur nach WHNF ausgewertet wird. In diesem Beispiel ist der Akkumulator ein Tupel, sodass nur die Auswertung des Tupel-Konstruktors erzwungen wird und nicht acc oder len.

f (acc, len) x = (acc + x, len + 1)

foldl' f (0, 0) [1, 2, 3]
 = foldl' f (0 + 1, 0 + 1) [2, 3]
 = foldl' f ((0 + 1) + 2, (0 + 1) + 1) [3]
 = foldl' f (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1) []
 = (((0 + 1) + 2) + 3, ((0 + 1) + 1) + 1)  -- Tuple constructor, stop.

Um dies zu vermeiden, müssen wir den Tuple-Konstruktor so einstellen, dass er die Auswertung von acc und len erzwingt. Wir tun dies mit seq.

f' (acc, len) x = let acc' = acc + x
                      len' = len + 1
                  in  acc' `seq` len' `seq` (acc', len')

foldl' f' (0, 0) [1, 2, 3]
 = foldl' f' (1, 1) [2, 3]
 = foldl' f' (3, 2) [3]
 = foldl' f' (6, 3) []
 = (6, 3)                    -- Tuple constructor, stop.
384
hammar

Der Abschnitt über Thunks and Weak Head Normal Form in den Haskell Wikibooks Beschreibung der Faulheit bietet eine sehr gute Beschreibung der WHNF zusammen mit dieser hilfreichen Darstellung:

Evaluating the value (4, [1, 2]) step by step. The first stage is completely unevaluated; all subsequent forms are in WHNF, and the last one is also in normal form.

Werten Sie den Wert (4, [1, 2]) schrittweise aus. Die erste Stufe ist völlig unbewertet; Alle nachfolgenden Formulare sind in WHNF, und das letzte ist auch in normaler Form.

41
aculich

Haskell-Programme sind Ausdrücke und werden durch Ausführen einer Auswertung ausgeführt.

Ersetzen Sie zum Auswerten eines Ausdrucks alle Funktionsanwendungen durch ihre Definitionen. Die Reihenfolge, in der Sie dies tun, spielt keine Rolle, aber es ist immer noch wichtig: Beginnen Sie mit der äußersten Anwendung und gehen Sie von links nach rechts vor; Dies nennt man Lazy Evaluation .

Beispiel:

   take 1 (1:2:3:[])
=> { apply take }
   1 : take (1-1) (2:3:[])
=> { apply (-)  }
   1 : take 0 (2:3:[])
=> { apply take }
   1 : []

Die Auswertung wird beendet, wenn keine Funktionsanwendungen mehr zu ersetzen sind. Das Ergebnis ist in Normalform (oder reduzierter Normalform , RNF). Unabhängig davon, in welcher Reihenfolge Sie einen Ausdruck bewerten, erhalten Sie immer dieselbe normale Form (jedoch nur, wenn die Auswertung endet).

Es gibt eine etwas andere Beschreibung für die Lazy Evaluation. Es heißt nämlich, dass Sie alles nur mit schwache Kopfnormalform bewerten sollten. Es gibt genau drei Fälle für einen Ausdruck in WHNF:

  • Ein Konstruktor: constructor expression_1 expression_2 ...
  • Eine eingebaute Funktion mit zu wenigen Argumenten wie (+) 2 oder sqrt
  • Ein Lambda-Ausdruck: \x -> expression

Mit anderen Worten, der Kopf des Ausdrucks (d. H. Die äußerste Funktionsanwendung) kann nicht weiter ausgewertet werden, aber das Funktionsargument kann unbewertete Ausdrücke enthalten.

Beispiele für WHNF:

3 : take 2 [2,3,4]   -- outermost function is a constructor (:)
(3+1) : [4..]        -- ditto
\x -> 4+5            -- lambda expression

Anmerkungen

  1. Der "Kopf" in WHNF bezieht sich nicht auf den Kopf einer Liste, sondern auf die äußerste Funktionsanwendung.
  2. Manchmal nennen die Leute unbewertete Ausdrücke "Thunks", aber ich denke nicht, dass dies ein guter Weg ist, um es zu verstehen.
  3. Kopfnormalform (HNF) ist für Haskell irrelevant. Es unterscheidet sich von WHNF dadurch, dass auch die Körper von Lambda-Ausdrücken zu einem gewissen Grad ausgewertet werden.
27

Eine gute Erklärung mit Beispielen finden Sie unter http://foldoc.org/Weak+Head+Normal+Form Die normale Kopfform vereinfacht sogar die Teile eines Ausdrucks innerhalb einer Funktionsabstraktion, während "schwach" Kopfnormalform bleibt bei Funktionsabstraktionen stehen.

Aus der Quelle, wenn Sie:

\ x -> ((\ y -> y+x) 2)

das ist in schwacher Kopfnormalform, aber nicht in Kopfnormalform ... weil die mögliche Anwendung in einer Funktion steckt, die noch nicht ausgewertet werden kann.

Tatsächliche Kopfnormalform wäre schwierig effizient umzusetzen. Es würde das Herumstöbern innerhalb von Funktionen erfordern. Der Vorteil der Normalform mit schwachem Kopf besteht also darin, dass Sie Funktionen immer noch als undurchsichtigen Typ implementieren können, und daher besser mit kompilierten Sprachen und Optimierungen kompatibel sind.

26
Chris Smith

Die WHNF möchte daher nicht, dass der Körper von Lambdas bewertet wird

WHNF = \a -> thunk
HNF = \a -> a + c

seq möchte, dass sein erstes Argument in WHNF steht

let a = \b c d e -> (\f -> b + c + d + e + f) b
    b = a 2
in seq b (b 5)

bewertet zu

\d e -> (\f -> 2 + 5 + d + e + f) 2

anstatt, was würde HNF verwenden

\d e -> 2 + 5 + d + e + 2
12
marc

Angenommen, Sie haben eine Art Thunk, t.

Wenn wir nun t zu WHNF oder NHF auswerten möchten, die bis auf Funktionen gleich sind, stellen wir fest, dass wir so etwas wie erhalten

t1 : t2 woher t1 und t2 sind Thunks. In diesem Fall, t1 wäre dein 0 (oder besser gesagt, ein Thunk an 0 kein extra Unboxing gegeben)

seq und $! WHNF auswerten. Beachten Sie, dass

f $! x = seq x (f x)
5
alternative