Carry STRef implicitly in an environment during computation

这一生的挚爱 提交于 2020-01-24 09:20:06

问题


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.

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)

And the evaluator:

runComp (Comp c) = runST $ do
   s <- newSTRef 0
  runReaderT c (Env {supply = s})  -- this is of type `ST s a`

...and it fails because

Couldn't match type ‘s’ with ‘s1’

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 foralls).

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.

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".


回答1:


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)

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

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)

Then you can write

getSup :: Comp s (STRef s Int)
getSup = Comp (asks supply)


来源:https://stackoverflow.com/questions/54316935/carry-stref-implicitly-in-an-environment-during-computation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!