繁体   English   中英

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

[英]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 aFoo b类型的强制力取决于参数a的作用。 特别是,如果a的角色是名义上的,那么Foo aFoo b是可强制的当且仅当ab是完全相同的类型。

所以,在程序中:

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

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