Почему параметризованный экземпляр типа работает без указания параметра типа - PullRequest
9 голосов
/ 15 мая 2019

При наличии параметризованного типа: data A a=X a| Y

Я попытался (успешно) реализовать Functor и Applicative без указания параметра типа:

instance Functor A where вместо instance Functor (A a) where.
Почему это работает?

Глядя в LYAH , кажется, что во всех примерах параметр типа указан во всех их typeclass instances.Когда следует пренебрегать параметром типа?

Ответы [ 2 ]

11 голосов
/ 15 мая 2019

instance Functor A where вместо instance Functor (A a) where. Почему это работает?

Мне легче понять, используя систему сортировки GHC. Давайте начнем с простого случая и поэкспериментируем в GHCi:

> :k Eq Int
Eq Int :: Constraint

Это говорит нам о том, что Eq Int является ограничением, некоторым свойством, которое может быть проверено во время проверки типа. Действительно, если мы введем check (12 :: Int) == (42 :: Int), компилятор проверит, что целые числа можно сравнить, разрешив ограничение Eq Int.

Что такое Eq, имя класса без параметра Int?

> :k Eq
Eq :: * -> Constraint

Это говорит нам о том, что Eq можно рассматривать как функцию от типов (* - это тип типов) до ограничения.

Действительно, в Eq Int, Int является типом, поэтому у нас Int :: * делает Int аргумент с хорошим характером для передачи Eq.

Достаточно классов классов, как насчет конструкторов типов?

> :k Maybe Int
Maybe Int :: *

Не удивительно, Maybe Int это тип

> :k Maybe
Maybe :: * -> *

Maybe - это функция от типов к типам (*->*). Это действительно то, что делает конструктор типа Maybe: отображение типа (Int) на тип (Maybe Int).

Вернуться к исходному вопросу. Почему мы не можем написать instance Functor (A a), но мы можем вместо этого написать instance Functor A? Ну, у нас есть это

> :k A Int
A Int :: *
> :k A
A :: * -> *

и, самое главное,

> :k Functor
Functor :: (* -> *) -> Constraint

Это говорит о том, что тип класса Functor отличается от класса Eq. Eq ожидает тип в качестве аргумента, в то время как Functor ожидает что-то вроде (* -> *) в качестве аргумента. A соответствует этому виду, а A Int - нет.

Это происходит, когда в определении класса аргумент применяется к какому-либо другому типу. Э.Г.

class C1 a where
   foo :: a -> Bool

приводит к C1 :: * -> Constraint. Вместо

class C2 f where
   bar :: f Int -> Bool

приводит к C2 :: (* -> *) -> Constraint, поскольку f само по себе не используется как тип, f Int - так, f должен быть параметризованным типом вида * -> *.

7 голосов
/ 15 мая 2019

Когда следует пренебрегать параметром типа?

Параметр типа не является ", которым пренебрегают ". Давайте сначала взглянем на класс Functor:

class Functor f where
    fmap :: (a -> b) -> <b>f a</b> -> <b>f b</b>

Обратите внимание на f a и f b в сигнатуре типа. Таким образом, здесь мы «конструируем типы» для сигнатуры типа fmap.

Здесь f, таким образом, в основном является функцией, которая принимает тип и преобразует его в тип. Например, если f ~ A, если a ~ Int, то f a генерирует тип A Int.

Узнайте, что такое Haskell для большого блага! фактически объясняет это в своей главе о Функторах, Аппликативах и Моноидах :

Много раз мы хотим создавать экземпляры наших типов определенного типа классы, но параметры типа просто не соответствуют тому, что мы хотим сделать. сделать Maybe экземпляром Functor легко, потому что класс Functor определяется следующим образом:

class Functor f where  
    fmap :: (a -> b) -> f a -> f b 

Итак, мы просто начнем с:

  instance Functor Maybe where

А затем реализовать fmap. Все параметры типа складываются, потому что Maybe занимает место f в определение класса Functor и, если мы посмотрим на fmap как если бы он работал только на Maybe, он ведет себя так:

fmap :: (a -> b) -> Maybe a -> Maybe b

(...)

...