Haskell Language
Strenge
Suche…
Bang Patterns
Mit einem Knall ( !
) Versehene Muster werden streng und nicht faul bewertet.
foo (!x, y) !z = [x, y, z]
In diesem Beispiel werden x
und z
vor der Rückgabe der Liste in schwacher Normalform ausgewertet. Es entspricht:
foo (x, y) z = x `seq` z `seq` [x, y, z]
Bang-Muster werden mit der BangPatterns
Haskell 2010 BangPatterns
.
Normalformen
Dieses Beispiel bietet einen kurzen Überblick - eine ausführlichere Erläuterung von Normalformen und Beispielen finden Sie in dieser Frage .
Normalform reduziert
Die reduzierte Normalform (oder nur die Normalform, wenn der Kontext klar ist) eines Ausdrucks ist das Ergebnis der Bewertung aller reduzierbaren Unterausdrücke im angegebenen Ausdruck. Aufgrund der nicht strengen Semantik von Haskell (normalerweise als Faulheit bezeichnet ) kann ein Unterausdruck nicht reduziert werden, wenn er sich unter einem Binder befindet (dh einer Lambda-Abstraktion - \x -> ..
). Die Normalform eines Ausdrucks hat die Eigenschaft, dass der Ausdruck eindeutig ist, wenn er existiert.
Mit anderen Worten, es spielt keine Rolle (in Bezug auf die Bedeutungssemantik), in welcher Reihenfolge Sie die Teilausdrücke reduzieren. Der Schlüssel zum Schreiben performanter Haskell-Programme ist jedoch häufig die Sicherstellung, dass der richtige Ausdruck zum richtigen Zeitpunkt ausgewertet wird, dh das Verständnis der operativen Semantik.
Ein Ausdruck, dessen Normalform selbst ist, wird als Normalform bezeichnet .
Einige Ausdrücke, z. B. let x = 1:x in x
, haben keine normale Form, sind aber dennoch produktiv. Der Beispielausdruck hat noch einen Wert , wenn man unendliche Werte zulässt, hier die Liste [1,1, ...]
. Andere Ausdrücke wie let y = 1+y in y
haben keinen Wert oder ihr Wert ist undefined
.
Schwache Kopfnormalform
Die RNF entspricht der vollständigen Auswertung eines Ausdrucks - ebenso entspricht die schwache Kopfnormalform (WHNF) der Auswertung des Kopfes des Ausdrucks. Der Kopf eines Ausdrucks e
wird vollständig ausgewertet, wenn e
eine Anwendung Con e1 e2 .. en
und Con
ein Konstruktor ist. oder eine Abstraktion \x -> e1
; oder eine Teilanwendung f e1 e2 .. en
, wobei Teilanwendung bedeutet, dass f
mehr als n
Argumente annimmt (oder äquivalent der Typ von e
ein Funktionstyp ist). In jedem Fall können die Unterausdrücke e1..en
für den Ausdruck als WHNF ausgewertet oder nicht ausgewertet werden - sie können sogar undefined
.
Die Evaluierungssemantik von Haskell kann anhand der WHNF beschrieben werden: Um einen Ausdruck e
auszuwerten, wird er zuerst in WHNF ausgewertet und dann alle seine Unterausdrücke rekursiv von links nach rechts ausgewertet.
Die primitive seq
Funktion wird verwendet, um einen Ausdruck in WHNF auszuwerten. seq xy
ist denotional gleich y
(der Wert von seq xy
ist genau y
); ferner wird x
zu WHNF ausgewertet, wenn y
zu WHNF ausgewertet wird. Ein Ausdruck kann auch mit einem Bang-Muster (aktiviert durch die Erweiterung -XBangPatterns
) in WHNF ausgewertet werden, dessen Syntax wie folgt lautet:
f !x y = ...
Dabei wird x
zu WHNF ausgewertet, wenn f
ausgewertet wird, während y
nicht (unbedingt) ausgewertet wird. Ein Knallmuster kann auch in einem Konstruktor erscheinen, z
data X = Con A !B C .. N
In diesem Fall wird der Konstruktor Con
im B
Feld als streng bezeichnet, was bedeutet, dass das B
Feld in WHNF ausgewertet wird, wenn der Konstruktor auf ausreichend Argumente (hier zwei) angewendet wird.
Faule Muster
Faule oder unwiderlegbare Muster (mit der Syntax ~pat
) sind Muster, die immer übereinstimmen, ohne den übereinstimmenden Wert zu betrachten. Dies bedeutet, dass faule Muster sogar den unteren Werten entsprechen. Nachfolgende Verwendungen von Variablen, die in Teilmustern eines nicht zu widerlegenden Musters gebunden sind, erzwingen jedoch das Auftreten des Musterabgleichs und wird nach unten bewertet, sofern der Abgleich nicht erfolgreich ist.
Die folgende Funktion ist in ihrem Argument faul:
f1 :: Either e Int -> Int
f1 ~(Right 1) = 42
und so bekommen wir
λ» f1 (Right 1)
42
λ» f1 (Right 2)
42
λ» f1 (Left "foo")
42
λ» f1 (error "oops!")
42
λ» f1 "oops!"
*** type mismatch ***
Die folgende Funktion wird mit einem Lazy-Pattern geschrieben, verwendet jedoch tatsächlich die Variable des Patterns, die die Übereinstimmung erzwingt, und schlägt daher für Left
Argumente fehl:
f2 :: Either e Int -> Int
f2 ~(Right x) = x + 1
λ» f2 (Right 1)
2
λ» f2 (Right 2)
3
λ» f2 (Right (error "oops!"))
*** Exception: oops!
λ» f2 (Left "foo")
*** Exception: lazypat.hs:5:1-21: Irrefutable pattern failed for pattern (Right x)
λ» f2 (error "oops!")
*** Exception: oops!
let
Bindungen faul sein, verhalten Sie sich wie unwiderlegbare Muster:
act1 :: IO ()
act1 = do
ss <- readLn
let [s1, s2] = ss :: [String]
putStrLn "Done"
act2 :: IO ()
act2 = do
ss <- readLn
let [s1, s2] = ss
putStrLn s1
Hier act1
auf Eingaben, die in eine Liste von Strings act1
, während in act2
putStrLn s1
den Wert von s1
der den Musterabgleich für [s1, s2]
erzwingt, sodass es nur für Listen mit genau zwei Strings funktioniert:
λ» act1
> ["foo"]
Done
λ» act2
> ["foo"]
*** readIO: no parse ***
Strikte Felder
In einer data
eine Art mit einem Knall prefixing ( !
) Macht das Feld ein strenges Feld. Wenn der Datenkonstruktor angewendet wird, werden diese Felder in der Normalform "Schwache Kopf" ausgewertet, sodass die Daten in den Feldern garantiert immer in der Normalform "Schwache Kopf" sind.
Strikte Felder können sowohl für Datensatz- als auch für Nicht-Datensatztypen verwendet werden:
data User = User
{ identifier :: !Int
, firstName :: !Text
, lastName :: !Text
}
data T = MkT !Int !Int