rad*_*row 3 monads haskell design-patterns monad-transformers
我正在研究一些需要在某些关键时刻使用可变数据的更大计算.我想尽可能地避免使用IO.我的模型使用的constist 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)
Run Code Online (Sandbox Code Playgroud)
评估员:
runComp (Comp c) = runST $ do
s <- newSTRef 0
runReaderT c (Env {supply = s}) -- this is of type `ST s a`
Run Code Online (Sandbox Code Playgroud)
......它失败了,因为
无法将类型's'与's1'匹配
这似乎很清楚,因为我混合了两个独立的幻影ST状态.但是,我不知道如何绕过它.我试过添加幻像s作为Comp和Env参数,但结果是相同的,代码变得更加丑陋(但由于缺少这些foralls 而不那么可疑).
我试图在这里实现的功能是随时supply可访问,但不明确传递(它不值得).存放它的最舒适的地方是在环境中,但我认为没有办法初始化它.
我知道有一些像STTmonad变换器这样的东西可能会有帮助,但是它不像哈希表这样雄心勃勃的数据结构兼容(或者它是什么?),所以我不想使用它,只要我不能自由使用经典ST图书馆.
如何正确设计这个模型?通过"正确",我的意思不仅是"对于typecheck",而且"对于代码的其余部分很好"和"尽可能灵活".
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)
Run Code Online (Sandbox Code Playgroud)
因为Comp结束s,你不能做出一个返回所包含的动作STRef; 但是你可以在内部公开一个使用引用的动作:
onRef :: (forall s. STRef s Int -> ST s a) -> Comp a
onRef f = Comp $ asks supply >>= lift . f
Run Code Online (Sandbox Code Playgroud)
例如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)
Run Code Online (Sandbox Code Playgroud)
然后你就可以写了
getSup :: Comp s (STRef s Int)
getSup = Comp (asks supply)
Run Code Online (Sandbox Code Playgroud)