Можно ли получить проходимый экземпляр с помощью другого конструктора типов?

#haskell #deriving #derivingvia

Вопрос:

Предположим , у нас есть какой-то класс Foo , экземпляр Foo f которого дает нам все необходимое для реализации Functor f , Foldable f и Traversable f . Чтобы избежать дублирования экземпляров, можно наблюдать эту связь между Foo Functor, Foldable, Traversable оболочкой newtype и под ней:

 type Foo :: (Type -> Type) -> Constraint
class Foo f
  where
  {- ... -}

type FoonessOf :: (Type -> Type) -> Type -> Type
newtype FoonessOf f a = FoonessOf (f a)

instance Foo f => Functor (FoonessOf f)
  where
  fmap = _

instance Foo f => Foldable (FoonessOf f)
  where
  foldMap = _

instance Foo f => Traversable (FoonessOf f)
  where
  traverse = _
 

Теперь предположим, что у нас есть какой-то конструктор типов:

 data Bar a = Bar {- ... -}
 

таким образом, что существует:

 instance Foo Bar
  where
  {- ... -}
 

Мы хотели бы Bar снабдить его примерами, подразумеваемыми его « Foo -ностью». Поскольку Bar a это Coercible FoonessOf Bar a так , мы ожидаем, что сможем получить экземпляры via , которые FoonessOf Bar :

 deriving via (FoonessOf Bar) instance Functor Bar
deriving via (FoonessOf Bar) instance Foldable Bar
 

И это удобно работает для таких типов, как Functor и Foldable

К сожалению , когда мы пытаемся сделать то же самое с Traversable , все идет наперекосяк:

 [typecheck -Wdeferred-type-errors] [E] • Couldn't match representation of type ‘f1 (Foo Bar a1)’
                           with that of ‘f1 (Bar a1)’
    arising from a use of ‘ghc-prim-0.6.1:GHC.Prim.coerce’
  NB: We cannot know what roles the parameters to ‘f1’ have;
    we must assume that the role is nominal
• In the expression:
      ghc-prim-0.6.1:GHC.Prim.coerce
        @(Foo Bar (f a) -> f (Foo Bar a)) @(Bar (f a) -> f (Bar a))
        (sequenceA @(Foo Bar)) ::
        forall (f :: TYPE ghc-prim-0.6.1:GHC.Types.LiftedRep
                     -> TYPE ghc-prim-0.6.1:GHC.Types.LiftedRep)
               (a :: TYPE ghc-prim-0.6.1:GHC.Types.LiftedRep).
        Applicative f => Bar (f a) -> f (Bar a)
  In an equation for ‘sequenceA’:
      sequenceA
        = ghc-prim-0.6.1:GHC.Prim.coerce
            @(Foo Bar (f a) -> f (Foo Bar a)) @(Bar (f a) -> f (Bar a))
            (sequenceA @(Foo Bar)) ::
            forall (f :: TYPE ghc-prim-0.6.1:GHC.Types.LiftedRep
                         -> TYPE ghc-prim-0.6.1:GHC.Types.LiftedRep)
                   (a :: TYPE ghc-prim-0.6.1:GHC.Types.LiftedRep).
            Applicative f => Bar (f a) -> f (Bar a)
  When typechecking the code for ‘sequenceA’
    in a derived instance for ‘Traversable Bar’:
    To see the code I am typechecking, use -ddump-deriv
  In the instance declaration for ‘Traversable Bar’
——————————————————————————————————————————————————————————————————————————————
...
 

Итак, у меня есть следующие вопросы:

  1. Можно ли придумать какую-то другую схему для вывода-через экземпляр Traversable Bar ?
  2. Можно ли придумать какую-то модификацию Traversable класса, которая может быть получена с помощью нового типа?

Комментарии:

1. Я давно хотел, чтобы этот Traversable метод был fmapTraverse :: Applicative f => (t b -> r) -> (a -> f b) -> t a -> f r таким . Это решило бы проблему с ролью типа, а также удалило бы лишние fmap буквы s из множества обходов. Там также может быть coerceTraverse :: (Applicative f, Coercible (t b) r) => (a -> f b) -> t a -> f r для дальнейшего повышения производительности магия.

