通过`coerce`方法进行类型转换的角色及其令人困惑的行为

10

我有一个类型Id a,我试图防止意外强制转换,例如将Id Double 转换为 Id Int

如果我正确理解类型的角色,以下代码不应该编译。

{-# LANGUAGE RoleAnnotations #-}
import Data.Coerce (coerce)

type role Id nominal
newtype Id a = Id String

badKey :: Id Int
badKey = coerce (Id "I point to a Double" :: Id Double)

不幸的是,它确实会:

Prelude> :load Id.hs
[1 of 1] Compiling Main             ( Id.hs, interpreted )
Ok, one module loaded.
*Main> :type badKey
badKey :: Id Int

我对类型角色有什么不理解的吗?


Id 中的 a 是一个幽灵变量,对内部实际值没有影响。如果你有 newtype Id a = Id a,那么强制转换就会失败。 - lehins
@lehins type role 的目的是使其不成为这种情况。这个问题在问为什么它没有起作用。 - Joseph Sible-Reinstate Monica
1个回答

12

Coercible有三种可能的实例"类型"(由编译器自动生成,而不是用户定义)。只有其中的一种实际上受到roles的影响。

  • 每个类型都可以强制转换为自身。
  • 您可以在类型构造函数下进行强制转换,前提是受影响的类型变量是representationalphantom。例如,您可以将Map Char Int强制转换为Map Char (Data.Monoid.Sum Int),因为对于Map,我们有type role Map nominal representational
  • 您始终可以将新类型强制转换为基础类型,反之亦然,前提是新类型构造函数在作用域内。这忽略了所有角色!理由是,假设构造函数可用,您始终可以手动包装和解包,因此角色不会给您带来任何安全性。
在你的例子中,第三条规则适用。如果新类型定义在另一个模块中且构造函数未被导入,则强制转换将失败(要使其再次正常工作,您需要将角色切换为phantom)。
新类型的某些令人惊讶的特殊行为在这个 GHC问题中得到解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接