effectful-data-cache 0.2.1.0 → 0.2.2.0
raw patch · 5 files changed
+120/−5 lines, 5 filesdep +stmdep +unordered-containersPVP ok
version bump matches the API change (PVP)
Dependencies added: stm, unordered-containers
API changes (from Hackage documentation)
+ Effectful.Cache.STM: data Store k v
+ Effectful.Cache.STM: newStore :: Maybe TimeSpec -> IO (Store k v)
+ Effectful.Cache.STM: runCacheSTM :: forall k v (es :: [Effect]) a. IOE :> es => Maybe TimeSpec -> Eff (Cache k v ': es) a -> Eff es a
+ Effectful.Cache.STM: runCacheSTMWith :: forall k v (es :: [Effect]) a. IOE :> es => Store k v -> Eff (Cache k v ': es) a -> Eff es a
Files
- CHANGELOG.md +6/−0
- effectful-data-cache.cabal +7/−1
- src/Effectful/Cache/STM.hs +91/−0
- test/Effectful/Cache/STMSpec.hs +9/−0
- test/Effectful/CacheSpec.hs +7/−4
CHANGELOG.md view
@@ -1,3 +1,9 @@+## [2.2.0](https://github.com/haskell-github-trust/effectful-data-cache/compare/2.1.0...2.2.0) (2026-10-27)++### Features++* add stm-based interpreter ([5b76933](https://github.com/haskell-github-trust/effectful-data-cache/commit/5b7693322b1fef87f9753aaf24e527764bf688a2))+ ## [2.1.0](https://github.com/haskell-github-trust/effectful-data-cache/compare/2.0.2...2.1.0) (2026-10-26) ### Features
effectful-data-cache.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: effectful-data-cache-version: 0.2.1.0+version: 0.2.2.0 synopsis: Data cache effect for the `effectful` system description: Please see the README on GitHub at <https://github.com/haskell-github-trust/effectful-data-cache#readme> category: cache@@ -29,6 +29,7 @@ library exposed-modules: Effectful.Cache+ Effectful.Cache.STM other-modules: Paths_effectful_data_cache autogen-modules:@@ -42,12 +43,15 @@ , clock ==0.8.* , effectful-core >=2.3 && <3 , hashable >=1.5 && <2+ , stm >=2.5 && <3+ , unordered-containers ==0.2.* default-language: Haskell2010 test-suite effectful-data-cache-test type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:+ Effectful.Cache.STMSpec Effectful.CacheSpec Paths_effectful_data_cache autogen-modules:@@ -65,4 +69,6 @@ , effectful-data-cache , hashable >=1.5 && <2 , hspec+ , stm >=2.5 && <3+ , unordered-containers ==0.2.* default-language: Haskell2010
+ src/Effectful/Cache/STM.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}++-- | 'Cache' interpreter over a 'HashMap' in a 'TVar', without "Data.Cache".+module Effectful.Cache.STM (+ Store,+ newStore,+ runCacheSTM,+ runCacheSTMWith,+) where++import Control.Concurrent.STM (+ STM,+ TVar,+ atomically,+ modifyTVar',+ newTVarIO,+ readTVar,+ writeTVar,+ )+import qualified Data.HashMap.Strict as HM+import Effectful (Eff, IOE, MonadIO (liftIO), type (:>))+import Effectful.Cache (Cache (..))+import Effectful.Dispatch.Dynamic (interpret)+import System.Clock (Clock (Monotonic), TimeSpec, getTime)++-- | Entries with their absolute expiration, and the default expiration.+--+-- @since 0.2.2.0+data Store k v = Store (TVar (HM.HashMap k (v, Maybe TimeSpec))) (TVar (Maybe TimeSpec))++-- | Fresh store with the given default expiration.+--+-- @since 0.2.2.0+newStore :: Maybe TimeSpec -> IO (Store k v)+newStore ts = Store <$> newTVarIO HM.empty <*> newTVarIO ts++-- | Run against a fresh store with the given default expiration.+--+-- @since 0.2.2.0+runCacheSTM+ :: forall k v es a+ . IOE :> es+ => Maybe TimeSpec+ -> Eff (Cache k v : es) a+ -> Eff es a+runCacheSTM ts eff = do+ s <- liftIO (newStore ts)+ runCacheSTMWith s eff++-- | Run against an existing 'Store', e.g. one shared between threads.+--+-- @since 0.2.2.0+runCacheSTMWith+ :: forall k v es a+ . IOE :> es+ => Store k v+ -> Eff (Cache k v : es) a+ -> Eff es a+runCacheSTMWith s = interpret $ \_ op -> liftIO $ do+ now <- getTime Monotonic+ atomically (withStore s now op)++withStore :: Store k v -> TimeSpec -> Cache k v m a -> STM a+withStore (Store ref def) now op = case op of+ Insert k v -> readTVar def >>= \ts -> modifyTVar' ref (HM.insert k (v, (now +) <$> ts))+ Insert' ts k v -> modifyTVar' ref (HM.insert k (v, (now +) <$> ts))+ Lookup k -> do+ m <- HM.lookup k <$> readTVar ref+ case m of+ Just (v, e) | live e -> pure (Just v)+ Just _ -> Nothing <$ modifyTVar' ref (HM.delete k)+ Nothing -> pure Nothing+ Lookup' k -> do+ m <- HM.lookup k <$> readTVar ref+ pure $ case m of+ Just (v, e) | live e -> Just v+ _ -> Nothing+ Keys -> HM.keys <$> readTVar ref+ Delete k -> modifyTVar' ref (HM.delete k)+ FilterWithKey p -> modifyTVar' ref (HM.filterWithKey (\k (v, _) -> p k v))+ Purge -> writeTVar ref HM.empty+ PurgeExpired -> modifyTVar' ref (HM.filter (live . snd))+ Size -> HM.size <$> readTVar ref+ DefaultExpiration -> readTVar def+ SetDefaultExpiration ts -> writeTVar def ts+ where+ live = maybe True (> now)
+ test/Effectful/Cache/STMSpec.hs view
@@ -0,0 +1,9 @@+module Effectful.Cache.STMSpec (spec) where++import Effectful (runEff)+import Effectful.Cache.STM (runCacheSTM)+import Effectful.CacheSpec (specWith)+import Test.Hspec (Spec)++spec :: Spec+spec = specWith (\ts -> runEff . runCacheSTM ts)
test/Effectful/CacheSpec.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeApplications #-} -module Effectful.CacheSpec (spec) where+module Effectful.CacheSpec (spec, specWith) where import Data.List (sort) import Effectful@@ -10,14 +11,16 @@ import Test.Hspec import Prelude hiding (lookup) -run :: Maybe TimeSpec -> Eff '[Cache String Int, IOE] a -> IO a-run ts = runEff . runCache ts+type Runner = forall a. Maybe TimeSpec -> Eff '[Cache String Int, IOE] a -> IO a expired :: Maybe TimeSpec expired = Just (TimeSpec (-1) 0) spec :: Spec-spec = do+spec = specWith (\ts -> runEff . runCache ts)++specWith :: Runner -> Spec+specWith run = do it "looks up what it inserted" $ do r <- run Nothing $ do insert "a" (1 :: Int)