Синтаксис записи с именованными полями
Есть ещё один способ определить тип данных. Предположим, что перед нами поставлена задача создать тип данных для описания человека. Данные, которые мы намереваемся хранить, – имя, фамилия, возраст, рост, телефон и любимый сорт мороженого. (Не знаю, как насчёт вас, но это всё, что я хотел бы знать о человеке!) Давайте опишем такой тип:
data Person = Person String String Int Float String String deriving (Show)
Первое поле – это имя, второе – фамилия, третье – возраст и т. д. И вот наш персонаж:
ghci> let guy = Person "Фредди" "Крюгер" 43 184.2 "526–2928" "Эскимо"
ghci> guy
Person "Фредди" "Крюгер" 43 184.2 "526–2928" "Эскимо"
Ну, в целом приемлемо, хоть и не очень «читабельно». Что если нам нужна функция для получения какого-либо поля? Функция, которая возвращает имя, функция для фамилии и т. д.? Мы можем определить их таким образом:
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 назначение полей совсем не так очевидно, и мы значительно выиграем, используя синтаксис записей с именованными полями.