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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow