通常在 Haskell 中处理的强制类型转换有两种方式:通过 newtype 和 Coercible 实现的表示相等,以及通过 Typeable 实现的有关类型变量的新信息。第二种类型与运行时表示几乎没有关系,因此我将只描述 Coercible/newtype 机制。
这是一个保证 newtype 仅更改类型信息而不更改底层表示的机制,因此如果我们有(标准示例):
newtype Age = Age { unAge :: Int }
那么我们应该有信心,像这样的事情
instance Num Age where
Age a + Age b = Age (a + b)
...
在 Int
上,(+)
和 Age
的速度是完全一样的,即在幕后没有指针间接操作。实际上,GHC 在这里毫不费力地消除了 Age
构造器。然而,当我们想要执行类似下面的操作时,就会面临挑战:
map Age :: [Int] -> [Age]
自从
Int
和
Age
在结构上完全相同,这也应该是一个无操作,我们所要做的就是在编译时满足类型系统,然后在运行时丢弃
map Age
。不幸的是,这并不是情况,因为即使在每个阶段什么也不做,
map
仍将遍历我们的列表。
在许多
newtype
被抛出但我们也希望GHC生成最紧凑的编译代码的情况下,您可能会看到(危险,小心)使用
unsafeCoerce
。
unsafeCoerce :: [Int] -> [Age]
在这种情况下,
unsafeCoerce
是“安全”的,因为我们知道这两种类型在运行时是相同的。此外,由于
unsafeCoerce
纯粹在类型级别上操作,并且在运行时是真正的无操作,所以与
map Age
不同,
unsafeCoerce
真的是一个
O(0)
强制转换。
但它非常危险。
Coercible
希望通过允许实例化来解决这个问题,例如:
instance Coercible a b => Coercible [a] [b] where coerce = unsafeCoerce
因此,Haskell类型类机制允许仅在安全情况下使用
coerce
,而不像
unsafeCoerce
那样。为确保这一点,不能构建恶意的
Coercible
实例。为此,所有
Coercible
实例都是由编译器基于对
newtype
的使用构建的。
最后需要注意的是,当你真正深入了解
Coercible
的工作原理时,你必须理解新的Haskell角色系统,该系统允许开发人员注释是否应允许强制转换
newtype
。这在[
Coercible
类的文档]中明确概述(
http://www.haskell.org/ghc/docs/7.8.1-rc2/html/libraries/base-4.7.0.0/Data-Coerce.html)。
a
和b
存在Coercible a b
实例?(通过“标准类型”,我可能指的是“Haskell报告中指定的类型”或其某个子集)。 - gspra
和b
。假设我想编写一个转换函数f :: a -> b
。我怀疑在许多平台上,使用 GHC 会有一个Coercible a b
实例,但并非所有平台都有(这可能发生,对吧?)。假设还有一个通用的、慢速的转换函数g :: a -> b
,它总是在不假设底层表示的情况下执行正确的操作(假设g
在 Haskell 报告中)。我能否以某种方式(如果必须使用 CPP)说“只要有Coercible a b
实例,就让f = coerce
,否则让f = g
”? - gsprreflection
包的内部,您会经常看到这些内容 - 但这些内容往往是非常不安全的。Coercible
的设计独立于任何实际的实现细节 - 它完全依靠newtype
的承诺来工作。 - J. AbrahamsonCoercible
与实现细节无关... 我想我在问的是:是否有一个易于访问的列表,保证它不会在次要版本中更改,其中列出了使用newtype
在GHC中实现的(标准)类型?或者,可能可以在特定平台上向特定版本的GHC询问,“嘿,你是否只是从此标准Haskell类型到此标准Haskell类型简单地使用newtype
?”,也许通过询问Coercible
实例来实现?我觉得这会很有帮助。 - gsprnewtype CDouble = CDouble Double
(或者是否有Coercible CDouble Double
实例)?”如果没有,请进行缓慢的转换。如果是,请使用coerce
进行转换。这对于涉及 FFI 的许多数值计算非常有用。 - gspr