Откуда мы знаем, что этот тип имеет сорт * –> (* –> *) – > *
? Именованные поля в алгебраических типах данных сделаны для того, чтобы хранить значения, так что они по определению должны иметь сорт *
. Мы предполагаем сорт *
для типа a
; это означает, что тип b
принимает один тип как параметр. Таким образом, его сорт – * –>
*
. Теперь мы знаем сорта типов a
и b
; так как они являются параметрами для типа Frank
, можно показать, что тип Frank
имеет сорт * –> (* –> *) – > *
. Первая *
обозначает сорт типа a
; (*
–> *)
обозначает сорт типа b
. Давайте создадим несколько значений типа Frank
и проверим их типы.
ghci> :t Frank {frankField = Just "ХА-ХА"}
Frank {frankField = Just "ХА-ХА"} :: Frank [Char] Maybe
ghci> :t Frank {frankField = Node 'a' EmptyTree EmptyTree}
Frank {frankField = Node 'a' EmptyTree EmptyTree} :: Frank Char Tree
ghci> :t Frank {frankField = "ДА"}
Frank {frankField = "ДА"} :: Frank Char []
Гм-м-м… Так как поле frankField
имеет тип вида a b
, его значения должны иметь типы похожего вида. Например, это может быть Just "ХА-ХА"
, тип в этом примере – Maybe [Char]
, или ['Д','А']
(тип [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
по первому полю.