Ну, в целом приемлемо, хоть и не очень «читабельно». Что если нам нужна функция для получения какого-либо поля? Функция, которая возвращает имя, функция для фамилии и т. д.? Мы можем определить их таким образом:
firstName :: Person –> String
firstName (Person firstname _ _ _ _ _) = firstname
lastName :: Person –> String
lastName (Person _ lastname _ _ _ _) = lastname
age :: Person –> Int
age (Person _ _ age _ _ _) = age
height :: Person –> Float
height (Person _ _ _ height _ _) = height
phoneNumber :: Person –> String
phoneNumber (Person _ _ _ _ number _) = number
flavor :: Person –> String
flavor (Person _ _ _ _ _ flavor) = flavor
Фу-ух! Мало радости писать такие функции!.. Этот метод очень громоздкий и скучный, но он работает.
ghci> let guy = Person "Фредди" "Крюгер" 43 184.2 "526–2928" "Эскимо"
ghci> firstName guy
"Фредди"
ghci> height guy
184.2
ghci> flavor guy
"Эскимо"
Вы скажете – должен быть лучший способ! Ан нет, извиняйте, нету… Шучу, конечно же. Такой метод есть! «Ха-ха» два раза. Создатели языка Haskell предусмотрели подобную возможность – предоставили ещё один способ для записи типов данных. Вот как мы можем достигнуть той же функциональности с помощью синтаксиса записей с именованными полями:
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String } deriving (Show)
Вместо того чтобы просто перечислять типы полей через запятую, мы используем фигурные скобки. Вначале пишем имя поля, например firstName
, затем ставим два двоеточия ::
и, наконец, указываем тип. Результирующий тип данных в точности такой же. Главная выгода – такой синтаксис генерирует функции для извлечения полей. Язык Haskell автоматически создаст функции firstName
, lastName
, age
, height
, phoneNumber
и flavor
.
ghci> :t flavor
flavor :: Person –> String
ghci> :t firstName
firstName :: Person –> String
Есть ещё одно преимущество в использовании синтаксиса записей. Когда мы автоматически генерируем экземпляр класса Show
для типа, он отображает тип не так, как если бы мы использовали синтаксис записей с именованными полями для объявления и инстанцирования типа. Например, у нас есть тип, представляющий автомобиль. Мы хотим хранить следующую информацию: компания-производитель, название модели и год производства.
data Car = Car String String Int deriving (Show)
Автомобиль отображается так:
ghci> Car "Форд" "Мустанг" 1967
Car "Форд" "Мустанг" 1967
Используя синтаксис записей с именованными полями, мы можем описать новый автомобиль так:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
Автомобиль теперь создаётся и отображается следующим образом:
ghci> Car {company="Форд", model="Мустанг", year=1967}
Car {company = "Форд", model = "Мустанг", year = 1967}
При создании нового автомобиля мы, разумеется, обязаны перечислить все поля, но указывать их можно в любом порядке. Но если мы не используем синтаксис записей с именованными полями, то должны указывать их по порядку.
Используйте синтаксис записей с именованными полями, если конструктор имеет несколько полей и не очевидно, какое поле для чего используется. Если, скажем, мы создаём трёхмерный вектор: data Vector = Vector Int Int Int
, то вполне понятно, что поля конструктора данных – это компоненты вектора. Но в типах Person
и Car
назначение полей совсем не так очевидно, и мы значительно выиграем, используя синтаксис записей с именованными полями.
Параметры типа