我有类型级别列表的见证类型,
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
答案 0 :(得分:5)
如果GHC在表达式e::T
上使用终止检查器,那将是非常好的,其中T
只有一个没有参数的构造函数K
(例如:~:
,{{1 }})。当检查成功时,GHC可以将()
重写为e
完全跳过计算。你必须排除FFI,K
,unsafePerformIO
,......但这似乎是可行的。如果实现了这一点,它将很好地解决发布的问题,允许实际编写运行成本为零的证明。
如果没有这个,你可以按照你的建议同时使用trace
。如果你真的,确实两种类型是相同的,你可以安全地使用它。典型的例子是实现unsafeCoerce
。当然,在不同类型上滥用Data.Typeable
会导致不可预测的影响,希望是崩溃。
你甚至可以写自己更安全的#34; unsafeCoerce
的变体:
unsafeCoerce
如果定义了unsafeButNotSoMuchCoerce :: (a :~: b) -> a -> b
#ifdef CHECK_TYPEEQ
unsafeButNotSoMuchCoerce Refl = id
#else
unsafeButNotSoMuchCoerce _ = unsafeCoerce
#endif
,则会导致代码变慢。如果未定义,它会跳过它并以零成本强制执行。在后一种情况下,它仍然是不安全的,因为你可以将底部作为第一个arg传递,程序将不会循环,而是执行错误的强制。通过这种方式,您可以使用安全但缓慢的模式测试您的程序,然后转向不安全模式并祈祷您的"校样"总是在终止。