[英]Why is GHC contradicting itself when using a Coercible constraint?
为什么 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
}
这一切都很好——GHC 将愉快地编译这段代码,并且确信withBaz
具有声明的签名。
现在,让我们尝试使用它!
instance (Foo a) => Foo (Maybe a) where
data Bar (Maybe a) = MabyeBar (Bar a)
toMaybeBaz :: Baz a -> Baz (Maybe a)
toMaybeBaz = withBaz Just
这给出了一个错误 - 但一个非常奇怪的错误:
withBaz Just
^^^^^^^^^^^^
cannot construct the infinite type: a ~ Maybe a
确实,如果我将 go 放入 GHCi,并要求它给我withBaz
的类型:
ghc>:t withBaz
withBaz :: (b -> b) -> Baz b -> Baz b
那不是我给它的签名。
我怀疑 GHC 正在处理 withBaz 的withBaz
类型,就好像它们必须统一一样,因为它是从Coercible (Bar a) (Bar b)
推断出Coercible ab
。 但是因为它是一个数据家族,所以它们甚至不需要是Coercible
- 当然不是统一的。
以下更改修复了编译:
instance (Foo a) => Foo (Maybe a) where
newtype Bar (Maybe a) = MabyeBar (Bar a)
也就是说,将数据族声明为新newtype
,而不是data
。 这似乎与 GHC 在语言中对Coercible
的处理一致,因为
data Id a = Id a
不会导致生成Coercible
实例 - 即使它绝对应该强制转换a
. 使用上述声明,这将出错:
wrapId :: a -> Id a
wrapId = coerce
但是使用newtype
声明:
newtype Id a = Id a
然后Coercible
实例存在,并且wrapId
编译。
我相信@dfeuer 关于缺乏对类型/数据系列的“角色”支持的评论提供了答案。
对于顶级的、 data
定义的参数化类型:
data Foo a = ...
Foo a
和Foo b
类型的强制力取决于参数a
的作用。 特别是,如果a
的角色是名义上的,那么Foo a
和Foo b
是可强制的当且仅当a
和b
是完全相同的类型。
所以,在程序中:
{-# 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
由于Foo a
中参数a
的nominal
作用, foo
的类型实际上被简化为b -> b
:
λ> :t foo
foo :: b -> b
如果角色注释从nominal
更改为representational
,则类型简化为Coercible ab => a -> b
,并且如果角色更改为phantom
(此特定Foo
声明的默认值,因为a
不会出现在右侧),类型被简化为a -> b
。 这一切都符合预期,并且对应于每个角色的定义。
请注意,如果您将Foo
的声明替换为:
data Foo a = Foo a
那么phantom
角色将不再被允许,但其他两个角色下foo
的推断类型将与以前完全相同。
但是,如果您从data
切换到newtype
,则会有一个重要的区别。 和:
newtype Foo a = Foo a
您会发现,即使使用type role Foo nominal
Foonominal , foo
的推断类型也会是Coercible ba => a -> b
而不是b -> b
。 这是因为类型安全强制的算法处理newtype
的方式与“等效” data
类型不同,正如您在问题中所指出的那样——只要构造函数位于 scope 中,无论类型参数。
因此,尽管如此,您对关联数据系列的体验与将系列的类型参数设置为的角色nominal
。 虽然我在 GHC 手册中找不到它的记录,但这似乎是设计的行为,并且有一张公开的票,承认所有数据/类型系列的所有参数都分配了nominal
角色并建议放宽这个限制,@dfeuer 实际上从去年 10 月开始有一个详细的建议。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.