radrow
radrow

Reputation: 7139

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

This question is a sequel of this thread: https://stackoverflow.com/a/54317095/4400060

I was asking there about carrying STRef in ReaderT's environment and performing ST-actions under it. My setup now looks like:

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

And it works cool in general – I can access or modify dataspace in place freely. However, when I have added immutable namespace I started to struggle. Feature I need is running Comp action with updated namespace in the way that it won't affect further computations' namespaces – exactly what local does.

First of all I wanted to write MonadReader instance for Comp, however I faced the ST's phantom type and got illegal instance error:

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

Full error message:

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

I understand this error, but I see no way bypassing it. To be honest I don't really require full local function. I only need to be able to run Comp with different namespace, but same dataspace.

The best solution would be to provide full MonadReader instance. I am aware that it might not be possible, so as a workaround I would like to have a function

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

Summarizing: I want to be able to run Comp with modified namespace while leaving dataspace unchanged as a reference keeping all changes under it.

How to do that? I can accept modifying my initial setup if needed.

Upvotes: 0

Views: 166

Answers (1)

Li-yao Xia
Li-yao Xia

Reputation: 33464

The scope parameter s of ST should remain outside:

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

The only place where you need a higher-rank type is when calling runST.

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

Everywhere else you can simply be parametric in s.

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

Then the MonadReader instance can be written:

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

Upvotes: 2

Related Questions