修改 ReaderT 中的 ST 依赖环境 - `local` 函数的问题

Modify ST dependent environment in ReaderT – problem with `local` function

此问题是此线程的 sequel:

我问的是在 ReaderT 的环境中携带 STRef 并在其下执行 ST 操作。我的设置现在看起来像:

import Data.HashTable.ST.Cuckoo as HT

-- |Environment for Comp
newtype Env s = Env { dataspace :: HashTable s Int Data
                    , namespace :: Map Name Int }

-- |Main computation monad
newtype Comp a = Comp (forall s. ReaderT (Env s) (ST s) a)


-- |Evaluate computation
runComp (Comp c) = runST $ do
    ds <- HT.new
    runReaderT c (Env ds empty)


-- |Perform an action on `dataspace` hashmap
onDataspace :: (forall s. HashTable s Int Data -> ST s a) -> Comp a
onDataspace f = Comp $ asks dataspace >>= lift . f

而且它总体上很酷——我可以自由地访问或修改 dataspace。但是,当我添加不可变 namespace 时,我开始挣扎。我需要的功能是 运行ning Comp 操作更新 namespace 不会影响进一步计算的命名空间——正是 local 所做的。

首先我想为 Comp 编写 MonadReader 实例,但是我遇到了 ST 的幻像类型并得到了 illegal instance 错误:

instance MonadReader (Env s) Comp where {}
instance MonadReader (forall s. Env s) Comp where {}
instance forall s. MonadReader (Env s) Comp where {}

完整错误信息:

Illegal instance declaration for
     ‘MonadReader (EvalEnv s) Evaluator’
     The coverage condition fails in class ‘MonadReader’
       for functional dependency: ‘m -> r’
     Reason: lhs type ‘Evaluator’
       does not determine rhs type ‘EvalEnv s’
     Un-determined variable: s

我理解这个错误,但我看不出有什么办法可以绕过它。老实说,我真的不需要完整的 local 功能。我只需要能够 运行 Comp 与不同的 namespace,但相同的 dataspace.

最好的解决方案是提供完整的 MonadReader 实例。我知道这可能是不可能的,所以作为一种解决方法,我想要一个函数

withNs :: Map Name Int -> Comp a -> Comp a

总结:我希望能够 运行 Comp 修改 namespace 而保持 dataspace 不变作为参考,保留所有更改。

怎么做?如果需要,我可以接受修改我的初始设置。

ST 的范围参数 s 应保留在外部:

newtype Comp s a = Comp (ReaderT (Env s) (ST s) a)

唯一需要更高级别类型的地方是调用 runST

runComp :: (forall s. Comp s a) -> a
runComp = runST $ do
  ds <- HT.new
  runReaderT c (Env ds empty)

在其他任何地方,您都可以在 s 中简单地进行参数化。

doStuff :: Comp s Bool
doMoreStuff :: Comp s Int

那么MonadReader实例可以写成:

instance MonadReader (Env s) (Comp s) where
  ...