通过`coerce`键入角色和令人困惑的行为


11

我有一个类型,Id a并且我正尝试防止意外强制,例如将an Id Double更改为an 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

我对类型角色缺少什么?


ain Id是一个幻像变量,对内部的实际值没有影响。如果您有newtype Id a = Id a,那么胁迫将失败。
lehins

@lehins的要点type role是使情况并非如此。这个问题在问为什么那没有用。
约瑟夫·西布尔-恢复莫妮卡

Answers:


12

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

  • 每种类型都可以强制执行。
  • 如果受影响的类型变量为representational或,则可以强制类型构造器“下” phantom。例如,您可以Map Char Int将a 强制为a Map Char (Data.Monoid.Sum Int)因为因为Map我们有type role Map nominal representational
  • 只要新类型构造函数在作用域内,您就可以始终将新类型强制为基础类型,反之亦然。这将忽略所有角色!这样做的理由是,鉴于构造函数可用,因此您始终可以手动包装和拆包,因此该角色无论如何都不会给您带来任何安全性。

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

对于newtypes的有些令人惊讶的特殊行为,说明这个 GHC问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.