(тип >[Char]
; если бы мы использовали наш собственный тип для списка, это был бы >List Char
). Мы видим, что значения типа >Frank
соответствуют сорту типа >Frank
. Сорт >[Char]
– это >*
, тип >Maybe
имеет сорт >* –> *
. Так как мы можем создать значение только конкретного типа и тип значения должен быть полностью определён, каждое значение типа >Frank
имеет сорт >*
.
Сделать для типа >Frank
экземпляр класса >Tofu
довольно просто. Мы видим, что функция >tofu
принимает значение типа >a j
(примером для типа такой формы может быть >Maybe Int
) и возвращает значение типа >t a j
. Если мы заменим тип >Frank
на >t
, результирующий тип будет >Frank
>Int
>Maybe
.
>instance Tofu Frank where
> tofu x = Frank x
Проверяем типы:
>ghci> tofu (Just 'a') :: Frank Char Maybe
>Frank {frankField = Just 'a'}
>ghci> tofu ["ПРИВЕТ"] :: Frank [Char] []
>Frank {frankField = ["ПРИВЕТ"]}
Пусть и без особой практической пользы, но мы потренировали наше понимание типов. Давайте сделаем ещё несколько упражнений из тип-фу. У нас есть такой тип данных:
>data Barry t k p = Barry { yabba :: p, dabba :: t k }
Ну а теперь определим для него экземпляр класса >Functor
. Класс >Functor
принимает типы сорта >*
>–>
>*
, но непохоже, что у типа >Barry
такой сорт. Каков же сорт у типа >Barry
? Мы видим, что он принимает три типа-параметра, так что его сорт будет похож на >(нечто –> нечто –> нечто –> *)
. Наверняка тип >p
– конкретный; он имеет сорт >*
. Для типа >k
мы предполагаем сорт >*
; следовательно, тип >t
имеет сорт >* –> *
. Теперь соединим всё в одну цепочку и получим, что тип >Barry
имеет сорт >(* –> *) –> * –> * –> *
. Давайте проверим это в интерпретаторе GHCi:
>ghci> :k Barry
>Barry :: (* –> *) –> * –> * –> *
Ага, мы были правы. Как приятно! Чтобы сделать для типа >Barry
экземпляр класса >Functor
, мы должны частично применить первые два параметра, после чего у нас останется сорт >* –> *
. Следовательно, начало декларации экземпляра будет таким:
>instance Functor (Barry a b) where
Если бы функция >fmap
была написана специально для типа >Barry
, она бы имела тип
>fmap :: (a –> b) –> Barry c d a –> Barry c d b
Здесь тип-параметр >f
просто заменён частично применённым типом >Barry c d
. Третий параметр типа >Barry
должен измениться, и мы видим, что это удобно сделать таким образом:
>instance Functor (Barry a b) where
> fmap f (Barry {yabba = x, dabba = y}) = Barry {yabba = f x, dabba = y}
Готово! Мы просто отобразили тип >f
по первому полю.
В данной главе мы хорошенько изучили, как работают параметры типов, и как они формализуются с помощью сортов по аналогии с тем, как формализуются параметры функций с помощью декларации типов. Мы провели любопытные параллели между функциями и конструкторами типов, хотя на первый взгляд они и не имеют ничего общего. При реальной работе с языком Haskell обычно не приходится возиться с сортами и делать вывод сортов вручную, как мы делали в этой главе. Обычно вы просто частично применяете свой тип к сорту