安全使用来自 GADT 存在主义的 unsafeCoerce?

问题描述 投票:0回答:2

SO是一场狗屎秀。感谢您的搭车。

haskell
2个回答
9
投票

是的;只要您可以确定

unsafeCoerce
只会被调用来强制转换实际上属于目标类型的值,那么它就是安全的。


6
投票

我不会用 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 问题之类的事情。您认为这是一个很好的解决方案,想要解决什么问题?

© www.soinside.com 2019 - 2024. All rights reserved.