在计算过程中在环境中隐式携带STRef

时间:2019-01-22 21:47:33

标签: haskell design-patterns monads monad-transformers

我正在做一些更大的计算,这需要在关键时刻使用可变数据。我想尽可能避免IO。 我的模型过去通常由{{1}到ExceptTReaderT数据类型构成,现在我想用提到的State代替State

为简化起见,假设我想在整个计算过程中将单个ST和一个STRef保持在一起,而跳过Int外层。我最初的想法是将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)

...它失败了,因为

  

无法将类型“ s”与“ s1”匹配

这似乎很清楚,因为我混合了两个单独的幻影ST状态。但是,我不知道如何绕过它。我曾尝试将幻像runComp (Comp c) = runST $ do s <- newSTRef 0 runReaderT c (Env {supply = s}) -- this is of type `ST s a` 添加为sComp参数,但是结果相同,代码变得更难看(但由于缺少这些Env而引起的可疑程度降低了) )。

我要在此处实现的功能是随时使forall可以访问,但是没有显式地传递(不值得)。存放它最舒适的地方是环境,但是我看不到初始化它的方法。

我知道有一种类似supply的monad转换器,在这里可能会有所帮助,但是它与诸如哈希表之类的更宏大的数据结构不兼容(或者是?),所以我不想使用只要我不能在那里自由使用经典的STT库就可以了。

如何正确设计此模型? “适当地”不仅是指“进行类型检查”,而且是“对其余代码友好”和“尽可能灵活”。

1 个答案:

答案 0 :(得分: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)

由于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)