我正在做一些更大的计算,这需要在关键时刻使用可变数据。我想尽可能避免IO。
我的模型过去通常由{{1}到ExceptT
到ReaderT
数据类型构成,现在我想用提到的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`
添加为s
和Comp
参数,但是结果相同,代码变得更难看(但由于缺少这些Env
而引起的可疑程度降低了) )。
我要在此处实现的功能是随时使forall
可以访问,但是没有显式地传递(不值得)。存放它最舒适的地方是环境,但是我看不到初始化它的方法。
我知道有一种类似supply
的monad转换器,在这里可能会有所帮助,但是它与诸如哈希表之类的更宏大的数据结构不兼容(或者是?),所以我不想使用只要我不能在那里自由使用经典的STT
库就可以了。
如何正确设计此模型? “适当地”不仅是指“进行类型检查”,而且是“对其余代码友好”和“尽可能灵活”。
答案 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 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)