haxl-effectful 1.1.0 → 1.1.1
raw patch · 5 files changed
+52/−9 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +6/−0
- haxl-effectful.cabal +2/−2
- src/Effectful/Haxl.hs +3/−3
- test/ExampleDataSource.hs +3/−3
- test/Main.hs +38/−1
CHANGELOG.md view
@@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [1.1.1] - 2026-10-01++### Changed++- `haxl`, `haxlWithWrites`, `haxlWithStats` no longer clone the environment.+ ## [1.1.0] - 2026-08-04 ### Added
haxl-effectful.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: haxl-effectful-version: 1.1.0+version: 1.1.1 category: Concurrency synopsis: Effectful bindings for Haxl description:@@ -67,7 +67,7 @@ build-depends: base >=4.10 && <5,- effectful >=2.6 && <2.7,+ effectful >=2.6 && <2.8, hashable >=1.5 && <1.6, library
src/Effectful/Haxl.hs view
@@ -256,13 +256,13 @@ haxl :: forall u w es a. (Monoid w) => GenHaxl u w es a -> Eff (Haxl u w ': es) a haxl g = do env <- initEnv- inject $ unsafeConcUnliftIO Persistent Unlimited $ Haxl.runHaxl env . unHaxl g+ inject $ unsafeSeqUnliftIO $ Haxl.runHaxl env . unHaxl g -- | Like 'haxl', but also return everything written via the 'w' monoid during the run. haxlWithWrites :: forall u w es a. (Monoid w) => GenHaxl u w es a -> Eff (Haxl u w ': es) (a, w) haxlWithWrites g = do env <- initEnv- inject $ unsafeConcUnliftIO Persistent Unlimited $ Haxl.runHaxlWithWrites env . unHaxl g+ inject $ unsafeSeqUnliftIO $ Haxl.runHaxlWithWrites env . unHaxl g -- | Like 'haxl', but also return the 'Stats' collected during the run. --@@ -271,7 +271,7 @@ haxlWithStats :: forall u w es a. (Monoid w) => GenHaxl u w es a -> Eff (Haxl u w ': es) (a, Stats) haxlWithStats g = do env <- initEnv- a <- inject $ unsafeConcUnliftIO Persistent Unlimited $ Haxl.runHaxl env . unHaxl g+ a <- inject $ unsafeSeqUnliftIO $ Haxl.runHaxl env . unHaxl g stats <- unsafeEff_ . readIORef $ statsRef env pure (a, stats)
test/ExampleDataSource.hs view
@@ -28,7 +28,7 @@ import System.IO (hPutStr, stderr) import Prelude --- Here is an example minimal data source. Our data source will have+-- Here is an example minimal data source. Our data source will have -- two requests: -- -- countAardvarks :: String -> Haxl Int@@ -48,13 +48,13 @@ ListWombats :: Id -> ExampleReq [Id] -- The request type (ExampleReq) is parameterized by the result type of--- each request. Each request might have a different result, so we use a+-- each request. Each request might have a different result, so we use a -- GADT - a data type in which each constructor may have different type -- parameters. Here CountAardvarks is a request that takes a String -- argument and its result is Int, whereas ListWombats takes an Id -- argument and returns a [Id]. --- The request type needs instances for 'Eq1' and 'Hashable1'. These+-- The request type needs instances for 'Eq1' and 'Hashable1'. These -- are like 'Eq' and 'Hashable', but for types with one parameter -- where the parameter is irrelevant for hashing and equality. -- These two instances are used to support caching of requests.
test/Main.hs view
@@ -5,6 +5,8 @@ module Main where +import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)+import Control.Monad (replicateM) import Data.Functor ((<&>)) import Data.List qualified as List import Data.Maybe (mapMaybe)@@ -18,7 +20,7 @@ import Effectful.Haxl as Haxl import Effectful.Hspec import Effectful.Prim (Prim, runPrim)-import Effectful.Prim.IORef (modifyIORef', newIORef, readIORef)+import Effectful.Prim.IORef (atomicModifyIORef', modifyIORef', newIORef, readIORef) import ExampleDataSource (Id (..)) import ExampleDataSource qualified import GHC.Stack (HasCallStack)@@ -66,6 +68,9 @@ asyncExceptionTest describe "cache dump" do dumpCacheTest+ describe "liftEff" do+ liftEffTest+ concurrentLiftEffTest -- * Helpers @@ -368,6 +373,38 @@ r `shouldSatisfy` \case Left ThreadKilled -> True _ -> False++-- * liftEff++liftEffTest :: forall es. (Haxl () () :> es, Hspec :> es, Prim :> es) => Expectation es+liftEffTest = it "runs an Eff action and sees its effect" do+ ref <- newIORef (0 :: Int)+ r <- inject . haxl @() @() $ do+ liftEff $ modifyIORef' ref (+ 1)+ liftEff $ readIORef ref+ r `shouldBe` 1+ final <- readIORef ref+ final `shouldBe` 1++concurrentLiftEffTest+ :: forall es+ . (Haxl () () :> es, Hspec :> es, Prim :> es, IOE :> es)+ => Expectation es+concurrentLiftEffTest = it "runs liftEff correctly across many concurrent haxl calls" do+ ref <- newIORef (0 :: Int)+ let go :: GenHaxl () () es Int+ go = do+ n <- liftEff $ atomicModifyIORef' ref (\n -> (n + 1, n + 1))+ _ <- ExampleDataSource.countAardvarks "a" -- also exercise a real fetch alongside liftEff+ pure n+ results <- concurrently 64 . inject $ haxl @() @() go+ List.sort results `shouldBe` [1 .. 64]++concurrently :: (IOE :> es) => Int -> Eff es a -> Eff es [a]+concurrently n act = withEffToIO (ConcUnlift Persistent Unlimited) \unlift -> do+ mvs <- replicateM n newEmptyMVar+ mapM_ (\mv -> forkIO (unlift act >>= putMVar mv)) mvs+ mapM takeMVar mvs -- * Cache dump