. Сорт – это нечто вроде «типа типов». Звучит немного странно, но на самом деле это очень мощная концепция.
Что такое сорта и для чего они полезны? Давайте посмотрим сорт типа, используя команду >:k
в интерпретаторе GHCi.
>ghci> :k Int
>Int :: *
Звёздочка? Как затейливо! Что это значит? Звёздочка обозначает, что тип является конкретным. Конкретный тип – это такой тип, у которого нет типов-параметров; значения могут быть только конкретных типов. Если бы мне надо было прочитать символ >*
вслух (до этого не приходилось), я бы сказал «звёздочка» или просто «тип».
О’кей, теперь посмотрим, каков сорт у типа >Maybe
:
>ghci> :k Maybe
>Maybe :: * –> *
Конструктор типов >Maybe
принимает один конкретный тип (например, >Int
) и возвращает конкретный тип (например, >Maybe Int
). Вот о чём говорит нам сорт. Точно так же тип >Int –> Int
означает, что функция принимает и возвращает значение типа >Int
; сорт >* – > *
означает, что конструктор типов принимает конкретный тип и возвращает конкретный тип. Давайте применим параметр к типу >Maybe
и посмотрим, какого он станет сорта.
>ghci> :k Maybe Int
>Maybe Int :: *
Так я и думал! Мы применили тип-параметр к типу >Maybe
и получили конкретный тип. Можно провести параллель (но не отождествление: типы – это не то же самое, что и сорта) с тем, как если бы мы сделали >:t isUpper
и >:t isUpper 'A'
. У функции >isUpper
тип >Char –> Bool
; выражение >isUpper 'A'
имеет тип >Bool
, потому что его значение – просто >False
. Сорт обоих типов, тем не менее, >*
.
Мы используем команду >:k
для типов, чтобы получить их сорт, так же как используем команду >:t
для значений, чтобы получить их тип. Выше уже было сказано, что типы – это метки значений, а сорта – это метки типов; и в этом они схожи.
Посмотрим на другие сорта.
>ghci> :k Either
>Either :: * –> * –> *
Это говорит о том, что тип >Either
принимает два конкретных типа для того, чтобы вернуть конкретный тип. Выглядит как декларация функции, которая принимает два значения и что-то возвращает. Конструкторы типов являются каррированными (так же, как и функции), поэтому мы можем частично применять их.
>ghci> :k Either String
>Either String :: * –> *
>ghci> :k Either String Int
>Either String Int :: *
Когда нам нужно было сделать для типа >Either
экземпляр класса >Functor
, пришлось частично применить его, потому что класс >Functor
принимает типы только с одним параметром, в то время как у типа >Either
их два. Другими словами, класс >Functor
принимает типы сорта >* –> *
, и нам пришлось частично применить тип >Either
для того, чтобы получить сорт >* –> *
из исходного сорта