unsafeCoerce 让你说服类型系统相信你喜欢的任何属性。因此,只有当您可以完全确定您声明的属性是真实的时,它才是“安全的”。所以,例如:
unsafeCoerce True :: Int
是一种违规行为,可能会导致不稳定、不良的运行时行为。
unsafeCoerce (3 :: Int) :: Int
(显然)很好,不会导致运行时不当行为。
那么unsafeCoerce 的重要用途是什么?假设我们有一个类型类绑定的存在类型
module MyClass ( SomethingMyClass (..), intSomething ) where
class MyClass x where {}
instance MyClass Int where {}
data SomethingMyClass = forall a. MyClass a => SomethingMyClass a
我们还假设,正如这里所指出的,类型类MyClass 没有导出,因此没有其他人可以创建它的实例。事实上,Int 是唯一实例化它的东西,也是唯一会实例化它的东西。
现在,当我们使用模式匹配来破坏 SomethingMyClass 的值时,我们将能够从内部拉出“某物”
foo :: SomethingMyClass -> ...
foo (SomethingMyClass a) =
-- here we have a value `a` with type `exists a . MyClass a => a`
--
-- this is totally useless since `MyClass` doesn't even have any
-- methods for us to use!
...
现在,正如评论所暗示的那样,我们提取的值没有类型信息——它已被存在上下文“遗忘”。它绝对可以是任何实例化MyClass。
当然,在这种非常特殊的情况下,我们知道唯一实现MyClass的是Int。所以我们的值a 必须实际上 具有Int 类型。我们永远无法让类型检查器相信这是真的,但由于外部证据,我们知道这是真的。
因此,我们可以(非常小心地)
intSomething :: SomethingMyClass -> Int
intSomething (SomethingMyClass a) = unsafeCoerce a -- shudder!
现在,希望我提出这是一个可怕而危险的想法,但它也可以让我们体验一下我们可以利用哪些信息来了解类型检查器无法了解的事情。
在非病理情况下,这种情况很少见。更罕见的是使用我们知道的东西而类型检查器不知道的情况本身并不是病态的。在上面的例子中,我们必须完全确定没有人会扩展我们的 MyClass 模块来实例化更多类型到 MyClass 否则我们对 unsafeCoerce 的使用会立即变得不安全。
> instance MyClass Bool where {}
> intSomething (SomethingMyClass True)
6917529027658597398
看起来我们的编译器内部正在泄漏!
使用newtype 包装器时,此类行为可能有价值的更常见示例是。这是一个相当普遍的想法,我们可以将一个类型包装在 newtype 包装器中,以便专门化其 instance 定义。
例如,Int 没有Monoid 定义,因为Ints 上有两个自然幺半群:和和积。相反,我们使用newtype 包装器更明确。
newtype Sum a = Sum { getSum :: a }
instance Num a => Monoid (Sum a) where
mempty = Sum 0
mappend (Sum a) (Sum b) = Sum (a+b)
现在,通常编译器非常聪明,并且认识到它可以消除所有那些Sum 构造函数,以便生成更高效的代码。可悲的是,有时它不能,尤其是在高度多态的情况下。
如果您 (a) 知道某些类型 a 实际上只是一个新类型包装的 b 并且 (b) 知道编译器无法自行推断,那么您可能想要这样做
unsafeCoerce (x :: a) :: b
略微提高效率。例如,这经常出现在lens 中,并在profunctors 的Data.Profunctor.Unsafe 模块中表示,这是lens 的依赖项。
但让我再次建议您在使用 unsafeCoerce 之前确实需要知道发生了什么,因为这绝不是非常不安全的。
最后要比较的是Data.Typeable 中的“typesafe cast”。这个函数看起来有点像unsafeCoerce,但是多了些仪式感。
unsafeCoerce :: a -> b
cast :: (Typeable a, Typeable b) => a -> Maybe b
其中,您可能会认为是使用unsafeCoerce 和函数typeOf :: Typeable a => a -> TypeRep 实现的,其中TypeRep 是不可伪造的,反映值类型的运行时标记。然后我们有
cast :: (Typeable a, Typeable b) => a -> Maybe b
cast a = if (typeOf a == typeOf b) then Just b else Nothing
where b = unsafeCoerce a
因此,cast 能够确保a 和b 的类型在运行时确实相同,如果它们不一样,它可以决定返回Nothing .举个例子:
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}
data A = A deriving (Show, Typeable)
data B = B deriving (Show, Typeable)
data Forget = forall a . Typeable a => Forget a
getAnA :: Forget -> Maybe A
getAnA (Forget something) = cast something
我们可以如下运行
> getAnA (Forget A)
Just A
> getAnA (Forget B)
Nothing
因此,如果我们将cast 的用法与unsafeCoerce 进行比较,我们会发现它可以实现一些相同的功能。特别是,它使我们能够重新发现可能被ExistentialQuantification 遗忘的信息。但是,cast 在运行时手动检查类型以确保它们真正相同,因此不能不安全地使用。为此,它要求源类型和目标类型都允许通过 Typeable 类对其类型进行运行时反射。