2014-02-06 3 views
7

Скажем, у меня естьБезопасное использование unsafeCoerce из GADT экзистенциально?

{-# 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 

(я сделать это более безопасным путем инкапсулирования использования mkRef в монады для обеспечения Ref s сгенерированы должным образом с соответствующим списком.)

+2

Вам также может понравиться [Dynamic] (http://hackage.haskell.org/package/base-4.6.0.1/docs/Data-Dynamic.html), который является уже инкапсулированным безопасным использованием 'unsafeCoerce' сродни тому, что вы здесь делаете. –

+2

@ DanielWagner 'Dynamic' на самом деле не похож на то, что делается здесь. 'Dynamic' использует' Typeable' для проверки правильности каждого принуждения во время выполнения. Используемый здесь подход заключается в использовании переменной типа фантомного типа, чтобы иметь доступ ко всей необходимой информации о типе во время компиляции. – Carl

+0

Возможно, вас заинтересует 'vault', который реализует этот разнородный контейнер с типом аннотированных ключей: http://hackage.haskell.org/package/vault – shang

ответ

8

Да; до тех пор, пока вы можете быть уверены, что unsafeCoerce будет вызван только для того, чтобы принудить значение, которое на самом деле относится к типу цели, тогда оно безопасно.

7

Я бы не сделал это с GADT экзистенциальным. Это не использование unsafeCoerce, о котором явно говорят документы. Я бы пошел с тем, что они говорят, и использовать Any от GHC.Prim в качестве промежуточного типа. Any является особенным в GHC несколькими способами - одним из них является то, что ценности каждого типа гарантированно смогут безопасно перемещаться по нему с помощью unsafeCoerce.

Но еще есть что рассмотреть. Монадическая обертка не так проста, как вы думаете. Допустим, вы написали это самый простой способ это возможно, что-то вроде этого:

{-# 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 могут иметь аналогичные проблемы с значениями утечки между контекстами. Решение хорошо известно на этом этапе: убедитесь, что Ref s не может уйти от 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. Что вы пытаетесь решить, что считаете это хорошим решением?

Смежные вопросы