Изучай Haskell во имя добра! (Липовача) - страница 114

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