Asa*_*din 6 haskell deriving derivingvia
假设我们有一个类,Foo
这样的实例Foo f
为我们提供了实现Functor f
,Foldable f
和所需的一切Traversable f
。为了避免重叠实例,可以在 newtype 包装器之间Foo
和Functor, Foldable, Traversable
下见证这种关系:
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 = _
Run Code Online (Sandbox Code Playgroud)
现在假设我们有一些类型构造函数:
data Bar a = Bar {- ... -}
Run Code Online (Sandbox Code Playgroud)
这样有一个:
instance Foo Bar
where
{- ... -}
Run Code Online (Sandbox Code Playgroud)
我们想配备Bar
它的“ Foo
-ness”所隐含的实例。既然Bar a
是Coercible
要FoonessOf Bar a
,我们就希望能够推导实例via
的FoonessOf Bar
:
deriving via (FoonessOf Bar) instance Functor Bar
deriving via (FoonessOf Bar) instance Foldable Bar
Run Code Online (Sandbox Code Playgroud)
这适用于类型类,例如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’
——————————————————————————————————————————————————————————————————————————————
...
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:
Traversable Bar
?Traversable
可以通过 newtype 派生的类进行一些修改?我怀疑1.的答案是:不,情况无法挽救,并且不可能获得Traversable
使用的实例DerivingVia
。
就 2. 而言,尝试在更简单的上下文中重现问题很有用。考虑以下:
\n-- Remember to turn on ScopedTypeVariables!\n\ndata A = A\nnewtype B = B A\n\na :: forall f. f A -> f A\na = id\n\nb :: forall f. f B -> f B\nb = coerce $ a @f\n
Run Code Online (Sandbox Code Playgroud)\n看起来这应该可行,但可惜的是:
\n[typecheck -Wdeferred-type-errors] [E] \xe2\x80\xa2 Couldn\'t match representation of type \xe2\x80\x98f A\xe2\x80\x99 with that of \xe2\x80\x98f B\xe2\x80\x99\n arising from a use of \xe2\x80\x98coerce\xe2\x80\x99\n NB: We cannot know what roles the parameters to \xe2\x80\x98f\xe2\x80\x99 have;\n we must assume that the role is nominal\n\xe2\x80\xa2 In the expression: coerce $ a @f\n In an equation for \xe2\x80\x98b\xe2\x80\x99: b = coerce $ a @f\n\xe2\x80\xa2 Relevant bindings include\n b :: f B -> f B\n
Run Code Online (Sandbox Code Playgroud)\n问题与类型构造函数参数的“角色”以及角色推断的工作方式有关。就我们的目的而言,角色有两种:“代表性”和“非代表性”。同样出于我们的目的,两者之间的差异可以近似如下:F :: Type -> Type
如果存在 的实例,则类型构造函数具有“代表性”角色的参数Representational F
,其中:
type Representational :: (Type -> Type) -> Constraint\ntype Representational f = forall x y. Coercible x y => Coercible (f x) (f y)\n
Run Code Online (Sandbox Code Playgroud)\n否则, 的参数F
是非代表性的。
类型检查器允许您在不同的位置注释类型参数的角色(尽管很奇怪,不是那种类型)。遗憾的是,没有办法注释高级类型变量的角色。然而,我们能做的就是直接要求Representational f
:
b\' :: forall f. Representational f => f B -> f B\nb\' = coerce $ a @f\n
Run Code Online (Sandbox Code Playgroud)\n现在进行类型检查。这提出了一种调整Traversable
类型类以使其可通过强制导出的可能方法。
现在让我们看看操作的Traversable
类型sequenceA
:
type Representational :: (Type -> Type) -> Constraint\ntype Representational f = forall x y. Coercible x y => Coercible (f x) (f y)\n
Run Code Online (Sandbox Code Playgroud)\n注意:又出现了令人讨厌的情况forall f
,意思f
是具有名义角色的类型参数。
要做DerivingVia
的是尝试coerce
:
b\' :: forall f. Representational f => f B -> f B\nb\' = coerce $ a @f\n
Run Code Online (Sandbox Code Playgroud)\n和:
\nclass Traversable t\n where\n sequenceA :: forall f. Applicative f => forall a. t (f a) -> f (t a)\n {- ... -}\n
Run Code Online (Sandbox Code Playgroud)\n尽管T1
( FoonessOf Bar
) 和T2
( Bar
) 是“参数化”可强制转换的,但这种强制转换将会失败,因为整个操作的强制转换最终会分解为类型检查器抱怨的强制转换:
sequenceA @T1 :: forall f. Applicative f => forall a. T1 (f a) -> f (T2 a)\n
Run Code Online (Sandbox Code Playgroud)\n正如我们所讨论的,这不起作用,因为f
\ 的参数被认为具有名义角色。
与上面的简化示例一样,修复方法很简单:只需询问Representational f
:
sequenceA @T2 :: forall f. Applicative f => forall a. T2 (f a) -> f (T2 a)\n
Run Code Online (Sandbox Code Playgroud)\n现在我们终于可以Traversable\'
通过以下方式派生出一个实例FoonessOf Bar
:
Couldn\'t match representation of type\n\xe2\x80\x98f1 (Foo Bar a1)\xe2\x80\x99\nwith that of\n\xe2\x80\x98f1 (Bar a1)\xe2\x80\x99\n
Run Code Online (Sandbox Code Playgroud)\n