11

我有一个类型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

我对类型角色缺少什么?

4

1 回答 1

12

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

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

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

这个GHC 问题解释了新类型的有点令人惊讶的特殊行为。

于 2019-12-27T19:36:54.057 回答