Haskell Language
Синтаксис записи
Поиск…
Основной синтаксис
Записи - это расширение типа алгебраических 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 }
Существует также специальный синтаксис для обновления типов данных с помощью меток полей.