在计算期间在环境中隐式携带STRef

rad*_*row 3 monads haskell design-patterns monad-transformers

我正在研究一些需要在某些关键时刻使用可变数据的更大计算.我想尽可能地避免使用IO.我的模型使用的constist ExceptT超过ReaderTState数据类型,现在我要替换State有提及ST.

为了简化,假设我想保持单一STRefInt整个计算过程中,让我们跳过ExceptT外层.我最初的想法是STRef s Int进入ReaderT环境:

{-#LANGUAGE Rank2Types#-}
{-#LANGUAGE ExistentialQuantification#-}

data Env = Env { supply :: forall s. STRef s Int }
data Comp a = forall s. Comp (ReaderT Env (ST s) a)
Run Code Online (Sandbox Code Playgroud)

评估员:

runComp (Comp c) = runST $ do
   s <- newSTRef 0
  runReaderT c (Env {supply = s})  -- this is of type `ST s a`
Run Code Online (Sandbox Code Playgroud)

......它失败了,因为

无法将类型's'与's1'匹配

这似乎很清楚,因为我混合了两个独立的幻影ST状态.但是,我不知道如何绕过它.我试过添加幻像s作为CompEnv参数,但结果是相同的,代码变得更加丑陋(但由于缺少这些foralls 而不那么可疑).

我试图在这里实现的功能是随时supply可访问,但不明确传递(它不值得).存放它的最舒适的地方是在环境中,但我认为没有办法初始化它.

我知道有一些像STTmonad变换器这样的东西可能会有帮助,但是它不像哈希表这样雄心勃勃的数据结构兼容(或者它是什么?),所以我不想使用它,只要我不能自由使用经典ST图书馆.

如何正确设计这个模型?通过"正确",我的意思不仅是"对于typecheck",而且"对于代码的其余部分很好"和"尽可能灵活".

Dan*_*ner 5

runST必须给出一个多态参数,并且你希望你的论证来自Comp.Ergo Comp必须包含多态的东西.

newtype Env s = Env { supply :: STRef s Int }
newtype Comp a = Comp (forall s. ReaderT (Env s) (ST s) a)

runComp (Comp c) = runST $ do
    s <- newSTRef 0
    runReaderT c (Env s)
Run Code Online (Sandbox Code Playgroud)

因为Comp结束s,你不能做出一个返回所包含的动作STRef; 但是你可以在内部公开一个使用引用的动作:

onRef :: (forall s. STRef s Int -> ST s a) -> Comp a
onRef f = Comp $ asks supply >>= lift . f
Run Code Online (Sandbox Code Playgroud)

例如onRef readSTRef :: Comp IntonRef (`modifySTRef` succ) :: Comp ().另一种可能更符合人体工程学的选择是让Comp自己变得单态,但runComp需要多态的动作.所以:

newtype Comp s a = Comp (ReaderT (Env s) (ST s) a)

runComp :: (forall s. Comp s a) -> a
runComp act = runST $ case act of
    Comp c -> do
        s <- newSTRef 0
        runReaderT c (Env s)
Run Code Online (Sandbox Code Playgroud)

然后你就可以写了

getSup :: Comp s (STRef s Int)
getSup = Comp (asks supply)
Run Code Online (Sandbox Code Playgroud)