Reputation: 7139
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
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