简体   繁体   English

为什么 GHC 在使用 Coercible 约束时会自相矛盾?

[英]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 从关联数据的强制性推断统一,为什么它与自己的检查类型签名相矛盾?

The problem问题

{-# 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.那不是我给它的签名。

Coercibility强制力

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 - 当然不是统一的。

Update!更新!

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 aFoo 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 aFoo b是可强制的当且仅当ab是完全相同的类型。

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中参数anominal作用, 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM