通过 `coerce` 键入角色和混淆行为
Type roles and confusing behavior by `coerce`
我有一个类型 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
关于类型角色,我遗漏了什么?
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" 类型构造函数,前提是受影响的类型变量是
representational
或 phantom
。例如,您可以将 Map Char Int
强制转换为 Map Char (Data.Monoid.Sum Int)
,因为对于 Map
我们有 type role Map nominal representational
.
- 您可以始终将新类型强制转换为基础类型,反之亦然,前提是新类型构造函数在范围内。这将忽略所有角色!理由是,鉴于构造函数可用,您始终可以手动包装和解包,因此该角色无论如何都不会给您任何安全性。
在您的示例中,第三条规则适用。如果在另一个模块中定义了新类型并且未导入构造函数,则强制转换将失败(要使其再次工作,您需要将角色切换为 phantom
)。
this GHC 问题中解释了新类型有些令人惊讶的特殊行为。
我有一个类型 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
关于类型角色,我遗漏了什么?
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" 类型构造函数,前提是受影响的类型变量是
representational
或phantom
。例如,您可以将Map Char Int
强制转换为Map Char (Data.Monoid.Sum Int)
,因为对于Map
我们有type role Map nominal representational
. - 您可以始终将新类型强制转换为基础类型,反之亦然,前提是新类型构造函数在范围内。这将忽略所有角色!理由是,鉴于构造函数可用,您始终可以手动包装和解包,因此该角色无论如何都不会给您任何安全性。
在您的示例中,第三条规则适用。如果在另一个模块中定义了新类型并且未导入构造函数,则强制转换将失败(要使其再次工作,您需要将角色切换为 phantom
)。
this GHC 问题中解释了新类型有些令人惊讶的特殊行为。