【发布时间】:2021-04-02 21:53:26
【问题描述】:
为什么 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
确实,如果我进入 GHCi,并要求它给我withBaz 的类型:
ghc>:t withBaz
withBaz :: (b -> b) -> Baz b -> Baz b
那不是我给它的签名。
强制力
我怀疑 GHC 将 withBaz 的类型参数视为必须统一,因为它从 Coercible (Bar a) (Bar b) 推断 Coercible a b。但是因为它是一个数据族,所以它们甚至不需要是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 编译。
【问题讨论】:
-
很奇怪。我很想说这显然是类型检查器中的一个错误。
-
首先,您可以通过指出函数
test :: forall a b. (Coercible (Bar a) (Bar b)) => Bar a -> Bar b与实现test = coerce以GHCi 中的test :: Bar b -> Bar b类型结束来简化示例代码。也就是说,您能否提供一个在任何实际具体类型中使用withBaz的示例?例如,对于toMaybeBaz,您认为可以强制转换为MabyeBar (Bar a)的类型是什么? -
"你心目中的哪种类型可以强制转换为
MabyeBar (Bar a)?" -Bar a,Bar (Maybe a)是一个包装器。它们在内存中显然具有相同的表示,因此它们应该是可强制的。 -
我添加了一个更新,因为@DDub 的评论启发我回顾了一些旧代码确实以这种方式使用
coerce,我发现它具有关联数据系列的newtype声明,而不是data声明。
标签: haskell ghc coercion type-families