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

Carry STRef implicitly in an environment during computation

我正在进行一些更大的计算,需要在某些关键时刻使用可变数据。我想尽可能地避免 IO。 我的模型过去由 ExceptT over ReaderT over State 数据类型组成,现在我想用提到的 ST.

替换 State

为简化起见,假设我想在整个计算过程中保持单个 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)

评价者:

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

...它失败了,因为

Couldn't match type ‘s’ with ‘s1’

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

我这里想实现的功能是让supply随时可以访问,但是不显式传递(不值得)。存放它最舒服的地方是在环境中,但我没办法初始化它。

我知道有像 STT monad transformer 这样的东西可能对这里有帮助,但它与哈希表等更雄心勃勃的数据结构不兼容(或者是吗?),所以我不想要只要我不能在那里自由使用经典 ST 库就可以使用它。

如何正确设计这个模型? "properly" 我的意思不仅是 "to typecheck" 而是 "to be nice to the rest of the code" 和 "as flexible as possible".

runST 必须给出一个多态参数,而您希望您的参数来自 Comp。因此 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)

因为 Comps 关闭,你不能做出 returns 包含 STRef 的动作;但是您可以公开一个在内部使用引用的操作:

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

例如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)

那你就可以写

getSup :: Comp s (STRef s Int)
getSup = Comp (asks supply)