[英]Why is GHC contradicting itself when using a Coercible constraint?
Why is GHC inferring unification from coercibility of associated data, and why is it contradicting its own checked type signature to do so?为什么 GHC 从关联数据的强制性推断统一,为什么它与自己的检查类型签名相矛盾?
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
module Lib
(
) where
import Data.Coerce
class Foo a where
data Bar a
data Baz a = Baz
{ foo :: a
, bar :: Bar a
}
type BarSame a b = (Coercible (Bar a) (Bar b), Coercible (Bar b) (Bar a))
withBaz :: forall a b. BarSame a b => (a -> b) -> Baz a -> Baz b
withBaz f Baz{..} = Baz
{ foo = f foo
, bar = coerce bar
}
This is all well and good - GHC will happily compile this code, and is confident that withBaz
has the declared signature.这一切都很好——GHC 将愉快地编译这段代码,并且确信
withBaz
具有声明的签名。
Now, let's try to use it!现在,让我们尝试使用它!
instance (Foo a) => Foo (Maybe a) where
data Bar (Maybe a) = MabyeBar (Bar a)
toMaybeBaz :: Baz a -> Baz (Maybe a)
toMaybeBaz = withBaz Just
This gives an error - but a really weird one:这给出了一个错误 - 但一个非常奇怪的错误:
withBaz Just
^^^^^^^^^^^^
cannot construct the infinite type: a ~ Maybe a
Indeed, if I go into GHCi, and ask it to give me the type of withBaz
:确实,如果我将 go 放入 GHCi,并要求它给我
withBaz
的类型:
ghc>:t withBaz
withBaz :: (b -> b) -> Baz b -> Baz b
That's not the signature I gave it.那不是我给它的签名。
I suspect that GHC is treating the type arguments of withBaz
as though they have to unify, because it's inferring Coercible ab
from Coercible (Bar a) (Bar b)
.我怀疑 GHC 正在处理 withBaz 的
withBaz
类型,就好像它们必须统一一样,因为它是从Coercible (Bar a) (Bar b)
推断出Coercible ab
。 But because it's a data family, they don't even need to be Coercible
- certainly not unifiable.但是因为它是一个数据家族,所以它们甚至不需要是
Coercible
- 当然不是统一的。
The following change fixes the compilation:以下更改修复了编译:
instance (Foo a) => Foo (Maybe a) where
newtype Bar (Maybe a) = MabyeBar (Bar a)
That is - declare the data family as a newtype
, instead of a data
.也就是说,将数据族声明为新
newtype
,而不是data
。 This seems consistent with GHC's treatment of Coercible
in the language in general, in that这似乎与 GHC 在语言中对
Coercible
的处理一致,因为
data Id a = Id a
will not cause a Coercible
instance to be generated - even though it should definitely be coercible to a
.不会导致生成
Coercible
实例 - 即使它绝对应该强制转换a
. With the above declaration, this will error:使用上述声明,这将出错:
wrapId :: a -> Id a
wrapId = coerce
But with a newtype
declaration:但是使用
newtype
声明:
newtype Id a = Id a
then the Coercible
instance exists, and wrapId
compiles.然后
Coercible
实例存在,并且wrapId
编译。
I believe @dfeuer's since-deleted comment about lack of "role" support for type/data families provides the answer.我相信@dfeuer 关于缺乏对类型/数据系列的“角色”支持的评论提供了答案。
For a top-level, data
-defined parametrized type:对于顶级的、
data
定义的参数化类型:
data Foo a = ...
the coercibility of types Foo a
and Foo b
depends on the role of the parameter a
. Foo a
和Foo b
类型的强制力取决于参数a
的作用。 In particular, if a
s role is nominal , then Foo a
and Foo b
are coercible if and only if a
and b
are precisely the same type.特别是,如果
a
的角色是名义上的,那么Foo a
和Foo b
是可强制的当且仅当a
和b
是完全相同的类型。
So, in the program:所以,在程序中:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RoleAnnotations #-}
import Data.Coerce
type role Foo nominal
data Foo a = Foo
foo :: (Coercible (Foo a) (Foo b)) => a -> b
foo = undefined
because of the nominal
role of the parameter a
in Foo a
, the type of foo
is actually simplified to b -> b
:由于
Foo a
中参数a
的nominal
作用, foo
的类型实际上被简化为b -> b
:
λ> :t foo
foo :: b -> b
If the role annotation is changed from nominal
to representational
, the type is simplified to Coercible ab => a -> b
, and if the role is changed to phantom
(the default for this particular declaration of Foo
, as a
does not appear on the right-hand side), the type is simplified to a -> b
.如果角色注释从
nominal
更改为representational
,则类型简化为Coercible ab => a -> b
,并且如果角色更改为phantom
(此特定Foo
声明的默认值,因为a
不会出现在右侧),类型被简化为a -> b
。 This is all as expected and corresponds to the definition of each of these roles.这一切都符合预期,并且对应于每个角色的定义。
Note that if you replaced the declaration of Foo
with:请注意,如果您将
Foo
的声明替换为:
data Foo a = Foo a
then the phantom
role would not longer be permitted, but the inferred types for foo
under the other two roles would be exactly as before.那么
phantom
角色将不再被允许,但其他两个角色下foo
的推断类型将与以前完全相同。
However, there's an important difference if you switch from a data
to a newtype
.但是,如果您从
data
切换到newtype
,则会有一个重要的区别。 With:和:
newtype Foo a = Foo a
you'd discover that even with type role Foo nominal
, the inferred type for foo
would be Coercible ba => a -> b
instead of b -> b
.您会发现,即使使用
type role Foo nominal
Foonominal , foo
的推断类型也会是Coercible ba => a -> b
而不是b -> b
。 That's because the algorithm for type-safe coercions handles newtype
s differently than "equivalent" data
types, as you've noted in your question -- they are always immediately coercible through unwrapping whenever the constructor is in scope, regardless of the role of the type parameter.这是因为类型安全强制的算法处理
newtype
的方式与“等效” data
类型不同,正如您在问题中所指出的那样——只要构造函数位于 scope 中,无论类型参数。
So, with all that being said, your experience with associated data families is consistent with the role of the family's type parameter being set to nominal
.因此,尽管如此,您对关联数据系列的体验与将系列的类型参数设置为的角色
nominal
。 Though I couldn't find it documented in the GHC manual, this appears to be as-designed behavior, and there's an open ticket that acknowledges all data/type families have all parameters assigned nominal
role and proposes relaxing this restriction, and @dfeuer actually has adetailed proposal from this past October.虽然我在 GHC 手册中找不到它的记录,但这似乎是设计的行为,并且有一张公开的票,承认所有数据/类型系列的所有参数都分配了
nominal
角色并建议放宽这个限制,@dfeuer 实际上从去年 10 月开始有一个详细的建议。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.