最近SO question促使我在Haskell中编写了一个不安全且纯粹的ST monad仿真,这是一个稍微修改过的版本,你可以在下面看到:
{-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving, RankNTypes #-}
import Control.Monad.Trans.State
import GHC.Prim (Any)
import Unsafe.Coerce (unsafeCoerce)
import Data.List
newtype ST s a = ST (State ([Any], Int) a) deriving (Functor, Applicative, Monad)
newtype STRef s a = STRef Int deriving Show
newSTRef :: a -> ST s (STRef s a)
newSTRef a = ST $ do
(env, i) <- get
put (unsafeCoerce a : env, i + 1)
pure (STRef i)
update :: [a] -> (a -> a) -> Int -> [a]
update as f i = case splitAt i as of
(as, b:bs) -> as ++ f b : bs
_ -> as
readSTRef :: STRef s a -> ST s a
readSTRef (STRef i) = ST $ do
(m, i') <- get
pure (unsafeCoerce (m !! (i' - i - 1)))
modifySTRef :: STRef s a -> (a -> a) -> ST s ()
modifySTRef (STRef i) f = ST $
modify $ \(env, i') -> (update env (unsafeCoerce f) (i' - i - 1), i')
runST :: (forall s. ST s a) -> a
runST (ST s) = evalState s ([], 0)
如果我们能够在没有unsafeCoerce
的情况下展示通常的ST monad API,那将是件好事。具体来说,我想说明为什么通常的GHC ST monad和上面的仿真工作的原因。在我看来,他们的工作原因是:
STRef s a
具有正确的s
标记必须已在当前ST计算中创建,因为runST
确保不能混合使用不同的状态起来。 STRef s a
标记的任何s
都指向有效a
类型的位置在环境中(在运行时可能弱化引用之后)。 以上几点可以实现非常明显的证明义务编程体验。在我能想到的安全和纯粹的Haskell中,没有什么能够真正接近;我们可以使用索引状态monad和异构列表进行相当差的模仿,但这并不表达上述任何一点,因此需要在STRef
- s的每个使用站点进行校对。
我不知道如何在Agda中正确地形式化这一点。对于初学者来说,“在这个计算中分配”是非常棘手的。我想将STRef
- s表示为特定分配包含在特定ST
计算中的证明,但这似乎导致类型索引的无限递归。
答案 0 :(得分:4)
这是通过假设参数性定理完成的某种形式的解决方案。 它还确保假设不会妨碍计算。
http://code.haskell.org/~Saizan/ST/ST.agda
“darcs获得http://code.haskell.org/~Saizan/ST/”以获取完整的来源
我对封闭的类型世界并不满意,但它是将参数化定理定制为我们实际需要的简单方法。