问题描述
我正在做一些更大的计算,需要在某些关键时刻使用可变数据.我想尽可能地避免IO.我的模型以前是在State
数据类型上在ReaderT
上构造了ExceptT
,现在我想用提到的ST
代替State
.
I am working on some bigger computation that requires use of mutable data in some critical moments. I want to avoid IO as much as I can.My model used to constist of ExceptT
over ReaderT
over State
datatype, and now I want to replace State
with mentioned ST
.
为简化起见,假设我想在整个计算过程中将单个STRef
与Int
保持在一起,并跳过ExceptT
外层.我最初的想法是将STRef s Int
放入ReaderT
的环境中:
To simplify, let's assume I would like to keep single STRef
with an Int
during the whole computation, and let's skip the ExceptT
outer layer. My initial idea was to put STRef s Int
into ReaderT
's environment:
{-#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`
...它失败了,因为
...and it fails because
这似乎很清楚,因为我混合了两个单独的幻影ST状态.但是,我不知道如何绕过它.我曾尝试将幻像s
添加为Comp
和Env
参数,但是结果是相同的,并且代码变得更丑陋(但由于缺少这些forall
,所以可疑程度较低).
Which seems to be clear, because I have mixed two separate phantom ST states. However, I have no idea how to bypass it. I have tried adding phantom s
as Comp
and Env
parameter, but the result was the same and the code got uglier (but less suspicious because of lack of these forall
s).
我要在此处实现的功能是使supply
随时可访问,但未显式传递(它不值得).存放它最舒适的地方是在环境中,但我看不到对其进行初始化的方法.
The feature I am trying to achieve here is to make supply
accessible at any time, but not passed explicitly (it does not deserve it). The most comfortable place to store it is in the environment, but I see no way in initializing it.
我知道有一个像STT
monad转换器这样的东西可能在这里有帮助,但是它与更雄心勃勃的数据结构(例如哈希表)不兼容(或者是?),所以我不想将其用作只要我不能在那里自由使用经典的ST
库.
I know there is such a thing like STT
monad transformer which may help here, but it is not compatible with more ambitious data structures like hashtables (or is it?), so I don't want to use it as long as I cannot freely use classic ST
libraries there.
如何正确设计此模型? 适当"的意思不仅是进行类型检查",而且是对其余代码友好"和尽可能灵活".
How to properly design this model? By "properly" I mean not only "to typecheck" but to "to be nice to the rest of the code" and "as flexible as possible".
推荐答案
runST
提供一个多态参数,并且您希望您的参数来自Comp
. Ergo Comp
必须包含多态的东西.
runST
must be given a polymorphic argument, and you want your argument to come from Comp
. Ergo Comp
must contain a polymorphic thing.
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
的操作;但是您可以公开内部使用引用的操作:
Because Comp
closes over s
, you can't make an action which returns the contained STRef
; but you can expose an action which uses the reference internally:
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
要求具有多态性.所以:
e.g. onRef readSTRef :: Comp Int
and onRef (`modifySTRef` succ) :: Comp ()
. Another choice that might be more ergonomic is to make Comp
itself monomorphic, but have runComp
demand a polymorphic action. So:
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)
这篇关于计算期间在环境中隐式携带STRef的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!