在计算期间在环境中隐式携带 STRef
Carry STRef implicitly in an environment during computation
我正在进行一些更大的计算,需要在某些关键时刻使用可变数据。我想尽可能地避免 IO。
我的模型过去由 ExceptT
over ReaderT
over State
数据类型组成,现在我想用提到的 ST
.
替换 State
为简化起见,假设我想在整个计算过程中保持单个 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 状态。但是,我不知道如何绕过它。我尝试添加 phantom s
作为 Comp
和 Env
参数,但结果是一样的,代码变得更丑陋(但由于缺少这些 forall
s 而不太可疑).
我这里想实现的功能是让supply
随时可以访问,但是不显式传递(不值得)。存放它最舒服的地方是在环境中,但我没办法初始化它。
我知道有像 STT
monad transformer 这样的东西可能对这里有帮助,但它与哈希表等更雄心勃勃的数据结构不兼容(或者是吗?),所以我不想要只要我不能在那里自由使用经典 ST
库就可以使用它。
如何正确设计这个模型? "properly" 我的意思不仅是 "to typecheck" 而是 "to be nice to the rest of the code" 和 "as flexible as possible".
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
关闭,你不能做出 returns 包含 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)
我正在进行一些更大的计算,需要在某些关键时刻使用可变数据。我想尽可能地避免 IO。
我的模型过去由 ExceptT
over ReaderT
over State
数据类型组成,现在我想用提到的 ST
.
State
为简化起见,假设我想在整个计算过程中保持单个 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 状态。但是,我不知道如何绕过它。我尝试添加 phantom s
作为 Comp
和 Env
参数,但结果是一样的,代码变得更丑陋(但由于缺少这些 forall
s 而不太可疑).
我这里想实现的功能是让supply
随时可以访问,但是不显式传递(不值得)。存放它最舒服的地方是在环境中,但我没办法初始化它。
我知道有像 STT
monad transformer 这样的东西可能对这里有帮助,但它与哈希表等更雄心勃勃的数据结构不兼容(或者是吗?),所以我不想要只要我不能在那里自由使用经典 ST
库就可以使用它。
如何正确设计这个模型? "properly" 我的意思不仅是 "to typecheck" 而是 "to be nice to the rest of the code" 和 "as flexible as possible".
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
关闭,你不能做出 returns 包含 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)