从GADT存在的安全使用unsafeCoerce?

时间:2014-02-06 19:31:15

标签: haskell

说我有

{-# LANGUAGE GADTs #-}

import Unsafe.Coerce 

data Any where
    Any :: a -> Any

type Index = Int

newtype Ref a = Ref Index

mkRef :: a -> Index -> (Any, Ref a)
mkRef x idx = (Any x, Ref idx)

(any0, ref0) = mkRef "hello" 0
(any1, ref1) = mkRef 'x' 1
(any2, ref2) = mkRef (666 :: Int) 2

anys :: [Any]
anys = [any0, any1, any2]

derefFrom :: Ref a -> [Any] -> a
(Ref idx) `derefFrom` pool = case pool !! idx of
    Any x -> unsafeCoerce x

如果我只使用derefFrom和适当构造的参数,这段代码会按预期工作吗?看起来如此,但我不知道可能会有什么。

通过适当构建的论点,我的意思是:

ref0 `derefFrom` anys
ref1 `derefFrom` anys
ref2 `derefFrom` anys

(我将通过在monad中封装mkRef来使这更安全,以确保使用相应的列表正确生成Ref。)

2 个答案:

答案 0 :(得分:8)

是;只要您确定unsafeCoerce只会被调用以强制实际为目标类型的值,那么它是安全的。

答案 1 :(得分:7)

我不会用GADT存在主义来做这件事。这不是文档明确说明的有效unsafeCoerce的用法。我会按照他们的说法去做,并使用Any中的GHC.Prim作为中间类型。 Any在GHC中有多种特殊之处 - 其中之一是每种类型的值都能保证能够使用unsafeCoerce安全地往返行进。

但还有更多需要考虑的问题。 monadic包装并不像你想象的那么简单。假设您以最简单的方式编写了它,如下所示:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import qualified Data.IntMap.Strict as M

import Control.Applicative

import Control.Monad.State.Strict

import GHC.Prim (Any)
import Unsafe.Coerce


newtype Ref a = Ref Int

newtype Env a = Env (State (M.IntMap Any, Int) a)
    deriving (Functor, Applicative, Monad)


runEnv :: Env a -> a
runEnv (Env s) = evalState s (M.empty, 0)


mkRef :: a -> Env (Ref a)
mkRef x = Env $ do
    (m, c) <- get
    let m' = M.insert c (unsafeCoerce x) m
        c' = c + 1
    put (m', c')
    return $ Ref c


readRef :: Ref a -> Env a
readRef (Ref c) = Env $ do
    (m, _) <- get
    return . unsafeCoerce $ m M.! c


writeRef :: Ref a -> a -> Env ()
writeRef (Ref c) x = Env $ do
    (m, c') <- get
    let m' = M.insert c (unsafeCoerce x) m
    put (m', c')


-- a stupid example of an exceedingly imperative fib function
fib :: Int -> Env Int
fib x = do
    res <- mkRef 1
    let loop i = when (i <= x) $ do
            r <- readRef res
            writeRef res $ r * i
            loop (i + 1)
    loop 2
    readRef res


main :: IO ()
main = print $ runEnv (fib 5)

这...功能的排序,如果你正确使用它。但是有很多方法可以错误地使用它。这是一个崩溃的简单示例,但更复杂的示例可能有不正确的类型强制。

main :: IO ()
main = do
    let x = runEnv $ mkRef "Hello"
        y = runEnv $ readRef x
    print y

幸运的是,我们不需要从头开始解决这个问题 - 我们可以从历史的教训中吸取教训。 ST可能在上下文之间泄漏STRef值时出现类似问题。此解决方案众所周知:通过使用通用量化的类型变量,确保Ref无法从runEnv中逃脱。

该代码看起来更像是这样:

{-# LANGUAGE RankNTypes, GeneralizedNewtypeDeriving #-}

import qualified Data.IntMap.Strict as M

import Control.Applicative

import Control.Monad.State.Strict

import GHC.Prim (Any)
import Unsafe.Coerce


newtype Ref s a = Ref Int

newtype Env s a = Env (State (M.IntMap Any, Int) a)
    deriving (Functor, Applicative, Monad)


runEnv :: (forall s. Env s a) -> a
runEnv (Env s) = evalState s (M.empty, 0)


mkRef :: a -> Env s (Ref s a)
mkRef x = Env $ do
    (m, c) <- get
    let m' = M.insert c (unsafeCoerce x) m
        c' = c + 1
    put (m', c')
    return $ Ref c


readRef :: Ref s a -> Env s a
readRef (Ref c) = Env $ do
    (m, _) <- get
    return . unsafeCoerce $ m M.! c


writeRef :: Ref s a -> a -> Env s ()
writeRef (Ref c) x = Env $ do
    (m, c') <- get
    let m' = M.insert c (unsafeCoerce x) m
    put (m', c')


-- a stupid example of an exceedingly imperative fib function
fib :: Int -> Env s Int
fib x = do
    res <- mkRef 1
    let loop i = when (i <= x) $ do
            r <- readRef res
            writeRef res $ r * i
            loop (i + 1)
    loop 2
    readRef res


main :: IO ()
main = print $ runEnv (fib 5)

当然,在这一点上,我所做的只是重新实现ST。这种方法涉及证明您自己使用unsafeCoerce是正确的,在使用短期引用的长时间运行计算的情况下不会立即收集引用,并且性能比ST差。因此,虽然它是安全的,但对任何事情都不是一个很好的解决方案。

所以,这个巨大的答案一直在问这是否是一个XY问题。您认为这是一个很好的解决方案,您打算解决什么?