Поиск…


Основной синтаксис

Записи - это расширение типа алгебраических data суммы, которое позволяет называть поля:

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
  }

Имена полей могут быть использованы для того, чтобы вывести именованное поле из записи

> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
  r :: RecordType
> :t aString
  aString :: RecordType -> String
> aString r
  "Foobar"

Записи могут быть сопоставлены с шаблонами

case r of
  RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"

Обратите внимание, что не все поля должны быть названы

Записи создаются путем присвоения имен их полям, но также могут быть созданы как обычные типы сумм (часто полезно, когда количество полей невелико и вряд ли изменится)

r  = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType  "Foobar" 42 True

Если запись создается без именованного поля, компилятор выдаст предупреждение, и результирующее значение не будет undefined .

> let r = RecordType {aString = "Foobar", aNumber= 42}
  <interactive>:1:9: Warning:
     Fields of RecordType not initialized: isTrue
> isTrue r
  Error 'undefined'

Поле записи можно обновить, установив его значение. Неизменяемые поля не меняются.

> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
    -- r'{aString = "Foobar", aNumber= 117, isTrue = True}

Часто бывает полезно создавать объективы для сложных типов записей.

Копирование записей при изменении значений полей

Предположим, что у вас есть этот тип:

data Person = Person { name :: String, age:: Int } deriving (Show, Eq)

и два значения:

alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }

новое значение типа Person может быть создано путем копирования из alex , указав, какие значения изменить:

anotherAlex = alex { age = 31 }

Теперь значения alex и anotherAlex будут следующими:

Person {name = "Alex", age = 21}

Person {name = "Alex", age = 31}

Записи с новым типом

Синтаксис записи может использоваться с newtype с ограничением, что существует ровно один конструктор с ровно одним полем. Преимущество здесь заключается в автоматическом создании функции для развертывания нового типа. Эти поля часто называются начиная с run для монадов, get для моноидов и un для других типов.

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

Важно отметить, что синтаксис записи обычно никогда не используется для формирования значений, а имя поля используется строго для разворачивания

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

Шаблон Client{..} включает в себя все поля Client конструктора и эквивалентен шаблону

Client{ firstName = firstName, lastName = lastName, clientID = clientID }

Его также можно комбинировать с другими полями:

Client { firstName = "Joe", .. }

Это эквивалентно

Client{ firstName = "Joe", lastName = lastName, clientID = clientID }

Определение типа данных с метками поля

Можно определить тип данных с метками поля.

data Person = Person { age :: Int, name :: String }

Это определение отличается от обычного определения записи, так как оно также определяет * записи, которые могут использоваться для доступа к частям типа данных.

В этом примере определены два регистратора доступа, age и name , которые позволяют нам получить доступ к полям age и name соответственно.

age :: Person -> Int
name :: Person -> String

Аксессоры записи - это просто функции Haskell, которые автоматически генерируются компилятором. Таким образом, они используются как обычные функции Haskell.

По наименованию полей мы также можем использовать метки полей в ряде других контекстов, чтобы сделать наш код более читаемым.

Соответствие шаблону

lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x

Мы можем привязать значение, расположенное в позиции соответствующей метки поля, в то время как соответствие шаблону соответствует новому значению (в данном случае x ), которое может использоваться в RHS определения.

Совпадение шаблонов с NamedFieldPuns

lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name

Расширение NamedFieldPuns вместо этого позволяет нам просто указывать метку поля, с которой мы хотим сопоставлять, это имя затем затеняется на RHS определения, поэтому обращение к name относится к значению, а не к аксессуару записи.

Сравнение шаблонов с RecordWildcards

lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name

При сопоставлении с использованием RecordWildCards все метки полей вводятся в объем. (В этом конкретном примере name и age )

Это расширение несколько противоречиво, поскольку неясно, как значения вводятся в область видимости, если вы не уверены в определении Person .

Обновление записей

setName :: String -> Person -> Person
setName newName person = person { name = newName }

Существует также специальный синтаксис для обновления типов данных с помощью меток полей.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow