- Любовные романы
- Фантастика и фэнтези
- Ненаучная фантастика
- Ироническое фэнтези
- Научная Фантастика
- Фэнтези
- Ужасы и Мистика
- Боевая фантастика
- Альтернативная история
- Космическая фантастика
- Попаданцы
- Юмористическая фантастика
- Героическая фантастика
- Детективная фантастика
- Социально-психологическая
- Боевое фэнтези
- Русское фэнтези
- Киберпанк
- Романтическая фантастика
- Городская фантастика
- Технофэнтези
- Мистика
- Разная фантастика
- Иностранное фэнтези
- Историческое фэнтези
- LitRPG
- Эпическая фантастика
- Зарубежная фантастика
- Городское фентези
- Космоопера
- Разное фэнтези
- Книги магов
- Любовное фэнтези
- Постапокалипсис
- Бизнес
- Историческая фантастика
- Социально-философская фантастика
- Сказочная фантастика
- Стимпанк
- Романтическое фэнтези
- Ироническая фантастика
- Детективы и Триллеры
- Проза
- Юмор
- Феерия
- Новелла
- Русская классическая проза
- Современная проза
- Повести
- Контркультура
- Русская современная проза
- Историческая проза
- Проза
- Классическая проза
- Советская классическая проза
- О войне
- Зарубежная современная проза
- Рассказы
- Зарубежная классика
- Очерки
- Антисоветская литература
- Магический реализм
- Разное
- Сентиментальная проза
- Афоризмы
- Эссе
- Эпистолярная проза
- Семейный роман/Семейная сага
- Поэзия, Драматургия
- Приключения
- Детская литература
- Загадки
- Книга-игра
- Детская проза
- Детские приключения
- Сказка
- Прочая детская литература
- Детская фантастика
- Детские стихи
- Детская образовательная литература
- Детские остросюжетные
- Учебная литература
- Зарубежные детские книги
- Детский фольклор
- Буквари
- Книги для подростков
- Школьные учебники
- Внеклассное чтение
- Книги для дошкольников
- Детская познавательная и развивающая литература
- Детские детективы
- Домоводство, Дом и семья
- Юмор
- Документальные книги
- Бизнес
- Работа с клиентами
- Тайм-менеджмент
- Кадровый менеджмент
- Экономика
- Менеджмент и кадры
- Управление, подбор персонала
- О бизнесе популярно
- Интернет-бизнес
- Личные финансы
- Делопроизводство, офис
- Маркетинг, PR, реклама
- Поиск работы
- Бизнес
- Банковское дело
- Малый бизнес
- Ценные бумаги и инвестиции
- Краткое содержание
- Бухучет и аудит
- Ораторское искусство / риторика
- Корпоративная культура, бизнес
- Финансы
- Государственное и муниципальное управление
- Менеджмент
- Зарубежная деловая литература
- Продажи
- Переговоры
- Личная эффективность
- Торговля
- Научные и научно-популярные книги
- Биофизика
- География
- Экология
- Биохимия
- Рефераты
- Культурология
- Техническая литература
- История
- Психология
- Медицина
- Прочая научная литература
- Юриспруденция
- Биология
- Политика
- Литературоведение
- Религиоведение
- Научпоп
- Психология, личное
- Математика
- Психотерапия
- Социология
- Воспитание детей, педагогика
- Языкознание
- Беременность, ожидание детей
- Транспорт, военная техника
- Детская психология
- Науки: разное
- Педагогика
- Зарубежная психология
- Иностранные языки
- Филология
- Радиотехника
- Деловая литература
- Физика
- Альтернативная медицина
- Химия
- Государство и право
- Обществознание
- Образовательная литература
- Учебники
- Зоология
- Архитектура
- Науки о космосе
- Ботаника
- Астрология
- Ветеринария
- История Европы
- География
- Зарубежная публицистика
- О животных
- Шпаргалки
- Разная литература
- Зарубежная литература о культуре и искусстве
- Пословицы, поговорки
- Боевые искусства
- Прочее
- Периодические издания
- Фанфик
- Военное
- Цитаты из афоризмов
- Гиды, путеводители
- Литература 19 века
- Зарубежная образовательная литература
- Военная история
- Кино
- Современная литература
- Военная техника, оружие
- Культура и искусство
- Музыка, музыканты
- Газеты и журналы
- Современная зарубежная литература
- Визуальные искусства
- Отраслевые издания
- Шахматы
- Недвижимость
- Великолепные истории
- Музыка, танцы
- Авто и ПДД
- Изобразительное искусство, фотография
- Истории из жизни
- Готические новеллы
- Начинающие авторы
- Спецслужбы
- Подростковая литература
- Зарубежная прикладная литература
- Религия и духовность
- Старинная литература
- Справочная литература
- Компьютеры и Интернет
- Блог
Изучай Haskell во имя добра! - Миран Липовача
Шрифт:
Интервал:
Закладка:
data IntMaybe = INothing | IJust Int
data StringMaybe = SNothing | SJust String
data ShapeMaybe = ShNothing | ShJust Shape
Более того, мы можем использовать типы-параметры для определения самого обобщённого Maybe, который может содержать данные вообще любых типов!
Обратите внимание: тип значения Nothing – Maybe a. Это полиморфный тип: в его имени присутствует типовая переменная – конкретнее, переменная a в типе Maybe a. Если некоторая функция принимает параметр типа Maybe Int, мы можем передать ей значение Nothing, так как оно не содержит значения, которое могло бы этому препятствовать. Тип Maybe a может вести себя как Maybe Int, точно так же как значение 5 может рассматриваться как значение типа Int или Double. Аналогичным образом тип пустого списка – это [a]. Пустой список может вести себя как список чего угодно. Вот почему можно производить такие операции, как [1,2,3] ++ [] и ["ха","ха","ха"] ++ [].
Параметризовать ли машины?
Когда имеет смысл применять типовые параметры? Обычно мы используем их, когда наш тип данных должен уметь сохранять внутри себя любой другой тип, как это делает Maybe a. Если ваш тип – это некоторая «обёртка», использование типов-параметров оправданно. Мы могли бы изменить наш тип данных Car с такого:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
на такой:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
Но выиграем ли мы в чём-нибудь? Ответ – вероятно, нет, потому что впоследствии мы всё равно определим функции, которые работают с типом Car String String Int. Например, используя первое определение Car, мы могли бы создать функцию, которая отображает свойства автомобиля в виде понятного текста:
tellCar :: Car –> String
tellCar (Car {company = c, model = m, year = y}) =
"Автомобиль " ++ c ++ " " ++ m ++ ", год: " ++ show y
ghci> let stang = Car {company="Форд", model="Мустанг", year=1967}
ghci> tellCar stang
"Автомобиль Форд Мустанг, год: 1967"
Приятная маленькая функция. Декларация типа функции красива и понятна. А что если Car – это Car a b c?
tellCar :: (Show a) => Car String String a –> String
tellCar (Car {company = c, model = m, year = y}) =
"Автомобиль " ++ c ++ " " ++ m ++ ", год: " ++ show y
Мы вынуждены заставить функцию принимать параметр Car типа (Show a) => Car String String a. Как видите, декларация типа функции более сложна; единственное преимущество, которое здесь имеется, – мы можем использовать любой тип, имеющий экземпляр класса Show, как тип для типовой переменной c.
ghci> tellCar (Car "Форд" "Мустанг" 1967)
"Автомобиль Форд Мустанг, год: 1967"
ghci> tellCar (Car "Форд" "Мустанг" "тысяча девятьсот шестьдесят седьмой")
"Автомобиль Форд Мустанг, год: "тысяча девятьсот шестьдесят седьмой""
ghci> :t Car "Форд" "Мустанг" 1967
Car "Форд" "Мустанг" 1967 :: (Num t) => Car [Char] [Char] t
ghci> :t Car "Форд" "Мустанг" "тысяча девятьсот шестьдесят седьмой"
Car "Форд" "Мустанг" "тысяча девятьсот шестьдесят седьмой"
:: Car [Char] [Char] [Char]
На практике мы всё равно в большинстве случаев использовали бы Car String String Int, так что в параметризации типа Car большого смысла нет. Обычно мы параметризируем типы, когда для работы нашего типа неважно, что в нём хранится. Список элементов – это просто список элементов, и неважно, какого они типа: список работает вне зависимости от этого. Если мы хотим суммировать список чисел, то в суммирующей функции можем уточнить, что нам нужен именно список чисел. То же самое верно и для типа Maybe. Он предоставляет возможность не иметь никакого значения или иметь какое-то одно значение. Тип хранимого значения не важен.
Ещё один известный нам пример параметризованного типа – отображения Map k v из модуля Data.Map. Параметр k – это тип ключей в отображении, параметр v – тип значений. Это отличный пример правильного использования параметризации типов. Параметризация отображений позволяет нам использовать любые типы, требуя лишь, чтобы тип ключа имел экземпляр класса Ord. Если бы мы определяли тип для отображений, то могли бы добавить ограничение на класс типа в объявлении:
data (Ord k) => Map k v = ...
Тем не менее в языке Haskell принято соглашение никогда не использовать ограничения класса типов при объявлении типов данных. Почему? Потому что серьёзных преимуществ мы не получим, но в конце концов будем использовать всё больше ограничений, даже если они не нужны. Поместим ли мы ограничение (Ord k) в декларацию типа или не поместим – всё равно придётся указывать его при объявлении функций, предполагающих, что ключ может быть упорядочен. Но если мы не поместим ограничение в объявлении типа, нам не придётся писать его в тех функциях, которым неважно, может ключ быть упорядочен или нет. Пример такой функции – toList :: Map k a –> [(k, a)]. Если бы Map k a имел ограничение типа в объявлении, тип для функции toList был бы таким: toList :: (Ord k) => Map k a –> [(k, a)], даже несмотря на то что функция не сравнивает элементы друг с другом.
Так что не помещайте ограничения типов в декларации типов данных, даже если это имело бы смысл, потому что вам всё равно придётся помещать ограничения в декларации типов функций.
Векторы судьбы
Давайте реализуем трёхмерный вектор и несколько операций для него. Мы будем использовать параметризованный тип, потому что хоть вектор и содержит только числовые параметры, он должен поддерживать разные типы чисел.
data Vector a = Vector a a a deriving (Show)
vplus :: (Num a) => Vector a –> Vector a –> Vector a
(Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
scalarProd :: (Num a) => Vector a –> Vector a –> a
(Vector i j k) `scalarProd` (Vector l m n) = i*l + j*m + k*n
vmult :: (Num a) => Vector a –> a –> Vector a
(Vector i j k) `vmult` m = Vector (i*m) (j*m) (k*m)
Функция vplus складывает два вектора путём сложения соответствующих координат. Функция scalarProd используется для вычисления скалярного произведения двух векторов, функция vmult – для умножения вектора на константу.
Эти функции могут работать с типами Vector Int, Vector Integer, Vector Float и другими, до тех пор пока тип-параметр a из определения Vector a принадлежит классу типов Num. По типам функций можно заметить, что они работают только с векторами одного типа, и все координаты вектора также должны иметь одинаковый тип. Обратите внимание на то, что мы не поместили ограничение класса Num в декларацию типа данных, так как нам всё равно бы пришлось повторять его в функциях.
Ещё раз повторю: очень важно понимать разницу между конструкторами типов и данных. При декларации типа данных часть объявления до знака = представляет собой конструктор типа, а часть объявления после этого знака – конструктор данных (возможны несколько конструкторов, разделённых символом |). Попытка дать функции тип Vector a a a -> Vector a a a -> a будет неудачной, потому что мы должны помещать типы в декларацию типа, и конструктор типа для вектора принимает только один параметр, в то время как конструктор данных принимает три. Давайте поупражняемся с нашими векторами:
ghci> Vector 3 5 8 `vplus` Vector 9 2 8
Vector 12 7 16
ghci> Vector 3 5 8 `vplus` Vector 9 2 8 `vplus` Vector 0 2 3
Vector 12 9 19
ghci> Vector 3 9 7 `vmult` 10
Vector 30 90 70
ghci> Vector 4 9 5 `scalarProd` Vector 9.0 2.0 4.0
74.0
ghci> Vector 2 9 3 `vmult` (Vector 4 9 5 `scalarProd`
Vector 9 2 4) Vector 148 666 222
Производные экземпляры
В разделе «Классы типов» главы 2 приводились базовые сведения о классах типов. Мы упомянули, что класс типов – это нечто вроде интерфейса, который определяет некоторое поведение. Тип может быть сделан экземпляром класса, если поддерживает это поведение. Пример: тип Int есть экземпляр класса типов Eq, потому что класс Eq определяет поведение для сущностей, которые могут быть проверены на равенство. Так как целые числа можно проверить на равенство, тип Int имеет экземпляр для класса Eq. Реальная польза от этого видна при использовании функций, которые служат интерфейсом класса Eq, – операторов == и /=. Если тип имеет определённый экземпляр класса Eq, мы можем применять оператор == к значениям этого типа. Вот почему выражения 4 == 4 и "раз" /= "два" проходят проверку типов.

