packages feed

cache-effectful (empty) → 0.0.1.0

raw patch · 7 files changed

+407/−0 lines, 7 filesdep +basedep +cachedep +cache-effectful

Dependencies added: base, cache, cache-effectful, effectful-core, hashable, tasty, tasty-hunit

Files

+ CHANGELOG.md view
@@ -0,0 +1,4 @@+# CHANGELOG++## v0.0.1.0 –+* Release
+ LICENSE.md view
@@ -0,0 +1,20 @@+Copyright (c) 2021 Hécate Moonlight++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,38 @@+# cache-effectful++A `Cache` effect for the [`effectful`][effectful] ecosystem.++## How to use++This library exposes the following elements:++* `Cache` — The type-level effect that you can declare in your type signatures.++```+populateIntCache :: (Cache Int Int :> es) => Eff es ()+```++* `insert`, `lookup`, `keys`, `delete`, `filterWithKey` – Operations on `Cache`. They should always be used with Type Applications when using literals:++```Haskell+insertAndLookup :: (Cache Int Int :> es) => Eff es (Maybe Int)+insertAndLookup = do+  insert @Int @Int 3 12+  lookup @Int 3++listKeys :: (Cache Int Int :> es) => Eff es [Int]+listKeys = do+  populateIntCache+  keys @Int @Int+```++* An IO Runner++```+runCacheIO (cache :: Data.Cache Int Int)+```++See the [tests][tests] to see an example use.++[effectful]: https://haskell-effectful.github.io+[tests]: https://github.com/haskell-effectful/cache-effectful/blob/main/cache-effectful/test/Main.hs
+ cache-effectful.cabal view
@@ -0,0 +1,84 @@+cabal-version: 3.0+name: cache-effectful+version: 0.0.1.0+synopsis: A Cache effect for the effectful ecosystem.+category: cache+homepage:+  https://github.com/haskell-effectful/cache-effectful/tree/main/cache-effectful#readme++bug-reports: https://github.com/haskell-effectful/cache-effectful/issues+author: Hécate Moonlight+maintainer: Hécate Moonlight+license: MIT+build-type: Simple+tested-with: ghc ==9.10.3 || ==9.12.4 || ==9.14.1+extra-source-files:+  LICENSE.md+  README.md++extra-doc-files:+  CHANGELOG.md++source-repository head+  type: git+  location: https://github.com/haskell-effectful/cache-effectful++common common-extensions+  default-extensions:+    ConstraintKinds+    DataKinds+    FlexibleContexts+    FlexibleInstances+    GADTs+    KindSignatures+    ScopedTypeVariables+    TypeApplications+    TypeOperators++  default-language: Haskell2010++common common-ghc-options+  ghc-options:+    -Wall+    -Wcompat+    -Widentities+    -Wincomplete-record-updates+    -Wincomplete-uni-patterns+    -Wpartial-fields+    -Wredundant-constraints+    -fhide-source-paths+    -Wno-unused-do-bind++common common-rts-options+  ghc-options:+    -rtsopts+    -threaded+    -with-rtsopts=-N++library+  import: common-extensions+  import: common-ghc-options+  hs-source-dirs: src+  exposed-modules: Effectful.Cache+  build-depends:+    base <4.23,+    cache >=0.1.3 && <0.2,+    effectful-core >=2.6 && <2.7,+    hashable >=1.5.1 && <1.6,++test-suite cache-effectful-test+  import: common-extensions+  import: common-ghc-options+  import: common-rts-options+  type: exitcode-stdio-1.0+  main-is: Main.hs+  other-modules: Utils+  hs-source-dirs: test+  build-depends:+    base,+    cache,+    cache-effectful,+    effectful-core,+    hashable,+    tasty >=1.5.4 && <1.6,+    tasty-hunit >=0.10.2 && <0.11,
+ src/Effectful/Cache.hs view
@@ -0,0 +1,160 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE Strict #-}+{-# LANGUAGE TypeFamilies #-}++-- |+--   Module      : Effectful.Cache+--   Copyright   : © Hécate Moonlight, 2021+--   License     : MIT+--   Maintainer  : hecate@glitchbra.in+--   Stability   : stable+--+--   An effect wrapper around 'Data.Cache' for the Effectful ecosystem+module Effectful.Cache+  ( -- * The /Cache/ effect+    Cache (..)++    -- * Handlers+  , runCacheIO++    -- * Cache operations+  , insert+  , lookup+  , keys+  , delete+  , filterWithKey+  ) where++import Control.Monad.IO.Class+import qualified Data.Cache as C+import Data.Hashable+import Data.Kind+import Effectful+import Effectful.Dispatch.Dynamic (interpret, send)+import Prelude hiding (lookup)++-- | Operations on a cache+--+-- Since it is an effect with type variables, you will have the duty of making unambiguous calls to the provided+-- functions.+-- This means that with numerical literals ('3', '4', etc),+-- [visible type applications](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_applications.html)+-- will be necessary. See each function's documentation for examples.+--+-- @since 0.0.1.0+data Cache k v :: Effect where+  Insert :: (Eq k, Hashable k) => k -> v -> Cache k v m ()+  Lookup :: (Eq k, Hashable k) => k -> Cache k v m (Maybe v)+  Keys :: Cache k v m [k]+  Delete :: (Eq k, Hashable k) => k -> Cache k v m ()+  FilterWithKey :: (Eq k, Hashable k) => (k -> v -> Bool) -> Cache k v m ()++-- |+-- @since 0.0.1.0+type instance DispatchOf (Cache k v) = 'Dynamic++-- | The default IO handler+--+-- @since 0.0.1.0+runCacheIO+  :: forall (k :: Type) (v :: Type) (es :: [Effect]) (a :: Type)+   . (Hashable k, IOE :> es)+  => C.Cache k v+  -> Eff (Cache k v : es) a+  -> Eff es a+runCacheIO cache = interpret $ \_ -> \case+  Insert key value -> liftIO $ C.insert cache key value+  Lookup key -> liftIO $ C.lookup cache key+  Keys -> liftIO $ C.keys cache+  Delete key -> liftIO $ C.delete cache key+  FilterWithKey fun -> liftIO $ C.filterWithKey fun cache++-- | Insert an item in the cache, using the default expiration value of the cache.+--+-- @since 0.0.1.0+insert+  :: forall (k :: Type) (v :: Type) (es :: [Effect])+   . (Hashable k, Cache k v :> es)+  => k+  -> v+  -> Eff es ()+insert key value = send $ Insert key value++-- | Lookup an item with the given key, and delete it if it is expired.+--+-- The function will only return a value if it is present in the cache and if the item is not expired.+-- The function will eagerly delete the item from the cache if it is expired.+--+-- @since 0.0.1.0+lookup+  :: forall (k :: Type) (v :: Type) (es :: [Effect])+   . (Hashable k, Cache k v :> es)+  => k+  -> Eff es (Maybe v)+lookup key = send $ Lookup key++-- | List all the keys of the cache.+--+-- Since 'Cache' has type variables, you will need to use visible type applications or explicitly typed+-- arguments for the key *and* value parameters to distinguish this 'Cache' from potentially other ones.+--+-- === __Example__+--+-- > listKeys :: (Cache Int Int :> es) => Eff es [Int]+-- > listKeys = do+-- >   mapM_ (\(k,v) -> insert @Int @Int k v) [(2,4),(3,6),(4,8),(5,10)]+-- >   keys @Int @Int -- [2,3,4,5]+--+-- @since 0.0.1.0+keys+  :: forall (k :: Type) (v :: Type) (es :: [Effect])+   . Cache k v :> es+  => Eff es [k]+keys = send @(Cache k v) Keys++-- | Delete the provided key from the cache it is present.+--+-- Since 'Cache' has type variables, you will need to use visible type applications or explicitly typed+-- arguments for the key *and* value parameters to distinguish this 'Cache' from potentially other ones.+--+-- === __Example__+--+-- > deleteKeys :: (Cache Int Int :> es) => Eff es [Int]+-- > deleteKeys = do+-- >   mapM_ (\(k,v) -> insert @Int @Int k v) [(2,4),(3,6),(4,8),(5,10)]+-- >   delete @Int @Int 3+-- >   delete @Int @Int 5+-- >   keys @Int @Int -- [2,4]+--+-- @since 0.0.1.0+delete+  :: forall (k :: Type) (v :: Type) (es :: [Effect])+   . (Hashable k, Cache k v :> es)+  => k+  -> Eff es ()+delete key = send @(Cache k v) $ Delete key++-- | Keeps elements that satisfy the predicate (used for cache invalidation).+--+-- Note that the predicate might be called for expired items.+--+-- Since 'Cache' has type variables, you will need to use visible type applications or explicitly typed+-- arguments for the key *and* value parameters to distinguish this 'Cache' from potentially other ones.+--+-- === __Example__+--+-- > filterKeys :: (Cache Int Int :> es) => Eff es [Int]+-- > filterKeys = do+-- >   mapM_ (\(k,v) -> insert @Int @Int k v) [(2,4),(3,6),(4,8),(5,10)]+-- >   filterWithKey @Int @Int (\k _ -> k /= 3)+-- >   keys @Int @Int -- [2,4,5]+--+-- @since 0.0.1.0+filterWithKey+  :: forall (k :: Type) (v :: Type) (es :: [Effect])+   . (Hashable k, Cache k v :> es)+  => (k -> v -> Bool)+  -> Eff es ()+filterWithKey fun = send $ FilterWithKey fun
+ test/Main.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE NoOverloadedStrings #-}++module Main where++import qualified Data.Cache as C+import Effectful+import Test.Tasty+import Test.Tasty.HUnit+import Prelude hiding (lookup)++import Effectful.Cache+import qualified Utils as U++main :: IO ()+main =+  defaultMain $+    testGroup+      "cache-effectful"+      [ testCase "Insert & Lookup" $ testInsertAndLookup =<< initIntCache+      , testCase "Listing keys" $ testListKeys =<< initIntCache+      , testCase "Deleting keys" $ testDeleteKeys =<< initIntCache+      , testCase "Filter with key" $ testFilterWithKey =<< initIntCache+      ]++initStringCache :: IO (C.Cache String String)+initStringCache = C.newCache Nothing++initIntCache :: IO (C.Cache Int Int)+initIntCache = C.newCache Nothing++populateIntCache :: Cache Int Int :> es => Eff es ()+populateIntCache =+  mapM_ (\(k, v) -> insert @Int @Int k v) [(2, 4), (3, 6), (4, 8), (5, 10)]++---++testInsertAndLookup :: C.Cache Int Int -> Assertion+testInsertAndLookup cache = runEff $ do+  result <- runCacheIO cache insertAndLookup+  U.assertEqual "Looking up key 3 yields the value 12" (Just 12) result+  where+    insertAndLookup :: Cache Int Int :> es => Eff es (Maybe Int)+    insertAndLookup = do+      insert @Int @Int 3 12+      lookup @Int 3++testListKeys :: C.Cache Int Int -> Assertion+testListKeys cache = runEff $ do+  result <- runCacheIO cache listKeys+  U.assertEqual "Correct list of key in the cache" [2, 3, 4, 5] result+  where+    listKeys :: Cache Int Int :> es => Eff es [Int]+    listKeys = do+      populateIntCache+      keys @Int @Int++testDeleteKeys :: C.Cache Int Int -> Assertion+testDeleteKeys cache = runEff $ do+  result <- runCacheIO cache deleteKeys+  U.assertEqual "Keys are deleted" [2, 4] result+  where+    deleteKeys :: Cache Int Int :> es => Eff es [Int]+    deleteKeys = do+      populateIntCache+      delete @Int @Int 3+      delete @Int @Int 5+      keys @Int @Int++testFilterWithKey :: C.Cache Int Int -> Assertion+testFilterWithKey cache = runEff $ do+  result <- runCacheIO cache filterKeys+  U.assertEqual "Keys are properly filtered" [2, 4, 5] result+  where+    filterKeys :: Cache Int Int :> es => Eff es [Int]+    filterKeys = do+      populateIntCache+      filterWithKey @Int @Int (\k _ -> k /= 3)+      keys @Int @Int -- [2,4,5]
+ test/Utils.hs view
@@ -0,0 +1,23 @@+module Utils+  ( assertBool+  , assertEqual+  , assertFailure+  ) where++import Effectful+import GHC.Stack+import qualified Test.Tasty.HUnit as T++assertBool :: (HasCallStack, IOE :> es) => String -> Bool -> Eff es ()+assertBool msg p = liftIO $ T.assertBool msg p++assertEqual+  :: (HasCallStack, Eq a, Show a, IOE :> es)+  => String+  -> a+  -> a+  -> Eff es ()+assertEqual msg expected given = liftIO $ T.assertEqual msg expected given++assertFailure :: (HasCallStack, IOE :> es) => String -> Eff es a+assertFailure msg = liftIO $ T.assertFailure msg