Я бы не сделал это с 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. Что вы пытаетесь решить, что считаете это хорошим решением?
Вам также может понравиться [Dynamic] (http://hackage.haskell.org/package/base-4.6.0.1/docs/Data-Dynamic.html), который является уже инкапсулированным безопасным использованием 'unsafeCoerce' сродни тому, что вы здесь делаете. –
@ DanielWagner 'Dynamic' на самом деле не похож на то, что делается здесь. 'Dynamic' использует' Typeable' для проверки правильности каждого принуждения во время выполнения. Используемый здесь подход заключается в использовании переменной типа фантомного типа, чтобы иметь доступ ко всей необходимой информации о типе во время компиляции. – Carl
Возможно, вас заинтересует 'vault', который реализует этот разнородный контейнер с типом аннотированных ключей: http://hackage.haskell.org/package/vault – shang