Синонимы типов также могут быть параметризованы. Если мы хотим задать синоним для ассоциативного списка и при этом нам нужно, чтобы он мог принимать любые типы для ключей и значений, мы можем сделать так:
type AssocList k v = [(k,v)]
Функция, которая получает значение по ключу в ассоциативном списке, может иметь тип (Eq
k)
=>
k
–>
AssocList
k
v
–>
Maybe
v
. Тип AssocList
– это конструктор типов, который принимает два типа и производит конкретный тип, например AssocList
Int
String
.
Мы можем частично применять функции, чтобы получить новые функции; аналогичным образом можно частично применять типы-параметры и получать новые конструкторы типов. Так же, как мы вызываем функцию, не передавая всех параметров для того, чтобы получить новую функцию, мы будем вызывать и конструктор типа, не указывая всех параметров, и получать частично применённый конструктор типа. Если мы хотим получить тип для отображений (из модуля Data.Map
) с целочисленными ключами, можно сделать так:
type IntMap v = Map Int v
или так:
type IntMap = Map Int
В любом случае конструктор типов IntMap
принимает один параметр – это и будет типом, в который мы будем отображать Int
.
И вот ещё что. Если вы попытаетесь реализовать этот пример, вам потребуется произвести квалифицированный импорт модуля Data.Map
. При квалифицированном импорте перед конструкторами типов также надо ставить имя модуля. Таким образом, мы бы записали: IntMap = Map.Map Int
.
Убедитесь, что вы понимаете различие между конструкторами типов и конструкторами данных. Если мы создали синоним типа IntMap
или AssocList
, это ещё не означает, что можно делать такие вещи, как AssocList [(1,2),(4,5),(7,9)]
. Это означает только то, что мы можем ссылаться на тип, используя другое имя. Можно написать: [(1,2),(3,5),(8,9)] :: AssocList Int Int
, в результате чего числа в списке будут трактоваться как целые – но мы также сможем работать с этим списком как с обычным списком пар целых чисел. Синонимы типов (и вообще типы) могут использоваться в языке Haskell только при объявлении типов. Часть языка, относящаяся к объявлению типов, – собственно объявление типов (то есть при определении данных и типов) или часть объявления после символа ::
(два двоеточия). Символ ::
используется при декларировании или аннотировании типов.
Иди налево, потом направо
Ещё один чудесный тип, принимающий два других в качестве параметров, – это тип Either
. Он определён приблизительно так:
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
У него два конструктора данных. Если используется конструктор Left
, его содержимое имеет тип a
; если Right
– содержимое имеет тип b
. Таким образом, мы можем использовать данный тип для инкапсуляции значения одного из двух типов. Когда мы работаем с типом Either a b
, то обычно используем сопоставление с образцом по Left
и Right
и выполняем действия в зависимости от того, какой вариант совпал.
ghci> Right 20
Right 20
ghci> Left "в00т"
Left "в00т"
ghci> :t Right 'a'
Right 'a' :: Either a Char ghci> :t Left True
Left True :: Either Bool b
Из приведённого примера следует, что типом значения Left
True
является Either
Bool
b
. Первый параметр типа Bool
, поскольку значение создано конструктором Left
; второй же параметр остался полиморфным. Ситуация подобна тому как значение Nothing
имеет тип Maybe
a
.
Мы видели, что тип Maybe
главным образом используется для того, чтобы представить результат вычисления, которое может завершиться неудачей. Но иногда тип Maybe
не так удобен, поскольку значение Nothing
не несёт никакой информации, кроме того что что-то пошло не так. Это нормально для функций, которые могут выдавать ошибку только в одном случае – или если нам просто не интересно, как и почему функция «упала». Поиск в отображении типа Data.Map
может завершиться неудачей, только если искомый ключ не найден, так что мы знаем, что случилось. Но если нам нужно знать, почему не сработала некоторая функция, обычно мы возвращаем результат типа Either a b
, где a
– это некоторый тип, который может нам что-нибудь рассказать о причине ошибки, и b
– результат удачного вычисления. Следовательно, ошибки используют конструктор данных Left
, правильные результаты используют конструктор Right
.