所有 `coerce` 的类型检查是否都可以安全地替换为 `unsafeCoerce`?

23

我相信以下代码与 Set.mapMonotonic coerce 一样安全,也就是说,如果 a 或者 b 有不同的 Ord 实例,最坏的情况就是我会破坏 Set 的不变性:

coerceSet :: Coercible a b=> Set.Set a -> Set.Set b
coerceSet = unsafeCoerce

那是正确的吗?

编辑:有关 Set 的相关特性问题,请参考:https://github.com/haskell/containers/issues/308


4
太棒的问题。我期待看到全面的答案; GHC文档本身对于你实际上可以使用unsafeCoerce做什么非常模糊。它有几个经验法则,但不清楚(至少对我来说)这些规则是否适用于此!希望我们能得到一个GHC内部专家写一些真正详细的内容,说明什么是可行的和什么是不可行的。 - Daniel Wagner
3
@DanielWagner 我对Haskell库的经验是,某些东西越不安全,人们就越谨慎地处理文档,这是不幸的。 - jberryman
@jberryman,某些东西越不安全,编写/记录它的人就越不可能真正理解它有多不安全,并且当编译器或库中的其他内容发生变化时,它变得更加不安全的可能性也越大。对于unsafeDupablePerformIO的安全规则非常简单,但我怀疑任何人都无法自信地为几乎任何使用unsafeInlinePerformIO的情况提供可靠的规则。 - dfeuer
2个回答

12

这个应该是安全的。

一个有效的coerce @a @b总是可以被一个有效的unsafeCoerce @a @b替换。为什么?因为在核心层面上,它们是同一个函数coerce(它就像id一样返回其输入)。问题在于,coerce以两个被强制转换的事物具有相同表示的证明作为参数。对于普通的coerce,这个证明是一个实际的证明,但对于unsafeCoerce,这个证明只是一个标记,表示“相信我”。这个证明被作为类型参数传递,因此通过类型擦除,对程序的行为没有影响。因此,只要两者都是可能的,unsafeCoercecoerce是等效的。

现在,这并不是Set的故事的结局,因为coerceSet上不起作用。为什么?让我们看一下它的定义。

data Set a = Bin !Size !a !(Set a) !(Set a) | Tip

根据这个定义,我们可以看到a没有出现在任何类型等式中。这意味着,在Set中我们有表现上相等的传递性:如果a ~#R b(如果ab具有相同的表示方式——~#R是未装箱的Coercible),则Set a ~#R Set b。因此,仅从Set的定义中,coerce应该适用于Set,因此你的unsafeCoerce应该是安全的。 containers库必须使用一个特定的

type role Set nominal

为了隐藏这个事实,人为地禁用coerce。然而,你永远无法禁用unsafeCoerce,并且需要重申的是,在这个上下文中,unsafeCoerce是安全的。

(请注意,unsafeCoercecoerce必须具有相同的类型!请参见@dfeuer的答案,了解“过度热心”的类型推断弯曲一切的示例。)


2
可以说,该库在这里有点自相矛盾,因为它允许我们使用 mapMonotonic 打破不变量,但禁用 coerce,因为它可能会打破不变量。虽然这可能是实用的方法,因为人们几乎不可能错过 mapMonotonic 的要求并打破它,但在涉及某个集合的较大类型上使用 coerce 更容易打破它。尽管如此,该库仍然可以提供一种启用安全强制转换的方式,例如使用 Dict 在集合上“按需”启用安全强制转换,让用户保证类型具有相同的排序。 - chi
@chi 我对你的 Dict 想法很感兴趣,但我没有太多在约束种类等方面的工作经验。如果我们能以某种方式获得一种比 mapMonotonic 更_安全_的集合 coerce 就太好了。 - jberryman
1
@chi,如果我是维护者,当“mapMonotonic”被添加时,它将位于不同的“不安全”模块中。但是,明确是好的。使用今天的GHC公开所需的可选强制转换的唯一方法是使用unsafeCoerce构建它。我正在考虑提案,通过从“Coercible”中删除向后推理来改变它(似乎没有足够有用来证明这些问题)。 - dfeuer
@chi,请查看 https://github.com/ghc-proposals/ghc-proposals/pull/276 - dfeuer
哦,另一个选择当然是将Set作为一个新类型围绕着具有“representational”参数角色的底层内部Set类型进行实现,但这将是一个重大的实现痛点。 - dfeuer

6

是的,在通常的现实情况下,这应该是安全的。然而,可能会有人故意制造一些例子使其不安全。以下是一个使用默认值的例子。我想也许可以使用重叠实例或其他奇怪的功能来做类似的事情,但我不确定。

{-# language GADTs, TypeOperators, ExistentialQuantification #-}

import Data.Coerce
import Unsafe.Coerce
import Data.Type.Equality

data Buh a = Buh (a :~: Rational) a

data Bah = forall a. Bah (a :~: Rational) a

instance Show Bah where
  show (Bah Refl x) = show x

goo :: Rational -> Bah
goo x = case coerce p of
          Buh pf m ->
            let _q = truncate m
            in Bah pf 12
  where
    p = Buh Refl x

如果你调用goo,一切都会很好。但是如果你将coerce替换为unsafeCoerce,调用goo将会导致段错误或者出现其他问题。


我认为这不是特别公平的。你通过滥用类型推断规则,将 coerce @(Buh Rational) @(Buh Rational) 转换成了 unsafeCoerce @(Buh Rational) @(Buh Double)。这不是 coerce -> unsafeCoerce 替换本身的问题,因为你根本没有真正地将 unsafeCoerce 替换为 coerce。虽然我同意这非常令人担忧。 - HTNW
1
@HTNW,当然。我的观点是,盲目地将一个替换为另一个可能不安全。 - dfeuer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接