首页 > 解决方案 > 计算期间在环境中隐式携带 STRef

问题描述

我正在做一些更大的计算,需要在某些关键时刻使用可变数据。我想尽可能地避免 IO。我的模型曾经由ExceptTover ReaderTover Statedatatype 组成,现在我想State用提到ST的 .

为简化起见,假设我想在整个计算过程中保持单一,让我们跳过STRef外层。我最初的想法是放入' s 环境中:IntExceptTSTRef s IntReaderT

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

...它失败了,因为

无法将类型“s”与“s1”匹配</p>

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

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

我知道有像STTmonad 转换器这样的东西在这里可能会有所帮助,但它与更雄心勃勃的数据结构(如哈希表)不兼容(或者是吗?),所以我不想使用它,只要我不能自由使用那里的经典ST图书馆。

如何正确设计这个模型?“正确地”我的意思不仅是“进行类型检查”,而且是“对代码的其余部分友好”和“尽可能灵活”。

标签: haskelldesign-patternsmonadsmonad-transformers

解决方案


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)

因为Comp关闭了s,你不能做出一个返回包含的动作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)

推荐阅读