Ответ №1:

Я подозреваю, что ответ на 1. таков: нет, ситуацию нельзя спасти, и невозможно получить экземпляр Traversable использования DerivingVia .


Что касается 2., полезно попытаться воспроизвести проблему в более простом контексте. Учесть следующее:

 -- Remember to turn on ScopedTypeVariables!

data A = A
newtype B = B A

a :: forall f. f A -> f A
a = id

b :: forall f. f B -> f B
b = coerce $ a @f
 

Кажется, это должно сработать, но, увы:

 [typecheck -Wdeferred-type-errors] [E] • Couldn't match representation of type ‘f A’ with that of ‘f B’
    arising from a use of ‘coerce’
  NB: We cannot know what roles the parameters to ‘f’ have;
    we must assume that the role is nominal
• In the expression: coerce $ a @f
  In an equation for ‘b’: b = coerce $ a @f
• Relevant bindings include
    b :: f B -> f B
 

Проблема связана с «ролями» параметров конструкторов типов и тем, как работает вывод ролей. Для наших целей роли бывают двух видов: «репрезентативные» и «нерепрезентативные». Также для наших целей разница между ними может быть приближена к следующему: конструктор типа F :: Type -> Type имеет параметр «представительной» роли, если существует экземпляр Representational F , где:

 type Representational :: (Type -> Type) -> Constraint
type Representational f = forall x y. Coercible x y => Coercible (f x) (f y)
 

В противном случае параметр of F не является репрезентативным.


Средство проверки типов позволяет вам аннотировать роли параметров типа в разных местах (хотя, как ни странно, не в том виде). К сожалению, нет способа аннотировать роли переменной более высокого типа. Что мы можем сделать, однако, это просто попросить Representational f напрямую:

 b' :: forall f. Representational f => f B -> f B
b' = coerce $ a @f
 

который теперь проверяет тип. Это предполагает возможный способ настройки Traversable класса типов, чтобы сделать его выводимым с помощью принуждения.


Теперь давайте посмотрим на тип Traversable операции sequenceA :

 class Traversable t
  where
  sequenceA :: forall f. Applicative f => forall a. t (f a) -> f (t a)
  {- ... -}
 

NB: Опять это надоедливо forall f , то f есть берется параметр типа номинальной роли.

Что DerivingVia мы собираемся сделать, так это попытаться coerce между:

 sequenceA @T1 :: forall f. Applicative f => forall a. T1 (f a) -> f (T2 a)
 

и:

 sequenceA @T2 :: forall f. Applicative f => forall a. T2 (f a) -> f (T2 a)
 

Несмотря T1 на то, что ( FoonessOf Bar ) и T2 ( Bar ) являются «параметрически» принудительными, это принуждение потерпит неудачу, потому что принуждение всей операции в конечном итоге разложится на принуждение, на которое жаловался проверяющий тип:

 Couldn't match representation of type
‘f1 (Foo Bar a1)’
with that of
‘f1 (Bar a1)’
 

Это не работает из f -за того, что параметр s считается номинальным, как мы обсуждали.

Как и в нашем упрощенном примере выше, исправление простое: просто попросите Representational f :

 type Traversable' :: (Type -> Type) -> Constraint
class Traversable' t
  where
  traverse :: (Representational f, Applicative f) => (a -> f b) -> t (f a) -> f (t b)
 

И теперь, наконец, мы можем получить экземпляр Traversable' с помощью FoonessOf Bar :

 instance Foo f => Traversable' (FoonessOf f)
  where
  traverse = _

deriving via (FoonessOf Bar) instance Traversable' Bar
 

Комментарии:

1. representational лежит между phantom и nominal . Что-то, чей параметр типа имеет призрачную роль, безусловно, будет вашим экземпляром Representational .

2. @dfeuer о, я понимаю. приятно знать, что я отредактировал ответ