Haskell Language
Składnia rekordu
Szukaj…
Podstawowa składnia
Rekordy są rozszerzeniem sumarycznego typu data
algebraicznych, które umożliwiają nazwanie pól:
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
}
Nazwy pól można następnie wykorzystać do usunięcia nazwanego pola z rekordu
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
r :: RecordType
> :t aString
aString :: RecordType -> String
> aString r
"Foobar"
Rekordy można dopasować do wzorca
case r of
RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"
Zauważ, że nie wszystkie pola muszą mieć nazwy
Rekordy są tworzone przez nazywanie ich pól, ale mogą być również tworzone jako zwykłe typy sum (często przydatne, gdy liczba pól jest niewielka i prawdopodobnie nie ulegnie zmianie)
r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType "Foobar" 42 True
Jeśli rekord zostanie utworzony bez nazwanego pola, kompilator wygeneruje ostrzeżenie, a wynikowa wartość będzie undefined
.
> let r = RecordType {aString = "Foobar", aNumber= 42}
<interactive>:1:9: Warning:
Fields of RecordType not initialized: isTrue
> isTrue r
Error 'undefined'
Pole rekordu można zaktualizować, ustawiając jego wartość. Niewymienione pola nie ulegają zmianie.
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
-- r'{aString = "Foobar", aNumber= 117, isTrue = True}
Często przydatne jest tworzenie obiektywów do skomplikowanych typów nagrań.
Kopiowanie rekordów podczas zmiany wartości pól
Załóżmy, że masz ten typ:
data Person = Person { name :: String, age:: Int } deriving (Show, Eq)
i dwie wartości:
alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }
nową wartość typu Person
można utworzyć, kopiując z alex
, określając, które wartości należy zmienić:
anotherAlex = alex { age = 31 }
Wartości alex
i anotherAlex
będzie teraz:
Person {name = "Alex", age = 21}
Person {name = "Alex", age = 31}
Rekordy z nowym typem
Składnia rekordu może być używana z newtype
z zastrzeżeniem, że istnieje dokładnie jeden konstruktor z dokładnie jednym polem. Korzyścią tutaj jest automatyczne tworzenie funkcji do rozpakowywania nowego typu. Te pola są często nazywane od run
dla monad, get
dla monoidów, a un
dla innych typów.
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
Należy zauważyć, że składnia rekordu zwykle nie jest używana do tworzenia wartości, a nazwa pola służy wyłącznie do rozpakowywania
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
Wzorzec Client{..}
obejmuje wszystkie pola konstruktora Client
i jest równoważny wzorowi
Client{ firstName = firstName, lastName = lastName, clientID = clientID }
Można go również łączyć z innymi dopasowaniami terenowymi, takimi jak:
Client { firstName = "Joe", .. }
Jest to równoważne z
Client{ firstName = "Joe", lastName = lastName, clientID = clientID }
Definiowanie typu danych za pomocą etykiet pól
Możliwe jest zdefiniowanie typu danych za pomocą etykiet pól.
data Person = Person { age :: Int, name :: String }
Ta definicja różni się od zwykłej definicji rekordu, ponieważ definiuje także * moduły dostępu do rekordów, których można użyć do uzyskania dostępu do części typu danych.
W tym przykładzie zdefiniowano dwa moduły dostępu do rekordów, age
i name
, które pozwalają nam na dostęp do pól age
i name
odpowiednio.
age :: Person -> Int
name :: Person -> String
Akcesoria do rekordów to tylko funkcje Haskell, które są automatycznie generowane przez kompilator. Jako takie są używane jak zwykłe funkcje Haskell.
Nadając nazwy polom, możemy również używać etykiet pól w wielu innych kontekstach, aby uczynić nasz kod bardziej czytelnym.
Dopasowywanie wzorów
lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x
Możemy powiązać wartość znajdującą się w miejscu odpowiedniej etykiety pola, dopasowując wzór do nowej wartości (w tym przypadku x
), która może być użyta w RHS definicji.
Dopasowywanie wzorców za pomocą NamedFieldPuns
lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name
NamedFieldPuns
tego rozszerzenie NamedFieldPuns
pozwala nam po prostu określić etykietę pola, na której chcemy dopasować, ta nazwa jest następnie wyświetlana w RHS definicji, więc odniesienie do name
odnosi się raczej do wartości niż do rejestratora.
Dopasowywanie RecordWildcards
pomocą RecordWildcards
lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name
Podczas dopasowywania za pomocą RecordWildCards
wszystkie etykiety pól są objęte zakresem. (W tym konkretnym przykładzie name
i age
)
To rozszerzenie jest nieco kontrowersyjne, ponieważ nie jest jasne, w jaki sposób wartości są wprowadzane w zakres, jeśli nie masz pewności co do definicji Person
.
Nagrywaj aktualizacje
setName :: String -> Person -> Person
setName newName person = person { name = newName }
Istnieje również specjalna składnia do aktualizacji typów danych za pomocą etykiet pól.