Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программирование » Изучай Haskell во имя добра! - Миран Липовача

Изучай Haskell во имя добра! - Миран Липовача

Читать онлайн Изучай Haskell во имя добра! - Миран Липовача

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 24 25 26 27 28 29 30 31 32 ... 96
Перейти на страницу:

Модуль Data.Map использует такой подход. Вы не можете создать отображение напрямую при помощи соответствующего конструктора данных, потому что такой конструктор не экспортирован. Однако можно создавать отображения, вызвав одну из вспомогательных функций, например Map.fromList. Разработчики, ответственные за Data.Map, в любой момент могут поменять внутреннее представление отображений, и при этом ни одна существующая программа не сломается.

Разумеется, экспорт конструкторов данных для типов попроще вполне допустим.

Синтаксис записи с именованными полями

Есть ещё один способ определить тип данных. Предположим, что перед нами поставлена задача создать тип данных для описания человека. Данные, которые мы намереваемся хранить, – имя, фамилия, возраст, рост, телефон и любимый сорт мороженого. (Не знаю, как насчёт вас, но это всё, что я хотел бы знать о человеке!) Давайте опишем такой тип:

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 назначение полей совсем не так очевидно, и мы значительно выиграем, используя синтаксис записей с именованными полями.

Параметры типа

Конструктор данных может принимать несколько параметров-значений и возвращать новое значение. Например, конструктор Car принимает три значения и возвращает одно – экземпляр типа Car. Таким же образом конструкторы типа могут принимать типы-параметры и создавать новые типы. На первый взгляд это несколько абстрактно, но на самом деле не так уж сложно. Если вы знакомы с шаблонами в языке С++, то увидите некоторые параллели. Чтобы получить более ясное представление о том, как работают типы-параметры, давайте посмотрим, как реализованы типы, с которыми мы уже встречались.

data Maybe a = Nothing | Just a

В данном примере идентификатор a – тип-параметр (переменная типа, типовая переменная). Так как в выражении присутствует тип-параметр, мы называем идентификатор Maybe конструктором типов. В зависимости от того, какой тип данных мы хотим сохранять в типе Maybe, когда он не Nothing, конструктор типа может производить такие типы, как Maybe Int, Maybe Car, Maybe String и т. д. Ни одно значение не может иметь тип «просто Maybe», потому что это не тип как таковой – это конструктор типов. Для того чтобы он стал настоящим типом, значения которого можно создать, мы должны указать все типы-параметры в конструкторе типа.

Итак, если мы передадим тип Char как параметр в тип Maybe, то получим тип Maybe Char. Для примера: значение Just 'a' имеет тип Maybe Char.

Обычно нам не приходится явно передавать параметры конструкторам типов, поскольку в языке Haskell есть вывод типов. Поэтому когда мы создаём значение Just 'a', Haskell тут же определяет его тип – Maybe Char.

Если мы всё же хотим явно указать тип как параметр, это нужно делать в типовой части выражений, то есть после символа ::. Явное указание типа может понадобиться, если мы, к примеру, хотим, чтобы значение Just 3 имело тип Maybe Int. По умолчанию Haskell выведет тип (Num a) => Maybe a. Воспользуемся явным аннотированием типа:

ghci> Just 3 :: Maybe Int

Just 3

Может, вы и не знали, но мы использовали тип, у которого были типы-параметры ещё до типа Maybe. Этот тип – список. Несмотря на то что дело несколько скрывается синтаксическим сахаром, конструктор списка принимает параметр для того, чтобы создать конкретный тип. Значения могут иметь тип [Int], [Char], [[String]], но вы не можете создать значение с типом [].

ПРИМЕЧАНИЕ. Мы называем тип конкретным, если он вообще не принимает никаких параметров (например, Int или Bool) либо если параметры в типе заполнены (например, Maybe Char). Если у вас есть какое-то значение, у него всегда конкретный тип.

Давайте поиграем с типом Maybe:

ghci> Just "Ха-ха"

Just "Ха-ха"

ghci> Just 84

Just 84

ghci> :t Just "Ха-ха"

Just "Ха-ха" :: Maybe [Char]

ghci> :t Just 84

Just 84 :: (Num t) => Maybe t

ghci> :t Nothing

Nothing :: Maybe a

ghci> Just 10 :: Maybe Double

Just 10.0

Типы-параметры полезны потому, что мы можем с их помощью создавать различные типы, в зависимости от того, какой тип нам надо хранить в нашем типе данных. К примеру, можно объявить отдельные Maybe-подобные типы данных для любых типов:

data IntMaybe = INothing | IJust Int

data StringMaybe = SNothing | SJust String

data ShapeMaybe = ShNothing | ShJust Shape

Более того, мы можем использовать типы-параметры для определения самого обобщённого Maybe, который может содержать данные вообще любых типов!

1 ... 24 25 26 27 28 29 30 31 32 ... 96
Перейти на страницу:
Тут вы можете бесплатно читать книгу Изучай Haskell во имя добра! - Миран Липовача.
Комментарии