Каковы ситуации, когда вы можете/не можете иметь экземпляры функтора для типа данных?


Рассматривая тип с видом * -> *, я пытаюсь найти правила и построить интуицию для того, когда вы можете и когда вы не можете иметь функтор для этого типа.

Пока что правила, которые я вижу, следующие:

  1. нет экземпляра Functor для типов контейнеров, имеющих ограничения о содержащихся в них значениях.

Пример: вы не можете иметь экземпляр Functor для Set, потому что Ord требуется для содержащегося значения

  1. не Functor экземпляр для контравариантных тип данных.

Пример:

newtype Contra a = Contra (a -> Int)

Кроме этого, есть ли другие ситуации?

3 2

3 ответа:

В дополнение к вашим правилам:

  • должен быть добрым * -> *
  • нет экземпляра Functor для типов контейнеров, которые имеют ограничения на содержащиеся значения.
  • нет экземпляра Functor для контравариантных типов данных.

Я бы добавил несколько:

  • естественное расширение "не для контравариантных типов": нет Functor экземпляра для инвариантных типов данных. например data Iso a b = Iso (a -> b) (b -> a)
  • GADTs часто не могут иметь экземпляр Functor. Для пример,

    data Foo a where
        Foo :: Foo Int
    

    Возможно, вы каким-то образом захотите объединить это в правило "только ковариант" (мне не ясно, какая дисперсия это вообще имеет) или правило "неограниченных типов контейнеров" (GADTs вводят равенства типов, которые очень похожи на ограничения).

Однако имейте в виду, что эти правила применяются к Functor только , не к функторам вообще. Я ожидаю, что любой глупый тип (соответствующего вида), который вы можете приготовить, будет функтором на некоторых подходящая категория, тесно связанная с Хаском.

С точки зрения теории категорий конструкторы типа Хаскелла вида *->* определяют отображение объектов в категории Hask (это категория по модулю конечных вопросов, которые я собираюсь удобно игнорировать). Функтор-это отображение объектов и, что более важно, отображение морфизмов. На самом деле, это в первую очередь отображение морфизмов-отображение объектов является, в некотором смысле, побочным эффектом этого. Объекты - это всего лишь конечные точки морфизмов. Это сопоставление морфизмы должны сохранять композицию и идентичность.

В Хаскелле морфизмы являются функциями, и отображение функций реализуется как fmap. Тот факт, что в Хаскелле мы начинаем с отображения объектов, немного отстает. Это работает потому, что синтаксис языка резко ограничивает возможности определения отображения объектов. Такие отображения очень регулярны и довольно часто снабжены каноническими отображениями функций. Например, алгебраические типы данных строятся с использованием продуктов и копродуктов, которые являются функциональными по своей природе (отсюда возможность deriving Functor автоматически). Кроме того, типы функций (категориальные экспоненты) являются функториальными во втором аргументе (и контравариантными в первом). Таким образом, пока мы используем инструменты бикартезианской замкнутой категории (продукты, копродукты и экспоненты), легко построить функториальные отображения объектов.

Функтор должен быть определен для каждого объекта в данной категории., таким образом, типы данных, ограниченные классами типов (например, Set с ограничением Ord), не являются Functors в Hask, но они могут быть функторами в подкатегории Hask. (В Haskell можно определить подкатегории вместе с их собственными функторами.)

В руководстве пользователя Haskell есть расширение -XDerivingFunctor. Вы можете найти полное описание здесь. Он содержит описание всех случаев, когда вывод функтора может завершиться неудачей. В этом описании есть случаи, когда алгоритм проверяет правильность вида и т. д. Но я считаю, что этот список ограничений для алгоритма является исчерпывающим.

Например, другой случай, когда тип имеет вид * -> * но не может быть экземпляром Functor:

Последняя переменная типа данных используется в - Ограничение XExistentialQuantification, или уточняется в GADT