使用unsafeCoerce获得有效的平等总是安全的吗?

时间:2014-11-01 03:33:29

标签: haskell

我有类型级别列表的见证类型,

data List xs where
    Nil :: List '[]
    Cons :: proxy x -> List xs -> List (x ': xs)

以及以下实用程序。

-- Type level append
type family xs ++ ys where
    '[] ++ ys = ys
    (x ': xs) ++ ys = x ': (xs ++ ys)

-- Value level append
append :: List xs -> List ys -> List (xs ++ ys)
append Nil ys = ys
append (Cons x xs) ys = Cons x (append xs ys)

-- Proof of associativity of (++)
assoc :: List xs -> proxy ys -> proxy' zs -> ((xs ++ ys) ++ zs) :~: (xs ++ (ys ++ zs))
assoc Nil _ _ = Refl
assoc (Cons _ xs) ys zs = case assoc xs ys zs of Refl -> Refl

现在,我有两种不同但等价的类型级反转函数定义

-- The first version, O(n)
type Reverse xs = Rev '[] xs
type family Rev acc xs where
    Rev acc '[] = acc
    Rev acc (x ': xs) = Rev (x ': acc) xs

-- The second version, O(n²)
type family Reverse' xs where
    Reverse' '[] = '[]
    Reverse' (x ': xs) = Reverse' xs ++ '[x]

第一种方法更有效,但第二种方法在向编译器提供证据时更容易使用,因此有一个等价证明会很好。为了做到这一点,我需要Rev acc xs :~: Reverse' xs ++ acc的证明。这就是我想出的:

revAppend :: List acc -> List xs -> Rev acc xs :~: Reverse' xs ++ acc
revAppend _ Nil = Refl
revAppend acc (Cons x xs) =
    case (revAppend (Cons x acc) xs, assoc (reverse' xs) (Cons x Nil) acc) of
        (Refl, Refl) -> Refl

reverse' :: List xs -> List (Reverse' xs)
reverse' Nil = Nil
reverse' (Cons x xs) = append (reverse' xs) (Cons x Nil)

不幸的是,revAppend是O(n³),这完全违背了本练习的目的。但是,我们可以绕过所有这些并使用unsafeCoerce获得O(1):

revAppend :: Rev acc xs :~: Reverse' xs ++ acc
revAppend = unsafeCoerce Refl

这样安全吗?一般情况怎么样?例如,如果我有两个类型系列F :: k -> *G :: k -> *,并且我知道它们是等效的,那么定义以下内容是否安全?

equal :: F a :~: G a
equal = unsafeCoerce Refl

1 个答案:

答案 0 :(得分:5)

如果GHC在表达式e::T上使用终止检查器,那将是非常好的,其中T只有一个没有参数的构造函数K(例如:~:,{{1 }})。当检查成功时,GHC可以将()重写为e完全跳过计算。你必须排除FFI,KunsafePerformIO,......但这似乎是可行的。如果实现了这一点,它将很好地解决发布的问题,允许实际编写运行成本为零的证明。

如果没有这个,你可以按照你的建议同时使用trace。如果你真的,确实两种类型是相同的,你可以安全地使用它。典型的例子是实现unsafeCoerce。当然,在不同类型上滥用Data.Typeable会导致不可预测的影响,希望是崩溃。

你甚至可以写自己更安全的#34; unsafeCoerce的变体:

unsafeCoerce

如果定义了unsafeButNotSoMuchCoerce :: (a :~: b) -> a -> b #ifdef CHECK_TYPEEQ unsafeButNotSoMuchCoerce Refl = id #else unsafeButNotSoMuchCoerce _ = unsafeCoerce #endif ,则会导致代码变慢。如果未定义,它会跳过它并以零成本强制执行。在后一种情况下,它仍然是不安全的,因为你可以将底部作为第一个arg传递,程序将不会循环,而是执行错误的强制。通过这种方式,您可以使用安全但缓慢的模式测试您的程序,然后转向不安全模式并祈祷您的"校样"总是在终止。