Я разрабатываю класс, представляющий сопоставления ключей и значений, и у меня есть функция, которая в основном похожа на alterF:

class C t where
  ...
  alterF :: Functor f => 
    (Maybe (Value t) -> f (Maybe (Value t))) -> Key t -> t -> f t

К сожалению, это нарушает работу GeneralisedNewtypeDeriving. В некоторых случаях это разумно, поскольку GeneralisedNewtypeDeriving, насколько я понимаю, по существу использует Coercible и функция coerce. Coercible представляет типы, которые репрезентативно равны, т.е. они имеют одинаковое представление во время выполнения, поэтому мы можем конвертировать между ними бесплатно. Например, учитывая:

newtype T a = T a

У нас есть:

Coercible a (T a)
Coercible (T a) a

Но у нас нет (в общем):

Coercible (f a) (f (T a))
Coercible (f (T a)) (f a)

Например, GADT нарушают это репрезентативное равенство. Но есть много значений f, которые действительно работают. Например:

Coercible (Maybe a) (Maybe (T a))
Coercible (Maybe (T a)) (Maybe a)
Coercible [a] [T a]
Coercible [T a] [a]
Coercible (Identity a) (Identity (T a))
Coercible (Identity (T a)) (Identity a)

Мне также приходит в голову, что этот экземпляр можно было бы написать:

Functor f => Coercible (f a) (f (T a))
Functor f => Coercible (f (T a)) (f a)

Просто использую fmap. В отличие от обычного coerce, это не будет бесплатным во время выполнения, но будет работать.

Итак, у меня есть класс с 10 функциями, 9 из которых отлично работают с GeneralisedNewtypeDeriving. Есть только последняя проблема, которой нет, и которую можно решить механически с помощью fmap. Должен ли я писать собственные реализации обертывания / распаковки для всех функций моего класса, или есть способ потребовать, чтобы я написал реализацию только для проблемной функции, или, в качестве альтернативы, уговорил GHC использовать fmap как часть его { {X3}}?

6
Clinton 18 Фев 2018 в 07:50

1 ответ

Лучший ответ

Если f является Functor, вы можете сделать для него "репрезентативную оболочку"

data Rep f a where
    Rep :: (b -> a) -> f b -> Rep f a

Которая изоморфна f, за исключением того, что она репрезентативна в a, что по сути является экзистенциальной квантификацией по любой номинальной дисперсии, которую может иметь f. Я думаю, что у этой конструкции есть какое-то причудливое название теории категорий, но я не помню, что это такое. Чтобы вернуть f a из Rep f a, вам нужно использовать вытяжку f Functor.

Вы можете использовать эту оболочку в своем методе, гарантируя, что ваш класс изменяется репрезентативно.

alterFRep :: (Functor f) 
          => (Maybe (Value t) -> Rep f (Maybe (Value t))) -> Key t -> t -> Rep f t

А затем превратите настоящий «метод» в обычную функцию в его терминах, используя изоморфизм с Rep f. Вы также можете создать удобный метод для авторов экземпляров:

toAlterFRep :: 
    (forall f t. (Functor f) => (Maybe (Value t) -> f (Maybe (Value t))) -> Key t -> t -> f t)
 -> (forall f t. (Functor f) => (Maybe (Value t) -> Rep f (Maybe (Value t))) -> Key t -> t -> Rep f t)

Поэтому им не нужно беспокоиться о том, что такое, черт возьми, Rep, они просто обычно реализуют alterF и используют на нем toAlterFRep.

6
luqui 18 Фев 2018 в 14:13