通过 `coerce` 键入角色和混淆行为

Type roles and confusing behavior by `coerce`

我有一个类型 Id a,我正试图防止意外强制转换,例如,Id DoubleId 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

关于类型角色,我遗漏了什么?

Coercible has three possible "types" of instances (which are automagically generated by the compiler, not defined by the user). Only one of them is actually affected by roles.

  • 每种类型都可以强制转换为自身。
  • 您可以强制 "under" 类型构造函数,前提是受影响的类型变量是 representationalphantom。例如,您可以将 Map Char Int 强制转换为 Map Char (Data.Monoid.Sum Int),因为对于 Map 我们有 type role Map nominal representational.
  • 您可以始终将新类型强制转换为基础类型,反之亦然,前提是新类型构造函数在范围内。这将忽略所有角色!理由是,鉴于构造函数可用,您始终可以手动包装和解包,因此该角色无论如何都不会给您任何安全性。

在您的示例中,第三条规则适用。如果在另一个模块中定义了新类型并且未导入构造函数,则强制转换将失败(要使其再次工作,您需要将角色切换为 phantom)。

this GHC 问题中解释了新类型有些令人惊讶的特殊行为。