Hi Polysemisers, I'm trying to implement an effect that caches the results of data obtained using other effects. I have code that feels close, but I'm stuck:
{-# LANGUAGE DataKinds #-}{-# LANGUAGE FlexibleContexts #-}{-# LANGUAGE GADTs #-}{-# LANGUAGE LambdaCase #-}{-# LANGUAGE PolyKinds #-}{-# LANGUAGE ScopedTypeVariables #-}{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE TypeApplications #-}{-# LANGUAGE TypeOperators #-}importData.Hashable(Hashable)importData.HashMap.Strict(HashMap)importqualifiedData.HashMap.StrictasHMimportPolysemyimportPolysemy.State-- import Polysemy.AtomicState -- TODO: thread safetydataCachekvmawhereCacheGet::(k->mv)-- ^ How to retrieve a value if it doesn't exist in the cache->(v->mv)-- ^ How to return a value if it does exist in the cache (useful to e.g. add logging, otherwise just use 'pure'->k-- ^ The key to retrieve->Cachekvmv-- ^ Returns the valuemakeSem''CacherunCacheAsHashMap::forallkvra.(Eqk,Hashablek)=>Sem(Cachekv':r)a->SemrarunCacheAsHashMap=evalState(HM.empty@k@v).reinterpretH(\caseCacheGetfetchreturnFromCachek->docache<-getcaseHM.lookupkcacheofJustv->doreturnFromCache'<-bindTreturnFromCache-- f v -> Sem (Cache k v ': State (HashMap k v) ': r) (f v)pureTv>>=returnFromCache'Nothing->dofetch'<-bindTfetch-- f k -> Sem (Cache k v ': State (HashMap k v) ': r) (f v)fv<-pureTk>>=fetch'letfcache=(\v'->HM.insertkv'cache)<$>fvput'<-bindTputput'fcachepurefv)
The error I'm getting is:
src/Cache.hs:61:25-27: error:
• Ambiguous use of effect 'State'
Possible fix:
add (Member (State (HashMap k v)) r0) to the context of
the type signature
If you already have the constraint you want, instead
add a type application to specify
'HashMap k v' directly, or activate polysemy-plugin which
can usually infer the type correctly.
• In the first argument of ‘bindT’, namely ‘put’
In a stmt of a 'do' block: put' <- bindT put
In the expression:
do fetch' <- bindT fetch
fv <- pureT k >>= fetch'
let fcache = (\ v' -> ...) <$> fv
put' <- bindT put
....
|
61 | put' <- bindT put
| ^^^
Oh, and it's not as simple as the error suggests: I already have polysemy-plugin running, and if I add a type application so the line is put' <- bindT (put @(HashMap k v)) then I still get the same error.
I think Cache could be simplified a little bit: first, (k, k -> m v) can be turned into (k , m v) unless you want to modify k dynamically before feeding it into the action for retrieval.
Second, instead of carrying e.g. logging action around, you could possibly intercept the effect, thus getting rid of one more field?
In case we apply these changes, maybe something like this would work:
Great!
That recursive call is really us saying "we want to run that effectful argument to Cache in same way as the computation we're in". Reason why polysemy doesn't do this for you is that you may want to run it in a different way.
Maybe this thing could be documented somewhere close to Tactics/Strategy "you may often have to recursively blabla..", I had the same question before and it was the same answer too.
Hi Polysemisers, I'm trying to implement an effect that caches the results of data obtained using other effects. I have code that feels close, but I'm stuck:
The error I'm getting is:
Oh, and it's not as simple as the error suggests: I already have polysemy-plugin running, and if I add a type application so the line is
put' <- bindT (put @(HashMap k v))
then I still get the same error.put
isn't anm a
in your effect, it's aSem
! you usedget
withoutbindT
, same here.I think
Cache
could be simplified a little bit: first,(k, k -> m v)
can be turned into(k , m v)
unless you want to modifyk
dynamically before feeding it into the action for retrieval.Second, instead of carrying e.g. logging action around, you could possibly
intercept
the effect, thus getting rid of one more field?In case we apply these changes, maybe something like this would work:
Thanks! I'll take a look tomorrow morning.
Thanks @TheMatten, starting with your code I got something working the way I wanted:
I have no idea how that recursive call to runCacheInHashMap works. All this Tactical stuff is mind bending. But it works!
Great!
That recursive call is really us saying "we want to run that effectful argument to
Cache
in same way as the computation we're in". Reason whypolysemy
doesn't do this for you is that you may want to run it in a different way.Maybe this thing could be documented somewhere close to Tactics/Strategy "you may often have to recursively blabla..", I had the same question before and it was the same answer too.
(or I just missed it in the docs :thinking: )