使用' unsafeCoerce'

时间:2014-04-03 19:57:56

标签: haskell type-conversion type-safety

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

提供实际使用unsafeCoerce的方式的示例。 Hackage的链接会有所帮助。某些问题中的示例代码不会。

3 个答案:

答案 0 :(得分:16)

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。我们永远无法说服typechecker这是真的,但由于外部证据我们知道它是。

因此,我们可以(非常仔细)

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定义,因为在Int s上有两个自然幺半位:总和和产品。相反,我们使用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实际上只是一个newtype-wrapped b而且(b)知道编译器无法推断出它本身,那么你可能想要做

unsafeCoerce (x :: a) :: b

略微提高效率。例如,这种情况经常发生在lens中,并在profunctors的{​​{3}}模块中表示,lens的依赖关系。

但是让我再次建议你在使用unsafeCoerce之前确实需要知道发生了什么事情,这样的事情是非常不安全的。


要比较的最后一件事是cast中提供的“类型安全Data.Typeable”。这个函数看起来有点像unsafeCoerce,但有更多的仪式。

unsafeCoerce ::                             a ->       b
cast         :: (Typeable a, Typeable b) => a -> Maybe b

其中,您可能会认为使用unsafeCoercetypeOf :: 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能够确保bNothing 的类型在运行时确实相同,并且可以决定返回{ {1}}如果他们不是。举个例子:

{-# 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类对其类型进行运行时反射。

答案 1 :(得分:6)

我唯一被迫使用unsafeCoerce的时间是有限的自然数。

{-# LANGUAGE DataKinds, GADTs, TypeFamilies, StandaloneDeriving #-}

data Nat = Z | S Nat deriving (Eq, Show)

data Fin (n :: Nat) :: * where
    FZ :: Fin (S n)
    FS :: Fin n -> Fin (S n)

deriving instance Show (Fin n)

Fin n是一个单链接的数据结构,静态地确保小于参数化的n类型级别自然数。

-- OK, 1 < 2 
validFin :: Fin (S (S Z))
validFin = FS FZ

-- type error, 2 < 2 is false
invalidFin :: Fin (S (S Z))
invalidFin = FS (FS FZ)

Fin可用于安全地索引到各种数据结构。它在依赖类型语言中非常标准,但不在Haskell中。

有时我们希望将Fin n的值转换为Fin m,其中m大于n

relaxFin :: Fin n -> Fin (S n)
relaxFin FZ     = FZ
relaxFin (FS n) = FS (relaxFin n)
根据定义,

relaxFin是无操作,但仍需要遍历该值才能签出类型。因此,我们可能只使用unsafeCoerce而不是relaxFin。通过强制包含Fin - s的较大数据结构可以产生更明显的速度提升(例如,您可以将带有Fin的lambda术语作为绑定变量。)

这是一个不可否认的异乎寻常的例子,但我发现它很有意思,它非常安全:我无法想到外部库或安全用户代码的方法来搞清楚这一点。我可能错了,我很想知道潜在的安全问题。

答案 2 :(得分:2)

我没有使用unsafeCoerce我真的可以推荐,但我可以看到在某些情况下这样的事情可能会有用。

首先考虑的是Typeable相关例程的实现。特别是cast :: (Typeable a, Typeable b) => a -> Maybe b实现了类型安全的行为,所以使用它是安全的,但它必须在其实现中发挥肮脏的技巧。

在导入FFI子例程以强制类型匹配时,也许unsafeCoerce可以找到一些用处。毕竟,FFI已经允许将不纯的C函数作为纯函数导入,因此它本质上是美国的。请注意&#34;不安全&#34;并不意味着不可能使用,而只是将证据的负担放在程序员身上&#34;。

最后,假装sortBy不存在。然后考虑这个例子:

-- Like Int, but using the opposite ordering
newtype Rev = Rev { unRev :: Int }
instance Ord Rev where compare (Rev x) (Rev y) = compare y x

sortDescending :: [Int] -> [Int]
sortDescending =  map unRev . sort . map Rev

上面的代码有效,但感觉愚蠢恕我直言。我们使用map等函数执行两个Rev,unRev,我们知道在运行时是无操作。所以我们只是无缘无故地扫描列表两次,而是说服编译器使用正确的Ord实例。

这些地图的性能影响应该很小,因为我们也对列表进行排序。然而,将map Rev重写为unsafeCoerce :: [Int]->[Rev]并节省一些时间是很诱人的。

请注意,具有强制功能

castNewtype :: IsNewtype t1 t2 => f t2 -> f t1

其中约束意味着t1t2的新类型会有所帮助,但这会非常危险。考虑

castNewtype :: Data.Set Int -> Data.Set Rev

以上会导致数据结构不变,因为我们正在改变下面的排序!由于Data.Set是作为二叉搜索树实现的,因此会造成相当大的破坏。