Haskell Language
Record-Syntax
Suche…
Grundlegende Syntax
Datensätze sind eine Erweiterung des summengebraischen data
, mit denen Felder benannt werden können:
data StandardType = StandardType String Int Bool --standard way to create a sum type
data RecordType = RecordType { -- the same sum type with record syntax
aString :: String
, aNumber :: Int
, isTrue :: Bool
}
Die Feldnamen können dann verwendet werden, um das benannte Feld aus dem Datensatz zu holen
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
r :: RecordType
> :t aString
aString :: RecordType -> String
> aString r
"Foobar"
Datensätze können mit einem Muster verglichen werden
case r of
RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"
Beachten Sie, dass nicht alle Felder benannt werden müssen
Datensätze werden durch Benennung ihrer Felder erstellt, können aber auch als gewöhnliche Summenarten erstellt werden (oft nützlich, wenn die Anzahl der Felder klein ist und sich wahrscheinlich nicht ändert)
r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType "Foobar" 42 True
Wenn ein Datensatz ohne ein benanntes Feld erstellt wird, gibt der Compiler eine Warnung aus und der resultierende Wert ist undefined
.
> let r = RecordType {aString = "Foobar", aNumber= 42}
<interactive>:1:9: Warning:
Fields of RecordType not initialized: isTrue
> isTrue r
Error 'undefined'
Ein Feld eines Datensatzes kann durch Setzen seines Werts aktualisiert werden. Nicht erwähnte Felder ändern sich nicht.
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
-- r'{aString = "Foobar", aNumber= 117, isTrue = True}
Es ist oft nützlich, Objektive für komplizierte Aufnahmetypen zu erstellen.
Datensätze beim Ändern von Feldwerten kopieren
Angenommen, Sie haben diesen Typ:
data Person = Person { name :: String, age:: Int } deriving (Show, Eq)
und zwei Werte:
alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }
Ein neuer Wert vom Typ Person
kann durch Kopieren von alex
, wobei die zu ändernden Werte angegeben werden
anotherAlex = alex { age = 31 }
Die Werte von alex
und anotherAlex
jetzt:
Person {name = "Alex", age = 21}
Person {name = "Alex", age = 31}
Datensätze mit newtype
Record-Syntax kann mit newtype
mit der Einschränkung, dass es genau einen Konstruktor mit genau einem Feld gibt. Der Vorteil hierbei ist die automatische Erstellung einer Funktion zum Entpacken des neuen Typs. Diese Felder werden oft mit run
für Monaden, get
für Monoids und un
für andere Typen benannt.
newtype State s a = State { runState :: s -> (s, a) }
newtype Product a = Product { getProduct :: a }
newtype Fancy = Fancy { unfancy :: String }
-- a fancy string that wants to avoid concatenation with ordinary strings
Es ist wichtig zu wissen, dass die Datensatzsyntax normalerweise niemals zur Bildung von Werten verwendet wird und der Feldname ausschließlich zum Entpacken verwendet wird
getProduct $ mconcat [Product 7, Product 9, Product 12]
-- > 756
RecordWildCards
{-# LANGUAGE RecordWildCards #-}
data Client = Client { firstName :: String
, lastName :: String
, clientID :: String
} deriving (Show)
printClientName :: Client -> IO ()
printClientName Client{..} = do
putStrLn firstName
putStrLn lastName
putStrLn clientID
Der Muster- Client{..}
bezieht alle Felder des Konstruktor- Client
und entspricht dem Muster
Client{ firstName = firstName, lastName = lastName, clientID = clientID }
Es kann auch mit anderen Field Matchern kombiniert werden:
Client { firstName = "Joe", .. }
Das ist äquivalent zu
Client{ firstName = "Joe", lastName = lastName, clientID = clientID }
Definieren eines Datentyps mit Feldbeschriftungen
Es ist möglich, einen Datentyp mit Feldbeschriftungen zu definieren.
data Person = Person { age :: Int, name :: String }
Diese Definition unterscheidet sich von einer normalen Datensatzdefinition, da sie auch * Record Accessors definiert, mit denen auf Teile eines Datentyps zugegriffen werden kann.
In diesem Beispiel werden zwei Record Accessors definiert, age
und name
, mit denen wir auf die Felder age
und name
zugreifen können.
age :: Person -> Int
name :: Person -> String
Record Accessoren sind nur Haskell-Funktionen, die automatisch vom Compiler generiert werden. Als solche werden sie wie gewöhnliche Haskell-Funktionen verwendet.
Durch die Benennung von Feldern können wir die Feldbezeichnungen auch in einer Reihe anderer Kontexte verwenden, um den Code lesbarer zu machen.
Musterabgleich
lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x
Wir können den Wert, der sich an der Position der relevanten Feldbeschriftung befindet, binden, während der Mustervergleich mit einem neuen Wert (in diesem Fall x
) erfolgt, der auf der RHS einer Definition verwendet werden kann.
Musterabgleich mit NamedFieldPuns
lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name
Die NamedFieldPuns
Erweiterung ermöglicht es uns stattdessen, nur die NamedFieldPuns
anzugeben, mit der wir übereinstimmen möchten. Dieser Name wird dann auf der rechten Seite einer Definition NamedFieldPuns
sodass der Bezug auf name
auf den Wert und nicht auf den Datensatz-Accessor verweist.
Musterabgleich mit RecordWildcards
lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name
Beim Abgleich mit RecordWildCards
werden alle RecordWildCards
in den Geltungsbereich gebracht. (In diesem speziellen Beispiel name
und age
)
Diese Erweiterung ist leicht umstritten, da nicht klar ist, wie Werte in den Geltungsbereich einbezogen werden, wenn Sie sich der Definition von Person
nicht sicher sind.
Updates aufzeichnen
setName :: String -> Person -> Person
setName newName person = person { name = newName }
Es gibt auch eine spezielle Syntax für die Aktualisierung von Datentypen mit Feldbezeichnungen.