在Haskell中,有一个名为unsafeCoerce的函数,可以将任何东西变成任何其他类型的东西。这到底是干什么用的?就像,为什么我们要以一种“不安全”的方式将事物彼此转换?

提供一个实际使用unsafeCoerce的方法的示例。链接到Hackack会有所帮助。有人的问题中的示例代码不会。

最佳答案

unsafeCoerce使您可以说服您喜欢的任何属性的类型系统。因此,只有在完全确定要声明的属性为true时,这才是“安全”的。因此,例如:

unsafeCoerce True :: Int

是一种违规,可能会导致不稳定的不良运行时行为。
unsafeCoerce (3 :: Int) :: Int

(显然)很好,并且不会导致运行时异常。

那么unsafeCoerce的非凡用途是什么?假设我们有一个类型类绑定(bind)的存在类型
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包装器时,这种行为可能很有值(value)的一个更常见的示例。这是一个相当普遍的想法,我们可以将类型包装在newtype包装器中,以专用于其instance定义。

例如,Int没有Monoid定义,因为在Int上有两个自然的id半峰:和和乘积。相反,我们使用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中发生,并在Data.Profunctor.Unsafeprofunctors lens 模块(unsafeCoerce的依赖项)中表达。

但是,让我再次建议您,在使用cast之前,您真的需要知道发生了什么,但实际上这是非常不安全的。

要比较的最后一件事是Data.Typeable中提供的“typesafe unsafeCoerce”。这个函数看起来有点像unsafeCoerce,但是还有更多的仪式。
unsafeCoerce ::                             a ->       b
cast         :: (Typeable a, Typeable b) => a -> Maybe b

您可能会认为这是使用typeOf :: Typeable a => a -> TypeRep和函数TypeRep实现的,其中cast是不可伪造的,运行时 token 反射(reflect)了值的类型。那我们有
cast :: (Typeable a, Typeable b) => a -> Maybe b
cast a = if (typeOf a == typeOf b) then Just b else Nothing
  where b = unsafeCoerce a

因此,a能够确保bNothing的类型在运行时确实相同,并且可以决定是否返回cast。举个例子:
{-# 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

因此,如果将unsafeCoerceExistentialQuantification的用法进行比较,我们会发现它可以实现某些相同的功能。特别是,它允许我们重新发现cast可能已忘记的信息。但是,Typeable在运行时手动检查类型,以确保它们确实相同,因此不会被不安全地使用。为此,它要求源类型和目标类型都允许通过ojit_code类在运行时反射(reflect)其类型。

关于haskell - 'unsafeCoerce'的使用,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22847740/

10-11 00:46