我正在从事一些更大的计算,需要在某些关键时刻使用可变数据。我想尽可能避免IO。
我的模型过去由 ExceptT
over ReaderT
over 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)
评估者:
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 状态。但是,我不知道如何绕过它。我尝试添加幻影 s
作为 Comp
和 Env
参数,但结果是相同的,并且代码变得更丑陋(但不太可疑,因为缺少这些forall
)。
我在这里试图实现的功能是使 supply
可以随时访问,但不显式传递(它不值得)。最舒服的存储位置是环境中,但我看不到初始化它的方法。
我知道有像 STT
monad 转换器这样的东西可能会有所帮助,但它与哈希表等更雄心勃勃的数据结构不兼容(或者是吗?),所以我不只要我不能在那里自由地使用经典的 ST
库,我就想使用它。
如何正确设计这个模型?我所说的“正确”不仅指“类型检查”,还指“对代码的其余部分友好”和“尽可能灵活”。
最佳答案
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 Int
和 onRef (`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)
关于haskell - 计算期间在环境中隐式携带 STRef,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54316935/