#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’
——————————————————————————————————————————————————————————————————————————————
...
Итак, у меня есть следующие вопросы:
- Можно ли придумать какую-то другую схему для вывода-через экземпляр
Traversable Bar
? - Можно ли придумать какую-то модификацию
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 о, я понимаю. приятно знать, что я отредактировал ответ