stm-containers 0.1.4 → 1.2.2
raw patch · 28 files changed
Files
- Setup.hs +0/−2
- executables/APITests.hs +0/−9
- executables/ConcurrentInsertionBench.hs +0/−131
- executables/ConcurrentTransactionsBench.hs +0/−208
- executables/InsertionBench.hs +0/−52
- executables/WordArrayTests.hs +0/−40
- library/STMContainers/Bimap.hs +0/−143
- library/STMContainers/HAMT.hs +0/−34
- library/STMContainers/HAMT/Level.hs +0/−29
- library/STMContainers/HAMT/Nodes.hs +0/−153
- library/STMContainers/Map.hs +0/−98
- library/STMContainers/Multimap.hs +0/−141
- library/STMContainers/Prelude.hs +0/−34
- library/STMContainers/Set.hs +0/−98
- library/STMContainers/SizedArray.hs +0/−87
- library/STMContainers/WordArray.hs +0/−198
- library/STMContainers/WordArray/Indices.hs +0/−68
- library/StmContainers/Bimap.hs +167/−0
- library/StmContainers/Map.hs +122/−0
- library/StmContainers/Multimap.hs +181/−0
- library/StmContainers/Prelude.hs +82/−0
- library/StmContainers/Set.hs +123/−0
- stm-containers.cabal +130/−212
- test/Main.hs +14/−0
- test/Suites/Bimap.hs +66/−0
- test/Suites/Map.hs +155/−0
- test/Suites/Map/Update.hs +52/−0
- test/Suites/Set.hs +125/−0
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
− executables/APITests.hs
@@ -1,9 +0,0 @@-{-# OPTIONS_GHC -F -pgmF htfpp #-}--import Test.Framework-import BasePrelude--import {-@ HTF_TESTS @-} APITests.MapTests---main = htfMain $ htf_thisModulesTests : htf_importedTests
− executables/ConcurrentInsertionBench.hs
@@ -1,131 +0,0 @@--import STMContainers.Prelude-import Criterion.Main-import Control.Monad.Free-import Control.Monad.Free.TH-import qualified Data.HashMap.Strict as UC-import qualified STMContainers.Map as SC-import qualified Control.Concurrent.Async as Async-import qualified System.Random.MWC.Monad as MWC-import qualified Focus-import qualified Data.Char as Char-import qualified Data.Text as Text---type UCMap k v = TVar (UC.HashMap k (TVar v))----- * Transactions ----------------------------data TransactionF k v n where- Insert :: v -> k -> n -> TransactionF k v n- deriving (Functor)--type Transaction k v = Free (TransactionF k v)----- * Interpreters----------------------------type Interpreter m = - forall k v r. (Hashable k, Eq k) => m k v -> Transaction k v r -> STM r--ucInterpreter :: Interpreter UCMap-ucInterpreter m = - iterM $ \case- Insert v k n -> do- mv <- readTVar m- vt <- newTVar v- writeTVar m $! UC.insert k vt mv- n--specializedSCInterpreter :: Interpreter SC.Map-specializedSCInterpreter m =- iterM $ \case- Insert v k n -> SC.insert v k m >> n--focusSCInterpreter :: Interpreter SC.Map-focusSCInterpreter m =- iterM $ \case- Insert v k n -> SC.focus (Focus.insertM v) k m >> n----- * Session and runners------------------------------ | A list of transactions per thread.-type Session k v = [[Transaction k v ()]]--type SessionRunner = - forall k v. (Hashable k, Eq k) => Session k v -> IO ()--scSessionRunner :: Interpreter SC.Map -> SessionRunner-scSessionRunner interpreter threadTransactions = do- m <- atomically $ SC.new- void $ flip Async.mapConcurrently threadTransactions $ \actions -> do- forM_ actions $ atomically . interpreter m--ucSessionRunner :: SessionRunner-ucSessionRunner threadTransactions = do- m <- newTVarIO UC.empty- void $ flip Async.mapConcurrently threadTransactions $ \actions -> do- forM_ actions $ atomically . ucInterpreter m----- * Generators----------------------------type Generator a = MWC.Rand IO a--transactionGenerator :: Generator (Transaction Text.Text () ())-transactionGenerator = do- k <- keyGenerator- return $ Free $ Insert () k (Pure ())--keyGenerator :: Generator Text.Text-keyGenerator = do- l <- length- s <- replicateM l char- return $! Text.pack s- where- length = MWC.uniformR (7, 20)- char = Char.chr <$> MWC.uniformR (Char.ord 'a', Char.ord 'z')----- * Utils----------------------------slices :: Int -> [a] -> [[a]]-slices size l =- case splitAt size l of- ([], _) -> []- (a, b) -> a : slices size b----- * Main----------------------------main = do- allTransactions <- MWC.runWithCreate $ replicateM actionsNum transactionGenerator- defaultMain $! flip map threadsNums $! \threadsNum ->- let- sliceSize = actionsNum `div` threadsNum- threadTransactions = slices sliceSize allTransactions- in - bgroup- (shows threadsNum . showString "/" . shows sliceSize $ "")- [- bgroup "STM Containers"- [- bench "Focus-based" $ - scSessionRunner focusSCInterpreter threadTransactions,- bench "Specialized" $ - scSessionRunner specializedSCInterpreter threadTransactions- ],- bench "Unordered Containers" $- ucSessionRunner threadTransactions- ]- where- actionsNum = 100000- threadsNums = [1, 2, 4, 6, 8, 12, 16, 32, 40, 52, 64, 80, 128]
− executables/ConcurrentTransactionsBench.hs
@@ -1,208 +0,0 @@--import STMContainers.Prelude-import STMContainers.Transformers-import Criterion.Main-import Control.Monad.Free-import Control.Monad.Free.TH-import qualified Data.HashMap.Strict as UC-import qualified STMContainers.Map as SC-import qualified Control.Concurrent.Async as Async-import qualified System.Random.MWC.Monad as MWC-import qualified Focus-import qualified Data.Char as Char-import qualified Data.Text as Text-import qualified Data.Set as Set----- * Custom data structures----------------------------type UCMap k v = TVar (UC.HashMap k (TVar v))----- * Transactions ----------------------------data TransactionF k v n where- Insert :: v -> k -> n -> TransactionF k v n- Delete :: k -> n -> TransactionF k v n- Lookup :: k -> n -> TransactionF k v n- deriving (Functor, Show)--type Transaction k v = Free (TransactionF k v)---- * Interpreters----------------------------type Interpreter m = - forall k v r. (Hashable k, Eq k) => m k v -> Transaction k v r -> STM r--ucInterpreter :: Interpreter UCMap-ucInterpreter m = - iterM $ \case- Insert v k c -> - do- mv <- readTVar m- vt <- newTVar v- writeTVar m $! UC.insert k vt mv- c- Delete k c -> - readTVar m >>= writeTVar m . UC.delete k >> c- Lookup k c -> - readTVar m >>= mapM readTVar . UC.lookup k >> c--specializedSCInterpreter :: Interpreter SC.Map-specializedSCInterpreter m =- iterM $ \case- Insert v k c -> SC.insert v k m >> c- Delete k c -> SC.delete k m >> c- Lookup k c -> SC.lookup k m >> c--focusSCInterpreter :: Interpreter SC.Map-focusSCInterpreter m =- iterM $ \case- Insert v k c -> SC.focus (Focus.insertM v) k m >> c- Delete k c -> SC.focus Focus.deleteM k m >> c- Lookup k c -> SC.focus Focus.lookupM k m >> c----- * Session and runners------------------------------ | A list of transactions per thread.-type Session k v = [[Transaction k v ()]]--type SessionRunner = - forall k v. (Hashable k, Eq k) => Session k v -> IO ()--scSessionRunner :: Interpreter SC.Map -> SessionRunner-scSessionRunner interpreter threadTransactions = do- m <- atomically $ SC.new- void $ flip Async.mapConcurrently threadTransactions $ \actions -> do- forM_ actions $ atomically . interpreter m--ucSessionRunner :: SessionRunner-ucSessionRunner threadTransactions = do- m <- newTVarIO UC.empty- void $ flip Async.mapConcurrently threadTransactions $ \actions -> do- forM_ actions $ atomically . ucInterpreter m----- * Generators----------------------------type Generator a = MWC.Rand IO a---- |--- Generate a list of transactions with a context of shared keys.-transactionsGroupGenerator :: Int -> Generator [Transaction Text.Text () ()]-transactionsGroupGenerator n =- (flip evalStateT) Set.empty $ replicateM n transaction- where- transaction = do- s <- size- fmap sequence_ $ replicateM s action- where- size = - lift $ join $ weightedElementGenerator $- [- (1 , MWC.uniformR (8, 14)),- (5 , MWC.uniformR (2, 7)),- (10 , return 1)- ]- action =- join $ lift $ weightedElementGenerator $- [- (1, delete),- (5, insert),- (20, lookup)- ]- where- insert = do- k <- key 10 1- modify $ Set.insert k- return $ liftF (Insert () k ())- delete = do- k <- key 1 10- modify $ Set.delete k- return $ liftF (Delete k ())- lookup = do- k <- key 1 5- return $ liftF (Lookup k ())- key unknownWeight knownWeight =- join $ lift $ weightedElementGenerator $ - [- (unknownWeight, unknown), - (knownWeight, known)- ]- where- unknown = lift $ keyGenerator- known = do- allKeys <- get- maybe unknown return =<< lift (setElementGenerator allKeys)- -keyGenerator :: Generator Text.Text-keyGenerator = do- l <- length- s <- replicateM l char- return $! Text.pack s- where- length = MWC.uniformR (7, 20)- char = Char.chr <$> MWC.uniformR (Char.ord 'a', Char.ord 'z')--setElementGenerator :: Set.Set a -> Generator (Maybe a)-setElementGenerator set = do- case Set.size set of- 0 -> return Nothing- size -> Just . (flip Set.elemAt) set <$> MWC.uniformR (0, pred size)--elementGenerator :: [a] -> Generator a-elementGenerator = \case- [] -> $bug "Empty list"- l -> (!!) l <$> MWC.uniformR (0, (pred . length) l)--weightedElementGenerator :: [(Int, a)] -> Generator a-weightedElementGenerator = \case- [] -> - $bug "Empty list"- l -> - (flip pick) l <$> MWC.uniformR (1, total)- where- total = (sum . map fst) l- pick n = \case- (n', e) : t ->- if n' >= n- then e - else pick (n - n') t---- * Utils----------------------------slices :: Int -> [a] -> [[a]]-slices size l =- case splitAt size l of- ([], _) -> []- (a, b) -> a : slices size b----- * Main----------------------------main = do- -- Pregenerate the transactions:- transactionsGroups <- - MWC.runWithCreate $ replicateM (maximum threadsNums) $- transactionsGroupGenerator (transactionsNum `div` (maximum threadsNums))-- -- Run the benchmark:- defaultMain $! flip map threadsNums $ \threadsNum -> - let- session = - map concat $! - slices (length transactionsGroups `div` threadsNum) transactionsGroups- in- bench (shows threadsNum . showString "/" . shows (transactionsNum `div` threadsNum) $ "") $- scSessionRunner specializedSCInterpreter session- where- threadsNums = [1, 2, 4, 6, 8, 12, 16, 32, 40, 52, 64, 80, 128]- transactionsNum = 400000
− executables/InsertionBench.hs
@@ -1,52 +0,0 @@--import STMContainers.Prelude-import Criterion.Main-import qualified Data.HashTable.IO as Hashtables-import qualified Data.HashMap.Strict as UnorderedContainers-import qualified Data.Map as Containers-import qualified STMContainers.Map as STMContainers-import qualified Focus-import qualified System.Random.MWC.Monad as MWC-import qualified Data.Char as Char-import qualified Data.Text as Text--main = do- keys <- MWC.runWithCreate $ replicateM rows keyGenerator- defaultMain- [- bgroup "STM Containers"- [- bench "focus-based" $ - do- t <- atomically $ STMContainers.new :: IO (STMContainers.Map Text.Text ())- forM_ keys $ \k -> atomically $ STMContainers.focus (Focus.insertM ()) k t- ,- bench "specialized" $- do- t <- atomically $ STMContainers.new :: IO (STMContainers.Map Text.Text ())- forM_ keys $ \k -> atomically $ STMContainers.insert () k t- ]- ,- bench "Unordered Containers" $- nf (foldr (\k -> UnorderedContainers.insert k ()) UnorderedContainers.empty) keys- ,- bench "Containers" $- nf (foldr (\k -> Containers.insert k ()) Containers.empty) keys- ,- bench "Hashtables" $ - do- t <- Hashtables.new :: IO (Hashtables.BasicHashTable Text.Text ())- forM_ keys $ \k -> Hashtables.insert t k ()- ]--rows :: Int = 100000--keyGenerator :: MWC.Rand IO Text.Text-keyGenerator = do- l <- length- s <- replicateM l char- return $! Text.pack s- where- length = MWC.uniformR (7, 20)- char = Char.chr <$> MWC.uniformR (Char.ord 'a', Char.ord 'z')-
− executables/WordArrayTests.hs
@@ -1,40 +0,0 @@-{-# OPTIONS_GHC -F -pgmF htfpp #-}--import Test.Framework-import STMContainers.Prelude-import STMContainers.Transformers-import qualified STMContainers.WordArray as WordArray-import qualified Focus-import qualified WordArrayTests.Update as Update---main = htfMain $ htf_thisModulesTests--prop_differentInterpretersProduceSameResults (update :: Update.Update Char ()) =- Update.interpretMaybeList update ==- fmap WordArray.toMaybeList (Update.interpretWordArray update)--prop_fromListIsIsomorphicToToList =- forAll gen prop- where- gen = do- indices <- (nub . sort) <$> listOf index- mapM (liftA2 (flip (,)) char . pure) indices- where- index = choose (0, pred (WordArray.maxSize)) :: Gen Int- char = arbitrary :: Gen Char- prop list = - list === (WordArray.toList . WordArray.fromList) list--test_focusMLookup = do- assertEqual (Just 'a') . fst =<< WordArray.focusM Focus.lookupM 3 w- assertEqual (Just 'b') . fst =<< WordArray.focusM Focus.lookupM 7 w- assertEqual Nothing . fst =<< WordArray.focusM Focus.lookupM 1 w- assertEqual Nothing . fst =<< WordArray.focusM Focus.lookupM 11 w- where- w = WordArray.fromList [(3, 'a'), (7, 'b'), (14, 'c')]--test_foldable = do- assertEqual "abdc" $ toList w- where- w = WordArray.fromList [(3, 'a'), (7, 'b'), (8, 'd'), (14, 'c')]
− library/STMContainers/Bimap.hs
@@ -1,143 +0,0 @@-module STMContainers.Bimap-(- Bimap,- Association,- new,- newIO,- insert1,- insert2,- delete1,- delete2,- lookup1,- lookup2,- focus1,- focus2,- foldM,- null,-)-where--import STMContainers.Prelude hiding (insert, delete, lookup, alter, foldM, toList, empty, null)-import qualified Focus-import qualified STMContainers.Map as Map----- |--- A bidirectional map.--- Essentially a bijection between subsets of its two argument types.--- --- For one value of a left-hand type this map contains one value --- of the right-hand type and vice versa.-data Bimap a b = - Bimap {m1 :: !(Map.Map a b), m2 :: !(Map.Map b a)}---- |--- A standard constraint for associations.-type Association a b = (Map.Key a, Map.Key b)---- |--- Construct a new bimap.-{-# INLINABLE new #-}-new :: STM (Bimap a b)-new = Bimap <$> Map.new <*> Map.new---- |--- Construct a new bimap in IO.--- --- This is useful for creating it on a top-level using 'unsafePerformIO', --- because using 'atomically' inside 'unsafePerformIO' isn't possible.-{-# INLINABLE newIO #-}-newIO :: IO (Bimap a b)-newIO = Bimap <$> Map.newIO <*> Map.newIO---- |--- Check on being empty.-{-# INLINABLE null #-}-null :: Bimap a b -> STM Bool-null = Map.null . m1---- |--- Look up a right value by a left value.-{-# INLINABLE lookup1 #-}-lookup1 :: (Association a b) => a -> Bimap a b -> STM (Maybe b)-lookup1 k = Map.lookup k . m1---- |--- Look up a left value by a right value.-{-# INLINABLE lookup2 #-}-lookup2 :: (Association a b) => b -> Bimap a b -> STM (Maybe a)-lookup2 k = Map.lookup k . m2---- |--- Insert an association by a left value.-{-# INLINABLE insert1 #-}-insert1 :: (Association a b) => b -> a -> Bimap a b -> STM ()-insert1 b a (Bimap m1 m2) = - do- Map.insert b a m1- Map.insert a b m2---- |--- Insert an association by a right value.-{-# INLINABLE insert2 #-}-insert2 :: (Association a b) => a -> b -> Bimap a b -> STM ()-insert2 b a (Bimap m1 m2) = (inline insert1) b a (Bimap m2 m1)---- |--- Delete an association by a left value.-{-# INLINABLE delete1 #-}-delete1 :: (Association a b) => a -> Bimap a b -> STM ()-delete1 k (Bimap m1 m2) =- Map.focus lookupAndDeleteStrategy k m1 >>= - mapM_ (\k' -> Map.delete k' m2)- where- lookupAndDeleteStrategy r =- return (r, Focus.Remove)---- |--- Delete an association by a right value.-{-# INLINABLE delete2 #-}-delete2 :: (Association a b) => b -> Bimap a b -> STM ()-delete2 k (Bimap m1 m2) = (inline delete1) k (Bimap m2 m1)---- |--- Focus on a right value by a left value with a strategy.--- --- This function allows to perform composite operations in a single access--- to a map item.--- E.g., you can look up an item and delete it at the same time,--- or update it and return the new value.-{-# INLINABLE focus1 #-}-focus1 :: (Association a b) => Focus.StrategyM STM b r -> a -> Bimap a b -> STM r-focus1 s a (Bimap m1 m2) =- do - (r, d, mb) <- Map.focus s' a m1- case d of- Focus.Keep -> - return ()- Focus.Remove -> - forM_ mb $ \b -> Map.delete b m2- Focus.Replace b' ->- do- forM_ mb $ \b -> Map.delete b m2- Map.insert a b' m2- return r- where- s' = \k -> s k >>= \(r, d) -> return ((r, d, k), d)---- |--- Focus on a left value by a right value with a strategy.--- --- This function allows to perform composite operations in a single access--- to a map item.--- E.g., you can look up an item and delete it at the same time,--- or update it and return the new value.-{-# INLINABLE focus2 #-}-focus2 :: (Association a b) => Focus.StrategyM STM a r -> b -> Bimap a b -> STM r-focus2 s b (Bimap m1 m2) = (inline focus1) s b (Bimap m2 m1)---- |--- Fold all the associations.-{-# INLINABLE foldM #-}-foldM :: (r -> (a, b) -> STM r) -> r -> Bimap a b -> STM r-foldM s r = Map.foldM s r . m1
− library/STMContainers/HAMT.hs
@@ -1,34 +0,0 @@-module STMContainers.HAMT where--import STMContainers.Prelude hiding (insert, lookup, delete, foldM)-import qualified STMContainers.HAMT.Nodes as Nodes-import qualified Focus---type HAMT e = Nodes.Nodes e--type Element e = (Nodes.Element e, Hashable (Nodes.ElementKey e))--{-# INLINE insert #-}-insert :: (Element e) => e -> HAMT e -> STM ()-insert e = Nodes.insert e (hash (Nodes.elementKey e)) (Nodes.elementKey e) 0--{-# INLINE focus #-}-focus :: (Element e) => Focus.StrategyM STM e r -> Nodes.ElementKey e -> HAMT e -> STM r-focus s k = Nodes.focus s (hash k) k 0--{-# INLINE foldM #-}-foldM :: (a -> e -> STM a) -> a -> HAMT e -> STM a-foldM step acc = Nodes.foldM step acc 0--{-# INLINE new #-}-new :: STM (HAMT e)-new = Nodes.new--{-# INLINE newIO #-}-newIO :: IO (HAMT e)-newIO = Nodes.newIO--{-# INLINE null #-}-null :: HAMT e -> STM Bool-null = Nodes.null
− library/STMContainers/HAMT/Level.hs
@@ -1,29 +0,0 @@-module STMContainers.HAMT.Level where--import STMContainers.Prelude hiding (mask)----- |--- A depth level of a node.--- Must be a multiple of the 'step' value.-type Level = Int--{-# INLINE hashIndex #-}-hashIndex :: Level -> (Int -> Int)-hashIndex l i = mask .&. unsafeShiftR i l--{-# INLINE mask #-}-mask :: Int-mask = bit step - 1--{-# INLINE step #-}-step :: Int-step = 5--{-# INLINE limit #-}-limit :: Int-limit = bitSize (undefined :: Int)--{-# INLINE succ #-}-succ :: Level -> Level-succ = (+ step)
− library/STMContainers/HAMT/Nodes.hs
@@ -1,153 +0,0 @@-module STMContainers.HAMT.Nodes where--import STMContainers.Prelude hiding (insert, lookup, delete, foldM, null)-import qualified STMContainers.Prelude as Prelude-import qualified STMContainers.WordArray as WordArray-import qualified STMContainers.SizedArray as SizedArray-import qualified STMContainers.HAMT.Level as Level-import qualified Focus---type Nodes e = TVar (WordArray.WordArray (Node e))--data Node e = - Nodes {-# UNPACK #-} !(Nodes e) |- Leaf {-# UNPACK #-} !Hash !e |- Leaves {-# UNPACK #-} !Hash {-# UNPACK #-} !(SizedArray.SizedArray e)--type Hash = Int--class (Eq (ElementKey e)) => Element e where- type ElementKey e- elementKey :: e -> ElementKey e--{-# INLINE new #-}-new :: STM (Nodes e)-new = newTVar WordArray.empty--{-# INLINE newIO #-}-newIO :: IO (Nodes e)-newIO = newTVarIO WordArray.empty--insert :: (Element e) => e -> Hash -> ElementKey e -> Level.Level -> Nodes e -> STM ()-insert e h k l ns = do- a <- readTVar ns- let write n = writeTVar ns $ WordArray.set i n a- case WordArray.lookup i a of- Nothing -> write (Leaf h e)- Just n -> case n of- Nodes ns' -> insert e h k (Level.succ l) ns'- Leaf h' e' ->- if h' == h- then if elementKey e' == k- then write (Leaf h e)- else write (Leaves h (SizedArray.pair e e'))- else do- nodes <- pair h (Leaf h e) h' (Leaf h' e') (Level.succ l)- write (Nodes nodes)- Leaves h' la ->- if h' == h- then case SizedArray.find ((== k) . elementKey) la of- Just (lai, _) ->- write (Leaves h' (SizedArray.insert lai e la))- Nothing ->- write (Leaves h' (SizedArray.append e la))- else- write . Nodes =<< pair h (Leaf h e) h' (Leaves h' la) (Level.succ l)- where- i = Level.hashIndex l h--pair :: Hash -> Node e -> Hash -> Node e -> Level.Level -> STM (Nodes e)-pair h1 n1 h2 n2 l =- if i1 == i2- then newTVar . WordArray.singleton i1 . Nodes =<< pair h1 n1 h2 n2 (Level.succ l)- else newTVar $ WordArray.pair i1 n1 i2 n2- where- hashIndex = Level.hashIndex l- i1 = hashIndex h1- i2 = hashIndex h2--focus :: (Element e) => Focus.StrategyM STM e r -> Hash -> ElementKey e -> Level.Level -> Nodes e -> STM r-focus s h k l ns = do- a <- readTVar ns- (r, a'm) <- WordArray.focusM s' ai a- maybe (return ()) (writeTVar ns) a'm- return r- where- ai = Level.hashIndex l h- s' = \case- Nothing -> traversePair (return . fmap (Leaf h)) =<< s Nothing- Just n -> case n of- Nodes ns' -> do- r <- focus s h k (Level.succ l) ns'- null ns' >>= \case- True -> return (r, Focus.Remove)- False -> return (r, Focus.Keep)- Leaf h' e' ->- case h' == h of- True -> - case elementKey e' == k of- True -> - traversePair (return . fmap (Leaf h)) =<< s (Just e')- False -> - traversePair processDecision =<< s Nothing- where- processDecision = \case- Focus.Replace e -> - return (Focus.Replace (Leaves h (SizedArray.pair e e')))- _ -> - return Focus.Keep- False -> - traversePair processDecision =<< s Nothing- where- processDecision = \case- Focus.Replace e -> do- ns' <- pair h (Leaf h e) h' (Leaf h' e') (Level.succ l)- return (Focus.Replace (Nodes ns'))- _ -> return Focus.Keep- Leaves h' a' ->- case h' == h of- True ->- case SizedArray.find ((== k) . elementKey) a' of- Just (i', e') -> - s (Just e') >>= traversePair processDecision- where- processDecision = \case- Focus.Keep -> - return Focus.Keep- Focus.Remove -> - case SizedArray.delete i' a' of- a'' -> case SizedArray.null a'' of- False -> return (Focus.Replace (Leaves h' a''))- True -> return Focus.Remove- Focus.Replace e ->- return (Focus.Replace (Leaves h' (SizedArray.insert i' e a')))- Nothing -> - s Nothing >>= traversePair processDecision- where- processDecision = \case- Focus.Replace e ->- return (Focus.Replace (Leaves h' (SizedArray.append e a')))- _ ->- return Focus.Keep- False ->- s Nothing >>= traversePair processDecision- where- processDecision = \case- Focus.Replace e -> do- ns' <- pair h (Leaf h e) h' (Leaves h' a') (Level.succ l)- return (Focus.Replace (Nodes ns'))- _ ->- return Focus.Keep--null :: Nodes e -> STM Bool-null = fmap WordArray.null . readTVar--foldM :: (a -> e -> STM a) -> a -> Level.Level -> Nodes e -> STM a-foldM step acc level = - readTVar >=> foldlM step' acc- where- step' acc' = \case- Nodes ns -> foldM step acc' (Level.succ level) ns- Leaf _ e -> step acc' e- Leaves _ a -> SizedArray.foldM step acc' a
− library/STMContainers/Map.hs
@@ -1,98 +0,0 @@-module STMContainers.Map-(- Map,- Key,- new,- newIO,- insert,- delete,- lookup,- focus,- foldM,- null,-)-where--import STMContainers.Prelude hiding (insert, delete, lookup, alter, foldM, toList, empty, null)-import qualified STMContainers.HAMT as HAMT-import qualified STMContainers.HAMT.Nodes as HAMTNodes-import qualified Focus----- |--- A hash table, based on an STM-specialized hash array mapped trie.-newtype Map k v = Map (HAMT.HAMT (Association k v))---- |--- A standard constraint for keys.-type Key a = (Eq a, Hashable a)---- |--- A key-value association.-type Association k v = (k, v)--instance (Eq k) => HAMTNodes.Element (Association k v) where- type ElementKey (Association k v) = k- elementKey (k, v) = k--{-# INLINE associationValue #-}-associationValue :: Association k v -> v-associationValue (_, v) = v---- |--- Look up an item.-{-# INLINE lookup #-}-lookup :: (Key k) => k -> Map k v -> STM (Maybe v)-lookup k = focus Focus.lookupM k---- |--- Insert a value at a key.-{-# INLINE insert #-}-insert :: (Key k) => v -> k -> Map k v -> STM ()-insert !v !k (Map h) = HAMT.insert (k, v) h---- |--- Delete an item by a key.-{-# INLINE delete #-}-delete :: (Key k) => k -> Map k v -> STM ()-delete k (Map h) = HAMT.focus Focus.deleteM k h---- |--- Focus on an item by a key with a strategy.--- --- This function allows to perform composite operations in a single access--- to a map item.--- E.g., you can look up an item and delete it at the same time,--- or update it and return the new value.-{-# INLINE focus #-}-focus :: (Key k) => Focus.StrategyM STM v r -> k -> Map k v -> STM r-focus f k (Map h) = HAMT.focus f' k h- where- f' = (fmap . fmap . fmap) (\v -> k `seq` v `seq` (k, v)) . f . fmap associationValue---- |--- Fold all the items of a map.-{-# INLINE foldM #-}-foldM :: (a -> (k, v) -> STM a) -> a -> Map k v -> STM a-foldM s a (Map h) = HAMT.foldM s a h---- |--- Construct a new map.-{-# INLINE new #-}-new :: STM (Map k v)-new = Map <$> HAMT.new---- |--- Construct a new map in IO.--- --- This is useful for creating it on a top-level using 'unsafePerformIO', --- because using 'atomically' inside 'unsafePerformIO' isn't possible.-{-# INLINE newIO #-}-newIO :: IO (Map k v)-newIO = Map <$> HAMT.newIO---- |--- Check, whether the map is empty.-{-# INLINE null #-}-null :: Map k v -> STM Bool-null (Map h) = HAMT.null h
− library/STMContainers/Multimap.hs
@@ -1,141 +0,0 @@-module STMContainers.Multimap-(- Multimap,- Association,- new,- newIO,- insert,- delete,- lookup,- focus,- foldM,- null,-)-where--import STMContainers.Prelude hiding (insert, delete, lookup, alter, foldM, toList, empty, null)-import qualified Focus-import qualified STMContainers.Map as Map-import qualified STMContainers.Set as Set----- |--- A multimap, based on an STM-specialized hash array mapped trie.--- --- Basically it's just a wrapper API around @'Map.Map' k ('Set.Set' v)@.-newtype Multimap k v = Multimap (Map.Map k (Set.Set v))---- |--- A standard constraint for items.-type Association k v = (Eq k, Hashable k, Eq v, Hashable v)---- |--- Look up an item by a value and a key.-{-# INLINE lookup #-}-lookup :: (Association k v) => v -> k -> Multimap k v -> STM Bool-lookup v k (Multimap m) = - maybe (return False) (Set.lookup v) =<< Map.lookup k m---- |--- Insert an item.-{-# INLINABLE insert #-}-insert :: (Association k v) => v -> k -> Multimap k v -> STM ()-insert v k (Multimap m) =- Map.focus ms k m- where- ms = - \case - Just s -> - do- Set.insert v s- return ((), Focus.Keep)- Nothing ->- do- s <- Set.new- Set.insert v s- return ((), Focus.Replace s)---- |--- Delete an item by a value and a key.-{-# INLINABLE delete #-}-delete :: (Association k v) => v -> k -> Multimap k v -> STM ()-delete v k (Multimap m) =- Map.focus ms k m- where- ms = - \case - Just s -> - do- Set.delete v s- Set.null s >>= returnDecision . bool Focus.Keep Focus.Remove- Nothing ->- returnDecision Focus.Keep- where- returnDecision c = return ((), c)---- |--- Focus on an item with a strategy by a value and a key.--- --- This function allows to perform simultaneous lookup and modification.--- --- The strategy is over a unit since we already know,--- which value we're focusing on and it doesn't make sense to replace it,--- however we still can decide wether to keep or remove it.-{-# INLINE focus #-}-focus :: (Association k v) => Focus.StrategyM STM () r -> v -> k -> Multimap k v -> STM r-focus = - \s v k (Multimap m) -> Map.focus (liftSetItemStrategy v s) k m- where- liftSetItemStrategy :: - (Set.Element e) => e -> Focus.StrategyM STM () r -> Focus.StrategyM STM (Set.Set e) r- liftSetItemStrategy e s =- \case- Nothing ->- traversePair liftDecision =<< s Nothing- where- liftDecision =- \case- Focus.Replace b ->- do- s <- Set.new- Set.insert e s- return (Focus.Replace s)- _ ->- return Focus.Keep- Just set -> - do- r <- Set.focus s e set- (r,) . bool Focus.Keep Focus.Remove <$> Set.null set---- |--- Fold all the items.-{-# INLINE foldM #-}-foldM :: (a -> (k, v) -> STM a) -> a -> Multimap k v -> STM a-foldM f a (Multimap m) = - Map.foldM f' a m- where- f' a' (k, set) = - Set.foldM f'' a' set- where- f'' a'' v = f a'' (k, v)---- |--- Construct a new multimap.-{-# INLINE new #-}-new :: STM (Multimap k v)-new = Multimap <$> Map.new---- |--- Construct a new multimap in IO.--- --- This is useful for creating it on a top-level using 'unsafePerformIO', --- because using 'atomically' inside 'unsafePerformIO' isn't possible.-{-# INLINE newIO #-}-newIO :: IO (Multimap k v)-newIO = Multimap <$> Map.newIO---- |--- Check on being empty.-{-# INLINE null #-}-null :: Multimap k v -> STM Bool-null (Multimap m) = Map.null m
− library/STMContainers/Prelude.hs
@@ -1,34 +0,0 @@-module STMContainers.Prelude-( - module Exports,- bug,- bottom,- traversePair,-)-where---- base---------------------------import BasePrelude as Exports---- placeholders---------------------------import Development.Placeholders as Exports---- hashable---------------------------import Data.Hashable as Exports (Hashable(..))---- custom---------------------------import qualified Debug.Trace.LocationTH--bug = [e| $(Debug.Trace.LocationTH.failure) . (msg <>) |]- where- msg = "A \"stm-containers\" package bug: " :: String--bottom = [e| $bug "Bottom evaluated" |]---- | A replacement for the missing 'Traverse' instance of pair in base < 4.7.-traversePair :: Functor f => (a -> f b) -> (c, a) -> f (c, b)-traversePair f (x, y) = (,) x <$> f y
− library/STMContainers/Set.hs
@@ -1,98 +0,0 @@-module STMContainers.Set-(- Set,- Element,- new,- newIO,- insert,- delete,- lookup,- focus,- foldM,- null,-)-where--import STMContainers.Prelude hiding (insert, delete, lookup, alter, foldM, toList, empty, null)-import qualified STMContainers.HAMT as HAMT-import qualified STMContainers.HAMT.Nodes as HAMTNodes-import qualified Focus----- |--- A hash set, based on an STM-specialized hash array mapped trie.-newtype Set e = Set {hamt :: HAMT.HAMT (HAMTElement e)}---- |--- A standard constraint for elements.-type Element a = (Eq a, Hashable a)--newtype HAMTElement e = HAMTElement e--instance (Eq e) => HAMTNodes.Element (HAMTElement e) where- type ElementKey (HAMTElement e) = e- elementKey (HAMTElement e) = e--{-# INLINE elementValue #-}-elementValue :: HAMTElement e -> e-elementValue (HAMTElement e) = e---- |--- Insert a new element.-{-# INLINE insert #-}-insert :: (Element e) => e -> Set e -> STM ()-insert e = HAMT.insert (HAMTElement e) . hamt---- |--- Delete an element.-{-# INLINE delete #-}-delete :: (Element e) => e -> Set e -> STM ()-delete e = HAMT.focus Focus.deleteM e . hamt---- |--- Lookup an element.-{-# INLINE lookup #-}-lookup :: (Element e) => e -> Set e -> STM Bool-lookup e = fmap (maybe False (const True)) . HAMT.focus Focus.lookupM e . hamt---- |--- Focus on an element with a strategy.--- --- This function allows to perform simultaneous lookup and modification.--- --- The strategy is over a unit since we already know, --- which element we're focusing on and it doesn't make sense to replace it,--- however we still can decide wether to keep or remove it.-{-# INLINE focus #-}-focus :: (Element e) => Focus.StrategyM STM () r -> e -> Set e -> STM r-focus s e = HAMT.focus elementStrategy e . hamt- where- elementStrategy = - (fmap . fmap . fmap) (const (HAMTElement e)) . s . fmap (const ())---- |--- Fold all the elements.-{-# INLINE foldM #-}-foldM :: (a -> e -> STM a) -> a -> Set e -> STM a-foldM f a = HAMT.foldM (\a -> f a . elementValue) a . hamt---- |--- Construct a new set.-{-# INLINE new #-}-new :: STM (Set e)-new = Set <$> HAMT.new---- |--- Construct a new set in IO.--- --- This is useful for creating it on a top-level using 'unsafePerformIO', --- because using 'atomically' inside 'unsafePerformIO' isn't possible.-{-# INLINE newIO #-}-newIO :: IO (Set e)-newIO = Set <$> HAMT.newIO---- |--- Check, whether the set is empty.-{-# INLINE null #-}-null :: Set e -> STM Bool-null = HAMT.null . hamt
− library/STMContainers/SizedArray.hs
@@ -1,87 +0,0 @@-module STMContainers.SizedArray where--import STMContainers.Prelude hiding (lookup, toList, foldM)-import Data.Primitive.Array-import qualified STMContainers.Prelude as Prelude-import qualified Focus---- |--- An array, --- which sacrifices the performance for space-efficiency and thread-safety.-data SizedArray a =- SizedArray {-# UNPACK #-} !Int {-# UNPACK #-} !(Array a)--instance Foldable SizedArray where- {-# INLINE foldr #-}- foldr step r (SizedArray size array) =- foldr step r $ map (indexArray array) [0 .. pred size]---- |--- An index of an element.-type Index = Int--{-# INLINE pair #-}-pair :: a -> a -> SizedArray a-pair e e' =- runST $ do- a <- newArray 2 e- writeArray a 1 e'- SizedArray 2 <$> unsafeFreezeArray a---- |--- Get the amount of elements.-{-# INLINE size #-}-size :: SizedArray a -> Int-size (SizedArray b _) = b---- |--- Get the amount of elements.-{-# INLINE null #-}-null :: SizedArray a -> Bool-null = (== 0) . size--{-# INLINE find #-}-find :: (a -> Bool) -> SizedArray a -> Maybe (Index, a)-find p (SizedArray s a) = loop 0- where- loop i = if i < s- then let e = indexArray a i in if p e- then Just (i, e)- else loop (succ i)- else Nothing---- |--- Unsafe. Doesn't check the index overflow.-{-# INLINE insert #-}-insert :: Index -> a -> SizedArray a -> SizedArray a-insert i e (SizedArray s a) = - runST $ do- m' <- newArray s undefined- forM_ [0 .. pred s] $ \i' -> indexArrayM a i' >>= writeArray m' i'- writeArray m' i e- SizedArray s <$> unsafeFreezeArray m'--{-# INLINE delete #-}-delete :: Index -> SizedArray a -> SizedArray a-delete i (SizedArray s a) = - runST $ do- m' <- newArray (pred s) undefined- forM_ [0 .. pred i] $ \i' -> indexArrayM a i' >>= writeArray m' i'- forM_ [succ i .. pred s] $ \i' -> indexArrayM a i' >>= writeArray m' (pred i')- SizedArray (pred s) <$> unsafeFreezeArray m'--{-# INLINE append #-}-append :: a -> SizedArray a -> SizedArray a-append e (SizedArray s a) =- runST $ do- m' <- newArray (succ s) undefined- forM_ [0 .. pred s] $ \i -> indexArrayM a i >>= writeArray m' i- writeArray m' s e- SizedArray (succ s) <$> unsafeFreezeArray m'--{-# INLINE foldM #-}-foldM :: (Monad m) => (a -> b -> m a) -> a -> SizedArray b -> m a-foldM step acc (SizedArray size array) =- Prelude.foldM step' acc [0 .. pred size]- where- step' acc' i = indexArrayM array i >>= step acc'
− library/STMContainers/WordArray.hs
@@ -1,198 +0,0 @@-module STMContainers.WordArray where--import STMContainers.Prelude hiding (lookup, toList, traverse_)-import Data.Primitive.Array-import qualified STMContainers.Prelude as Prelude-import qualified STMContainers.WordArray.Indices as Indices-import qualified Focus----- |--- An immutable space-efficient sparse array, --- which can store only as many elements as there are bits in the machine word.-data WordArray e =- WordArray {-# UNPACK #-} !Indices {-# UNPACK #-} !(Array e)--instance Foldable WordArray where- {-# INLINE foldr #-}- foldr step r (WordArray indices array) =- foldr (step . indexArray array) r $ Indices.positions indices---- | --- A bitmap of set elements.-type Indices = Indices.Indices---- |--- An index of an element.-type Index = Int--{-# INLINE indices #-}-indices :: WordArray e -> Indices-indices (WordArray b _) = b--{-# INLINE maxSize #-}-maxSize :: Int-maxSize = Indices.maxSize--{-# INLINE empty #-}-empty :: WordArray e-empty = WordArray 0 a- where- a = runST $ newArray 0 undefined >>= unsafeFreezeArray---- |--- An array with a single element at the specified index.-{-# INLINE singleton #-}-singleton :: Index -> e -> WordArray e-singleton i e = - let b = Indices.insert i 0- a = runST $ newArray 1 e >>= unsafeFreezeArray- in WordArray b a--{-# INLINE pair #-}-pair :: Index -> e -> Index -> e -> WordArray e-pair i e i' e' =- WordArray is a- where - is = Indices.fromList [i, i']- a = - runST $ if - | i < i' -> do- a <- newArray 2 e- writeArray a 1 e'- unsafeFreezeArray a- | i > i' -> do- a <- newArray 2 e- writeArray a 0 e'- unsafeFreezeArray a- | i == i' -> do- a <- newArray 1 e'- unsafeFreezeArray a---- |--- Unsafe.--- Assumes that the list is sorted and contains no duplicate indexes.-{-# INLINE fromList #-}-fromList :: [(Index, e)] -> WordArray e-fromList l = - runST $ do- indices <- newSTRef 0- array <- newArray (length l) undefined- forM_ (zip l [0..]) $ \((i, e), ai) -> do- modifySTRef indices $ Indices.insert i- writeArray array ai e- WordArray <$> readSTRef indices <*> unsafeFreezeArray array- -{-# INLINE toList #-}-toList :: WordArray e -> [(Index, e)]-toList (WordArray is a) = do- i <- Indices.toList is- e <- indexArrayM a (Indices.position i is)- return (i, e)---- |--- Convert into a list representation.-{-# INLINE toMaybeList #-}-toMaybeList :: WordArray e -> [Maybe e]-toMaybeList w = do- i <- [0 .. pred Indices.maxSize] - return $ lookup i w--{-# INLINE elements #-}-elements :: WordArray e -> [e]-elements (WordArray indices array) =- map (\i -> indexArray array (Indices.position i indices)) .- Indices.toList $- indices---- |--- Set an element value at the index.-{-# INLINE set #-}-set :: Index -> e -> WordArray e -> WordArray e-set i e (WordArray b a) = - let - sparseIndex = Indices.position i b- size = Indices.size b- in if Indices.elem i b- then - let a' = runST $ do- ma' <- newArray size undefined- forM_ [0 .. (size - 1)] $ \i -> indexArrayM a i >>= writeArray ma' i- writeArray ma' sparseIndex e- unsafeFreezeArray ma'- in WordArray b a'- else- let a' = runST $ do- ma' <- newArray (size + 1) undefined- forM_ [0 .. (sparseIndex - 1)] $ \i -> indexArrayM a i >>= writeArray ma' i- writeArray ma' sparseIndex e- forM_ [sparseIndex .. (size - 1)] $ \i -> indexArrayM a i >>= writeArray ma' (i + 1)- unsafeFreezeArray ma'- b' = Indices.insert i b- in WordArray b' a'---- |--- Remove an element.-{-# INLINE unset #-}-unset :: Index -> WordArray e -> WordArray e-unset i (WordArray b a) =- if Indices.elem i b- then- let - b' = Indices.invert i b- a' = runST $ do- ma' <- newArray (pred size) undefined- forM_ [0 .. pred sparseIndex] $ \i -> indexArrayM a i >>= writeArray ma' i- forM_ [succ sparseIndex .. pred size] $ \i -> indexArrayM a i >>= writeArray ma' (pred i)- unsafeFreezeArray ma'- sparseIndex = Indices.position i b- size = Indices.size b- in WordArray b' a'- else WordArray b a---- |--- Lookup an item at the index.-{-# INLINE lookup #-}-lookup :: Index -> WordArray e -> Maybe e-lookup i (WordArray b a) =- if Indices.elem i b- then Just (indexArray a (Indices.position i b))- else Nothing---- |--- Lookup strictly, using 'indexArrayM'.-{-# INLINE lookupM #-}-lookupM :: Monad m => Index -> WordArray e -> m (Maybe e)-lookupM i (WordArray b a) =- if Indices.elem i b- then liftM Just (indexArrayM a (Indices.position i b))- else return Nothing---- |--- Check, whether there is an element at the index.-{-# INLINE isSet #-}-isSet :: Index -> WordArray e -> Bool-isSet i = Indices.elem i . indices---- |--- Get the amount of elements.-{-# INLINE size #-}-size :: WordArray e -> Int-size = Indices.size . indices--{-# INLINE null #-}-null :: WordArray e -> Bool-null = Indices.null . indices--{-# INLINE focusM #-}-focusM :: Monad m => Focus.StrategyM m a r -> Index -> WordArray a -> m (r, Maybe (WordArray a))-focusM f i w = do- let em = lookup i w- (r, c) <- f em- let w' = case c of- Focus.Keep -> Nothing- Focus.Remove -> case em of- Nothing -> Nothing- Just _ -> Just $ unset i w- Focus.Replace e' -> Just $ set i e' w- return (r, w')
− library/STMContainers/WordArray/Indices.hs
@@ -1,68 +0,0 @@-module STMContainers.WordArray.Indices where--import STMContainers.Prelude hiding (toList, traverse_)-import qualified STMContainers.Prelude as Prelude----- |--- A compact set of indices.-type Indices = Int--type Index = Int--type Position = Int---- |--- A number of indexes, preceding this one.-{-# INLINE position #-}-position :: Index -> Indices -> Position-position i b = popCount (b .&. (bit i - 1))--{-# INLINE singleton #-}-singleton :: Index -> Indices-singleton = bit--{-# INLINE insert #-}-insert :: Index -> Indices -> Indices-insert i = (bit i .|.)--{-# INLINE invert #-}-invert :: Index -> Indices -> Indices-invert i = (bit i `xor`)--{-# INLINE elem #-}-elem :: Index -> Indices -> Bool-elem = flip testBit--{-# INLINE size #-}-size :: Indices -> Int-size = popCount--{-# INLINE null #-}-null :: Indices -> Bool-null = (== 0)--{-# INLINE maxSize #-}-maxSize :: Int-maxSize = bitSize (undefined :: Indices)--{-# INLINE fromList #-}-fromList :: [Index] -> Indices-fromList = Prelude.foldr (.|.) 0 . map bit--{-# INLINE toList #-}-toList :: Indices -> [Index]-toList w = filter (testBit w) allIndices--{-# INLINE positions #-}-positions :: Indices -> [Position]-positions = enumFromTo 0 . pred . size--{-# NOINLINE allIndices #-}-allIndices :: [Index]-allIndices = [0 .. pred maxSize]--{-# INLINE foldr #-}-foldr :: (Index -> r -> r) -> r -> Indices -> r-foldr s r ix = - Prelude.foldr (\i r' -> if testBit ix i then s i r' else r') r allIndices
+ library/StmContainers/Bimap.hs view
@@ -0,0 +1,167 @@+module StmContainers.Bimap+ ( Bimap,+ new,+ newIO,+ null,+ size,+ focusLeft,+ focusRight,+ lookupLeft,+ lookupRight,+ insertLeft,+ insertRight,+ deleteLeft,+ deleteRight,+ reset,+ unfoldlM,+ listT,+ )+where++import qualified Focus as B+import qualified StmContainers.Map as A+import StmContainers.Prelude hiding (delete, empty, foldM, insert, lookup, null, toList)++-- |+-- Bidirectional map.+-- Essentially, a bijection between subsets of its two argument types.+--+-- For one value of the left-hand type this map contains one value+-- of the right-hand type and vice versa.+data Bimap leftKey rightKey+ = Bimap !(A.Map leftKey rightKey) !(A.Map rightKey leftKey)++-- |+-- Construct a new bimap.+{-# INLINE new #-}+new :: STM (Bimap leftKey rightKey)+new =+ Bimap <$> A.new <*> A.new++-- |+-- Construct a new bimap in IO.+--+-- This is useful for creating it on a top-level using 'unsafePerformIO',+-- because using 'atomically' inside 'unsafePerformIO' isn't possible.+{-# INLINE newIO #-}+newIO :: IO (Bimap leftKey rightKey)+newIO =+ Bimap <$> A.newIO <*> A.newIO++-- |+-- Check on being empty.+{-# INLINE null #-}+null :: Bimap leftKey rightKey -> STM Bool+null (Bimap leftMap _) =+ A.null leftMap++-- |+-- Get the number of elements.+{-# INLINE size #-}+size :: Bimap leftKey rightKey -> STM Int+size (Bimap leftMap _) =+ A.size leftMap++-- |+-- Focus on a right value by the left value.+--+-- This function allows to perform composite operations in a single access+-- to a map item.+-- E.g., you can look up an item and delete it at the same time,+-- or update it and return the new value.+{-# INLINE focusLeft #-}+focusLeft :: (Hashable leftKey, Hashable rightKey) => B.Focus rightKey STM result -> leftKey -> Bimap leftKey rightKey -> STM result+focusLeft rightFocus leftKey (Bimap leftMap rightMap) =+ do+ ((output, change), maybeRightKey) <- A.focus (B.extractingInput (B.extractingChange rightFocus)) leftKey leftMap+ case change of+ B.Leave ->+ return ()+ B.Remove ->+ forM_ maybeRightKey $ \oldRightKey -> A.delete oldRightKey rightMap+ B.Set newRightKey ->+ do+ forM_ maybeRightKey $ \rightKey -> A.delete rightKey rightMap+ maybeReplacedLeftKey <- A.focus (B.lookup <* B.insert leftKey) newRightKey rightMap+ forM_ maybeReplacedLeftKey $ \replacedLeftKey -> A.delete replacedLeftKey leftMap+ return output++-- |+-- Focus on a left value by the right value.+--+-- This function allows to perform composite operations in a single access+-- to a map item.+-- E.g., you can look up an item and delete it at the same time,+-- or update it and return the new value.+{-# INLINE focusRight #-}+focusRight :: (Hashable leftKey, Hashable rightKey) => B.Focus leftKey STM result -> rightKey -> Bimap leftKey rightKey -> STM result+focusRight valueFocus2 rightKey (Bimap leftMap rightMap) =+ focusLeft valueFocus2 rightKey (Bimap rightMap leftMap)++-- |+-- Look up a right value by the left value.+{-# INLINE lookupLeft #-}+lookupLeft :: (Hashable leftKey) => leftKey -> Bimap leftKey rightKey -> STM (Maybe rightKey)+lookupLeft leftKey (Bimap leftMap _) =+ A.lookup leftKey leftMap++-- |+-- Look up a left value by the right value.+{-# INLINE lookupRight #-}+lookupRight :: (Hashable rightKey) => rightKey -> Bimap leftKey rightKey -> STM (Maybe leftKey)+lookupRight rightKey (Bimap _ rightMap) =+ A.lookup rightKey rightMap++-- |+-- Insert the association by the left value.+{-# INLINE insertLeft #-}+insertLeft :: (Hashable leftKey, Hashable rightKey) => rightKey -> leftKey -> Bimap leftKey rightKey -> STM ()+insertLeft rightKey =+ focusLeft (B.insert rightKey)++-- |+-- Insert the association by the right value.+{-# INLINE insertRight #-}+insertRight :: (Hashable leftKey, Hashable rightKey) => leftKey -> rightKey -> Bimap leftKey rightKey -> STM ()+insertRight leftKey rightKey (Bimap leftMap rightMap) =+ insertLeft leftKey rightKey (Bimap rightMap leftMap)++-- |+-- Delete the association by the left value.+{-# INLINE deleteLeft #-}+deleteLeft :: (Hashable leftKey, Hashable rightKey) => leftKey -> Bimap leftKey rightKey -> STM ()+deleteLeft leftKey (Bimap leftMap rightMap) =+ A.focus B.lookupAndDelete leftKey leftMap+ >>= mapM_ (\rightKey -> A.delete rightKey rightMap)++-- |+-- Delete the association by the right value.+{-# INLINE deleteRight #-}+deleteRight :: (Hashable leftKey, Hashable rightKey) => rightKey -> Bimap leftKey rightKey -> STM ()+deleteRight rightKey (Bimap leftMap rightMap) =+ deleteLeft rightKey (Bimap rightMap leftMap)++-- |+-- Delete all the associations.+{-# INLINE reset #-}+reset :: Bimap leftKey rightKey -> STM ()+reset (Bimap leftMap rightMap) =+ do+ A.reset leftMap+ A.reset rightMap++-- |+-- Stream associations actively.+--+-- Amongst other features this function provides an interface to folding.+{-# INLINE unfoldlM #-}+unfoldlM :: Bimap leftKey rightKey -> UnfoldlM STM (leftKey, rightKey)+unfoldlM (Bimap leftMap _) =+ A.unfoldlM leftMap++-- |+-- Stream the associations passively.+{-# INLINE listT #-}+listT :: Bimap key value -> ListT STM (key, value)+listT (Bimap leftMap _) =+ A.listT leftMap
+ library/StmContainers/Map.hs view
@@ -0,0 +1,122 @@+module StmContainers.Map+ ( Map,+ new,+ newIO,+ null,+ size,+ focus,+ lookup,+ insert,+ delete,+ reset,+ unfoldlM,+ listT,+ listTNonAtomic,+ )+where++import qualified DeferredFolds.UnfoldlM as C+import qualified Focus as B+import StmContainers.Prelude hiding (delete, empty, foldM, insert, lookup, null, toList)+import qualified StmHamt.Hamt as A++-- |+-- Hash-table, based on STM-specialized Hash Array Mapped Trie.+newtype Map key value+ = Map (A.Hamt (Product2 key value))++-- |+-- Construct a new map.+{-# INLINEABLE new #-}+new :: STM (Map key value)+new =+ Map <$> A.new++-- |+-- Construct a new map in IO.+--+-- This is useful for creating it on a top-level using 'unsafePerformIO',+-- because using 'atomically' inside 'unsafePerformIO' isn't possible.+{-# INLINEABLE newIO #-}+newIO :: IO (Map key value)+newIO =+ Map <$> A.newIO++-- |+-- Check, whether the map is empty.+{-# INLINEABLE null #-}+null :: Map key value -> STM Bool+null (Map hamt) =+ A.null hamt++-- |+-- Get the number of elements.+{-# INLINEABLE size #-}+size :: Map key value -> STM Int+size =+ C.foldlM' (\x _ -> return (succ x)) 0 . unfoldlM++-- |+-- Focus on a value by the key.+--+-- This function allows to perform composite operations in a single access+-- to the map's row.+-- E.g., you can look up a value and delete it at the same time,+-- or update it and return the new value.+{-# INLINE focus #-}+focus :: (Hashable key) => B.Focus value STM result -> key -> Map key value -> STM result+focus valueFocus key (Map hamt) =+ A.focus rowFocus (\(Product2 key _) -> key) key hamt+ where+ rowFocus =+ B.mappingInput (\value -> Product2 key value) (\(Product2 _ value) -> value) valueFocus++-- |+-- Look up an item.+{-# INLINEABLE lookup #-}+lookup :: (Hashable key) => key -> Map key value -> STM (Maybe value)+lookup key =+ focus B.lookup key++-- |+-- Insert a value at a key.+{-# INLINE insert #-}+insert :: (Hashable key) => value -> key -> Map key value -> STM ()+insert value key (Map hamt) =+ void (A.insert (\(Product2 key _) -> key) (Product2 key value) hamt)++-- |+-- Delete an item by a key.+{-# INLINEABLE delete #-}+delete :: (Hashable key) => key -> Map key value -> STM ()+delete key =+ focus B.delete key++-- |+-- Delete all the associations.+{-# INLINEABLE reset #-}+reset :: Map key value -> STM ()+reset (Map hamt) =+ A.reset hamt++-- |+-- Stream the associations actively.+--+-- Amongst other features this function provides an interface to folding.+{-# INLINEABLE unfoldlM #-}+unfoldlM :: Map key value -> UnfoldlM STM (key, value)+unfoldlM (Map hamt) =+ fmap (\(Product2 k v) -> (k, v)) (A.unfoldlM hamt)++-- |+-- Stream the associations passively.+{-# INLINE listT #-}+listT :: Map key value -> ListT STM (key, value)+listT (Map hamt) = fmap (\(Product2 k v) -> (k, v)) (A.listT hamt)++-- |+-- Stream the associations passively.+-- Data may be inconsistent/out of date.+{-# INLINE listTNonAtomic #-}+listTNonAtomic :: Map key value -> ListT IO (key, value)+listTNonAtomic (Map hamt) = fmap (\(Product2 k v) -> (k, v)) (A.listTNonAtomic hamt)
+ library/StmContainers/Multimap.hs view
@@ -0,0 +1,181 @@+module StmContainers.Multimap+ ( Multimap,+ new,+ newIO,+ null,+ focus,+ lookup,+ lookupByKey,+ insert,+ delete,+ deleteByKey,+ reset,+ unfoldlM,+ unfoldlMKeys,+ unfoldlMByKey,+ listT,+ listTKeys,+ listTByKey,+ )+where++import qualified Focus as C+import qualified StmContainers.Map as A+import StmContainers.Prelude hiding (delete, empty, foldM, insert, lookup, null, toList)+import qualified StmContainers.Set as B++-- |+-- A multimap, based on an STM-specialized hash array mapped trie.+--+-- Basically it's just a wrapper API around @'A.Map' key ('B.Set' value)@.+newtype Multimap key value+ = Multimap (A.Map key (B.Set value))++-- |+-- Construct a new multimap.+{-# INLINE new #-}+new :: STM (Multimap key value)+new =+ Multimap <$> A.new++-- |+-- Construct a new multimap in IO.+--+-- This is useful for creating it on a top-level using 'unsafePerformIO',+-- because using 'atomically' inside 'unsafePerformIO' isn't possible.+{-# INLINE newIO #-}+newIO :: IO (Multimap key value)+newIO =+ Multimap <$> A.newIO++-- |+-- Check on being empty.+{-# INLINE null #-}+null :: Multimap key value -> STM Bool+null (Multimap map) =+ A.null map++-- |+-- Focus on an item by the value and the key.+--+-- This function allows to perform simultaneous lookup and modification.+--+-- The focus is over a unit since we already know,+-- which value we're focusing on and it doesn't make sense to replace it,+-- however we still can decide wether to keep or remove it.+{-# INLINE focus #-}+focus :: (Hashable key, Hashable value) => C.Focus () STM result -> value -> key -> Multimap key value -> STM result+focus unitFocus@(Focus concealUnit _) value key (Multimap map) = A.focus setFocus key map+ where+ setFocus = C.Focus conceal reveal+ where+ conceal = do+ (output, change) <- concealUnit+ case change of+ C.Set () ->+ do+ set <- B.new+ B.insert value set+ return (output, C.Set set)+ _ ->+ return (output, C.Leave)+ reveal set = do+ output <- B.focus unitFocus value set+ change <- bool C.Leave C.Remove <$> B.null set+ return (output, change)++-- |+-- Look up an item by a value and a key.+{-# INLINE lookup #-}+lookup :: (Hashable key, Hashable value) => value -> key -> Multimap key value -> STM Bool+lookup value key (Multimap m) =+ maybe (return False) (B.lookup value) =<< A.lookup key m++-- |+-- Look up all values by key.+{-# INLINE lookupByKey #-}+lookupByKey :: (Hashable key) => key -> Multimap key value -> STM (Maybe (B.Set value))+lookupByKey key (Multimap m) =+ A.lookup key m++-- |+-- Insert an item.+{-# INLINEABLE insert #-}+insert :: (Hashable key, Hashable value) => value -> key -> Multimap key value -> STM ()+insert value key (Multimap map) = A.focus setFocus key map+ where+ setFocus = Focus conceal reveal+ where+ conceal = do+ set <- B.new+ B.insert value set+ return ((), C.Set set)+ reveal set = do+ B.insert value set+ return ((), C.Leave)++-- |+-- Delete an item by a value and a key.+{-# INLINEABLE delete #-}+delete :: (Hashable key, Hashable value) => value -> key -> Multimap key value -> STM ()+delete value key (Multimap map) = A.focus setFocus key map+ where+ setFocus = Focus conceal reveal+ where+ conceal = returnChange C.Leave+ reveal set = do+ B.delete value set+ B.null set >>= returnChange . bool C.Leave C.Remove+ returnChange c = return ((), c)++-- |+-- Delete all values associated with the key.+{-# INLINEABLE deleteByKey #-}+deleteByKey :: (Hashable key) => key -> Multimap key value -> STM ()+deleteByKey key (Multimap map) =+ A.delete key map++-- |+-- Delete all the associations.+{-# INLINE reset #-}+reset :: Multimap key value -> STM ()+reset (Multimap map) =+ A.reset map++-- |+-- Stream associations actively.+--+-- Amongst other features this function provides an interface to folding.+unfoldlM :: Multimap key value -> UnfoldlM STM (key, value)+unfoldlM (Multimap m) =+ A.unfoldlM m >>= \(key, s) -> (key,) <$> B.unfoldlM s++-- |+-- Stream keys actively.+unfoldlMKeys :: Multimap key value -> UnfoldlM STM key+unfoldlMKeys (Multimap m) =+ fmap fst (A.unfoldlM m)++-- |+-- Stream values by a key actively.+unfoldlMByKey :: (Hashable key) => key -> Multimap key value -> UnfoldlM STM value+unfoldlMByKey key (Multimap m) =+ lift (A.lookup key m) >>= maybe mempty B.unfoldlM++-- |+-- Stream associations passively.+listT :: Multimap key value -> ListT STM (key, value)+listT (Multimap m) =+ A.listT m >>= \(key, s) -> (key,) <$> B.listT s++-- |+-- Stream keys passively.+listTKeys :: Multimap key value -> ListT STM key+listTKeys (Multimap m) =+ fmap fst (A.listT m)++-- |+-- Stream values by a key passively.+listTByKey :: (Hashable key) => key -> Multimap key value -> ListT STM value+listTByKey key (Multimap m) =+ lift (A.lookup key m) >>= maybe mempty B.listT
+ library/StmContainers/Prelude.hs view
@@ -0,0 +1,82 @@+module StmContainers.Prelude+ ( module Exports,+ modifyTVar',+ Product2 (..),+ )+where++import Control.Applicative as Exports+import Control.Arrow as Exports+import Control.Category as Exports+import Control.Concurrent as Exports+import Control.Exception as Exports+import Control.Monad as Exports hiding (forM, forM_, mapM, mapM_, msum, sequence, sequence_)+import Control.Monad.Fix as Exports hiding (fix)+import Control.Monad.IO.Class as Exports+import Control.Monad.ST as Exports+import Control.Monad.Trans.Class as Exports+import Data.Bits as Exports+import Data.Bool as Exports+import Data.Char as Exports+import Data.Coerce as Exports+import Data.Complex as Exports+import Data.Data as Exports+import Data.Dynamic as Exports+import Data.Either as Exports+import Data.Fixed as Exports+import Data.Foldable as Exports+import Data.Function as Exports hiding (id, (.))+import Data.Functor as Exports hiding (unzip)+import Data.Hashable as Exports (Hashable (..))+import Data.IORef as Exports+import Data.Int as Exports+import Data.Ix as Exports+import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons)+import Data.Maybe as Exports+import Data.Monoid as Exports hiding (First (..), Last (..))+import Data.Ord as Exports+import Data.Proxy as Exports+import Data.Ratio as Exports+import Data.STRef as Exports+import Data.String as Exports+import Data.Traversable as Exports+import Data.Tuple as Exports+import Data.Unique as Exports+import Data.Version as Exports+import Data.Word as Exports+import Debug.Trace as Exports+import DeferredFolds.Unfoldl as Exports (Unfoldl (..))+import DeferredFolds.UnfoldlM as Exports (UnfoldlM (..))+import Focus as Exports (Focus (..))+import Foreign.ForeignPtr as Exports+import Foreign.Ptr as Exports+import Foreign.StablePtr as Exports+import Foreign.Storable as Exports hiding (alignment, sizeOf)+import GHC.Conc as Exports hiding (threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar)+import GHC.Exts as Exports (groupWith, inline, lazy, sortWith)+import GHC.Generics as Exports (Generic)+import GHC.IO.Exception as Exports+import ListT as Exports (ListT (..))+import Numeric as Exports+import System.Environment as Exports+import System.Exit as Exports+import System.IO as Exports+import System.IO.Error as Exports+import System.IO.Unsafe as Exports+import System.Mem as Exports+import System.Mem.StableName as Exports+import System.Timeout as Exports+import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readP_to_Prec, readPrec_to_P, readPrec_to_S, readS_to_Prec)+import Text.Printf as Exports (hPrintf, printf)+import Text.Read as Exports (Read (..), readEither, readMaybe)+import Unsafe.Coerce as Exports+import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.))++-- | Strict version of 'modifyTVar'.+{-# INLINE modifyTVar' #-}+modifyTVar' :: TVar a -> (a -> a) -> STM ()+modifyTVar' var f = do+ x <- readTVar var+ writeTVar var $! f x++data Product2 a b = Product2 !a !b deriving (Eq)
+ library/StmContainers/Set.hs view
@@ -0,0 +1,123 @@+module StmContainers.Set+ ( Set,+ new,+ newIO,+ null,+ size,+ focus,+ lookup,+ insert,+ delete,+ reset,+ unfoldlM,+ listT,+ listTNonAtomic,+ )+where++import qualified Focus as B+import StmContainers.Prelude hiding (delete, empty, foldM, insert, lookup, null, toList)+import qualified StmHamt.SizedHamt as A++-- |+-- A hash set, based on an STM-specialized hash array mapped trie.+newtype Set item+ = Set (A.SizedHamt item)++-- |+-- Construct a new set.+{-# INLINEABLE new #-}+new :: STM (Set item)+new =+ Set <$> A.new++-- |+-- Construct a new set in IO.+--+-- This is useful for creating it on a top-level using 'unsafePerformIO',+-- because using 'atomically' inside 'unsafePerformIO' isn't possible.+{-# INLINEABLE newIO #-}+newIO :: IO (Set item)+newIO =+ Set <$> A.newIO++-- |+-- Check, whether the set is empty.+{-# INLINEABLE null #-}+null :: Set item -> STM Bool+null (Set hamt) =+ A.null hamt++-- |+-- Get the number of elements.+{-# INLINEABLE size #-}+size :: Set item -> STM Int+size (Set hamt) =+ A.size hamt++-- |+-- Focus on an element with a strategy.+--+-- This function allows to perform simultaneous lookup and modification.+--+-- The strategy is over a unit since we already know,+-- which element we're focusing on and it doesn't make sense to replace it,+-- however we still can decide wether to keep or remove it.+{-# INLINEABLE focus #-}+focus :: (Hashable item) => B.Focus () STM result -> item -> Set item -> STM result+focus unitFocus item (Set hamt) =+ A.focus rowFocus id item hamt+ where+ rowFocus =+ B.mappingInput (const item) (const ()) unitFocus++-- |+-- Lookup an element.+{-# INLINEABLE lookup #-}+lookup :: (Hashable item) => item -> Set item -> STM Bool+lookup =+ focus (fmap isJust B.lookup)++-- |+-- Insert a new element.+{-# INLINEABLE insert #-}+insert :: (Hashable item) => item -> Set item -> STM ()+insert item (Set hamt) =+ A.insert id item hamt++-- |+-- Delete an element.+{-# INLINEABLE delete #-}+delete :: (Hashable item) => item -> Set item -> STM ()+delete item (Set hamt) =+ A.focus B.delete id item hamt++-- |+-- Delete all the elements.+{-# INLINEABLE reset #-}+reset :: Set item -> STM ()+reset (Set hamt) =+ A.reset hamt++-- |+-- Stream the elements actively.+--+-- Amongst other features this function provides an interface to folding.+{-# INLINEABLE unfoldlM #-}+unfoldlM :: Set item -> UnfoldlM STM item+unfoldlM (Set hamt) =+ A.unfoldlM hamt++-- |+-- Stream the elements passively.+{-# INLINE listT #-}+listT :: Set item -> ListT STM item+listT (Set hamt) =+ A.listT hamt++-- |+-- Stream the elements passively.+-- Data may be inconsistent/out of date.+{-# INLINE listTNonAtomic #-}+listTNonAtomic :: Set item -> ListT IO item+listTNonAtomic (Set hamt) = A.listTNonAtomic hamt
stm-containers.cabal view
@@ -1,232 +1,150 @@-name:- stm-containers-version:- 0.1.4-synopsis:- Containers for STM+cabal-version: 3.0+name: stm-containers+version: 1.2.2+synopsis: Containers for STM description:- This library is based on an STM-specialized implementation of a+ This library is based on an STM-specialized implementation of Hash Array Mapped Trie.- It provides efficient implementations of @Map@, @Set@ + It provides efficient implementations of @Map@, @Set@ and other data structures,- which are slightly slower than their counterparts from \"unordered-containers\",- but scale very well on concurrent access patterns.+ which starting from version @1@ perform even better than their counterparts from \"unordered-containers\",+ but also scale well on concurrent access patterns. .- For details on performance of the library see+ For details on performance of the library, which are a bit outdated, see <http://nikita-volkov.github.io/stm-containers/ this blog post>.-category:- Data Structures, STM, Concurrency-homepage:- https://github.com/nikita-volkov/stm-containers -bug-reports:- https://github.com/nikita-volkov/stm-containers/issues -author:- Nikita Volkov <nikita.y.volkov@mail.ru>-maintainer:- Nikita Volkov <nikita.y.volkov@mail.ru>-copyright:- (c) 2014, Nikita Volkov-license:- MIT-license-file:- LICENSE-build-type:- Simple-cabal-version:- >=1.10 +category: Data Structures, STM, Concurrency+homepage: https://github.com/nikita-volkov/stm-containers+bug-reports: https://github.com/nikita-volkov/stm-containers/issues+author: Nikita Volkov <nikita.y.volkov@mail.ru>+maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>+copyright: (c) 2014, Nikita Volkov+license: MIT+license-file: LICENSE source-repository head- type:- git- location:- git://github.com/nikita-volkov/stm-containers.git-+ type: git+ location: https://github.com/nikita-volkov/stm-containers library- hs-source-dirs:- library- other-modules:- STMContainers.Prelude- STMContainers.WordArray.Indices- STMContainers.WordArray- STMContainers.SizedArray- STMContainers.HAMT.Level- STMContainers.HAMT.Nodes- STMContainers.HAMT- exposed-modules:- STMContainers.Bimap- STMContainers.Multimap- STMContainers.Map- STMContainers.Set- build-depends:- -- data:- hashable < 1.3,- -- control:- focus > 0.1.0 && < 0.2,- -- debugging:- loch-th == 0.2.*,- placeholders == 0.1.*,- -- general:- primitive == 0.5.*,- base-prelude == 0.1.*+ hs-source-dirs: library default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010+ Arrows+ BangPatterns+ ConstraintKinds+ DataKinds+ DefaultSignatures+ DeriveDataTypeable+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveTraversable+ EmptyDataDecls+ FlexibleContexts+ FlexibleInstances+ FunctionalDependencies+ GADTs+ GeneralizedNewtypeDeriving+ LambdaCase+ LiberalTypeSynonyms+ MagicHash+ MultiParamTypeClasses+ MultiWayIf+ NoImplicitPrelude+ NoMonomorphismRestriction+ OverloadedStrings+ ParallelListComp+ PatternGuards+ PatternSynonyms+ QuasiQuotes+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TemplateHaskell+ TupleSections+ TypeApplications+ TypeFamilies+ TypeOperators+ UnboxedTuples + default-language: Haskell2010+ exposed-modules:+ StmContainers.Bimap+ StmContainers.Map+ StmContainers.Multimap+ StmContainers.Set -test-suite word-array-tests- type:- exitcode-stdio-1.0- hs-source-dirs:- executables- library- main-is:- WordArrayTests.hs+ other-modules: StmContainers.Prelude build-depends:- -- testing:- free >= 4.6 && < 4.10,- mtl == 2.*,- QuickCheck == 2.7.*,- HTF == 0.11.*,- -- data:- hashable < 1.3,- -- control:- focus > 0.1.0 && < 0.2,- -- debugging:- loch-th == 0.2.*,- placeholders == 0.1.*,- -- general:- primitive == 0.5.*,- base-prelude == 0.1.*- default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010-+ base >=4.9 && <5,+ deferred-folds >=0.9 && <0.10,+ focus >=1.0.1.4 && <1.1,+ hashable >=1.4 && <2,+ list-t >=1.0.1 && <1.1,+ stm-hamt >=1.2.2 && <1.3,+ transformers >=0.5 && <0.7, -test-suite api-tests- type:- exitcode-stdio-1.0- hs-source-dirs:- executables- main-is:- APITests.hs- build-depends:- QuickCheck == 2.7.*,- HTF == 0.11.*,- stm-containers,- -- debugging:- loch-th == 0.2.*,- placeholders == 0.1.*,- -- general:- base-prelude == 0.1.*,- focus > 0.1.0 && < 0.2,- unordered-containers == 0.2.*,- free >= 4.6 && < 4.10,- mtl == 2.*,- hashable < 1.3,- base >= 4.5 && < 4.8+test-suite test+ type: exitcode-stdio-1.0+ hs-source-dirs: test default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010+ Arrows+ BangPatterns+ ConstraintKinds+ DataKinds+ DefaultSignatures+ DeriveDataTypeable+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveTraversable+ EmptyDataDecls+ FlexibleContexts+ FlexibleInstances+ FunctionalDependencies+ GADTs+ GeneralizedNewtypeDeriving+ LambdaCase+ LiberalTypeSynonyms+ MagicHash+ MultiParamTypeClasses+ MultiWayIf+ NoImplicitPrelude+ NoMonomorphismRestriction+ OverloadedStrings+ ParallelListComp+ PatternGuards+ PatternSynonyms+ QuasiQuotes+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TemplateHaskell+ TupleSections+ TypeApplications+ TypeFamilies+ TypeOperators+ UnboxedTuples + default-language: Haskell2010+ main-is: Main.hs+ other-modules:+ Suites.Bimap+ Suites.Map+ Suites.Map.Update+ Suites.Set -benchmark insertion-bench- type: - exitcode-stdio-1.0- hs-source-dirs:- executables- main-is:- InsertionBench.hs- ghc-options:- -O2 -threaded "-with-rtsopts=-N"- default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010 build-depends:- mwc-random == 0.13.*,- mwc-random-monad == 0.7.*,- criterion == 0.8.*,- -- data:- text < 1.2,- focus > 0.1.0 && < 0.2,- hashable < 1.3,- hashtables == 1.1.*,- containers == 0.5.*,- unordered-containers == 0.2.*,- stm-containers,- -- debugging:- loch-th == 0.2.*,- placeholders == 0.1.*,- -- general:- base >= 4.5 && < 4.8---benchmark concurrent-insertion-bench- type: - exitcode-stdio-1.0- hs-source-dirs:- executables- main-is:- ConcurrentInsertionBench.hs- ghc-options:- -O2 -threaded "-with-rtsopts=-N"- default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010- build-depends:- criterion == 0.8.*,- mwc-random == 0.13.*,- mwc-random-monad == 0.7.*,- -- data:- text < 1.2,- focus > 0.1.0 && < 0.2,- unordered-containers == 0.2.*,- hashable < 1.3,- stm-containers,- -- debugging:- loch-th == 0.2.*,- placeholders == 0.1.*,- -- general:- free >= 4.5 && < 4.10,- async == 2.0.*,- base >= 4.5 && < 4.8---benchmark concurrent-transactions-bench- type: - exitcode-stdio-1.0- hs-source-dirs:- executables- main-is:- ConcurrentTransactionsBench.hs- ghc-options:- -O2 -threaded "-with-rtsopts=-N"- default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010- build-depends:- criterion == 0.8.*,- mwc-random == 0.13.*,- mwc-random-monad == 0.7.*,- -- data:- containers >= 0.5.2 && < 0.6,- text < 1.2,- focus > 0.1.0 && < 0.2,- unordered-containers == 0.2.*,- hashable < 1.3,+ deferred-folds,+ focus,+ foldl >=1.4 && <2,+ free >=4.6 && <6,+ list-t,+ quickcheck-instances >=0.3.29.1 && <0.4,+ rerebase >=1 && <2, stm-containers,- -- debugging:- loch-th == 0.2.*,- placeholders == 0.1.*,- -- general:- mtl == 2.*,- free >= 4.5 && < 4.10,- async == 2.0.*,- base >= 4.5 && < 4.8+ tasty >=0.12 && <2,+ tasty-hunit >=0.10.0.3 && <0.11,+ tasty-quickcheck >=0.10.2 && <0.12,
+ test/Main.hs view
@@ -0,0 +1,14 @@+import qualified Suites.Bimap+import qualified Suites.Map+import qualified Suites.Set+import Test.Tasty+import Prelude++main :: IO ()+main =+ defaultMain+ . testGroup ""+ $ [ testGroup "Bimap" Suites.Bimap.tests,+ testGroup "Map" Suites.Map.tests,+ testGroup "Set" Suites.Set.tests+ ]
+ test/Suites/Bimap.hs view
@@ -0,0 +1,66 @@+module Suites.Bimap (tests) where++import qualified Focus+import qualified ListT+import StmContainers.Bimap+import Test.Tasty+import Test.Tasty.HUnit+import Prelude++tests :: [TestTree]+tests =+ [ testCase "construction" $ do+ m <- newIO :: IO (Bimap Int Int)+ atomically $ insertRight 3 1 m+ atomically $ insertRight 4 2 m+ assertEqual "" [(3, 1), (4, 2)] =<< atomically (ListT.toList (listT m)),+ testCase "deleteLeft" $ do+ m <- newIO :: IO (Bimap Int Int)+ atomically $ insertRight 3 1 m+ atomically $ insertRight 4 2 m+ atomically $ deleteLeft 4 m+ assertEqual "" [(3, 1)] =<< atomically (ListT.toList (listT m)),+ testCase "deleteRight" $ do+ m <- newIO :: IO (Bimap Int Int)+ atomically $ insertRight 3 1 m+ atomically $ insertRight 4 2 m+ atomically $ deleteRight 2 m+ assertEqual "" [(3, 1)] =<< atomically (ListT.toList (listT m)),+ testCase "replacing construction" $ do+ m <- newIO :: IO (Bimap Int Int)+ atomically $ insertRight 3 1 m+ atomically $ insertRight 4 2 m+ atomically $ insertRight 3 2 m+ assertEqual "" [(3, 2)] =<< atomically (ListT.toList (listT m)),+ testCase "insert overwrites" $ do+ m <- newIO :: IO (Bimap Int Int)+ atomically $ insertRight 3 1 m+ assertEqual "" 1 =<< atomically (size m)+ atomically $ insertRight 3 2 m+ assertEqual "" 1 =<< atomically (size m)+ assertEqual "" Nothing =<< atomically (lookupRight 1 m)+ assertEqual "" (Just 3) =<< atomically (lookupRight 2 m)+ assertEqual "" (Just 2) =<< atomically (lookupLeft 3 m)+ assertEqual "" Nothing =<< atomically (focusRight Focus.lookup 1 m)+ assertEqual "" (Just 3) =<< atomically (focusRight Focus.lookup 2 m)+ atomically $ focusRight (Focus.insert 3) 4 m+ assertEqual "" 1 =<< atomically (size m)+ assertEqual "" Nothing =<< atomically (lookupRight 1 m)+ assertEqual "" Nothing =<< atomically (lookupRight 2 m)+ assertEqual "" (Just 3) =<< atomically (lookupRight 4 m),+ testCase "insert overwrites 2" $ do+ m <- newIO :: IO (Bimap Int Char)+ atomically $ insertLeft 'a' 1 m+ assertEqual "" 1 =<< atomically (size m)+ atomically $ insertLeft 'a' 2 m+ assertEqual "" 1 =<< atomically (size m)+ assertEqual "" Nothing =<< atomically (lookupLeft 1 m)+ assertEqual "" (Just 'a') =<< atomically (lookupLeft 2 m)+ assertEqual "" Nothing =<< atomically (focusLeft Focus.lookup 1 m)+ assertEqual "" (Just 'a') =<< atomically (focusLeft Focus.lookup 2 m)+ atomically $ focusLeft (Focus.insert 'a') 3 m+ assertEqual "" 1 =<< atomically (size m)+ assertEqual "" Nothing =<< atomically (lookupLeft 1 m)+ assertEqual "" Nothing =<< atomically (lookupLeft 2 m)+ assertEqual "" (Just 'a') =<< atomically (lookupLeft 3 m)+ ]
+ test/Suites/Map.hs view
@@ -0,0 +1,155 @@+module Suites.Map (tests) where++import qualified Control.Foldl as Foldl+import Control.Monad.Free+import qualified Data.HashMap.Strict as HashMap+import qualified DeferredFolds.UnfoldlM as UnfoldlM+import qualified Focus+import qualified StmContainers.Map as StmMap+import qualified Suites.Map.Update as Update+import Test.QuickCheck.Instances ()+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import Prelude hiding (choose)++interpretStmMapUpdate :: (Hashable k) => Update.Update k v -> STM (StmMap.Map k v)+interpretStmMapUpdate update = do+ m <- StmMap.new+ flip iterM update $ \case+ Update.Insert k v c -> StmMap.insert v k m >> c+ Update.Delete k c -> StmMap.delete k m >> c+ Update.Adjust f k c -> StmMap.focus ((Focus.adjustM . fmap return) f) k m >> c+ return m++interpretHashMapUpdate :: (Hashable k) => Update.Update k v -> HashMap.HashMap k v+interpretHashMapUpdate update =+ flip execState HashMap.empty $ flip iterM update $ \case+ Update.Insert k v c -> modify (HashMap.insert k v) >> c+ Update.Delete k c -> modify (HashMap.delete k) >> c+ Update.Adjust f k c -> modify (adjust f k) >> c+ where+ adjust f k m =+ case HashMap.lookup k m of+ Nothing -> m+ Just a -> HashMap.insert k (f a) m++stmMapToHashMap :: (Hashable k) => StmMap.Map k v -> STM (HashMap.HashMap k v)+stmMapToHashMap = UnfoldlM.foldM (Foldl.generalize Foldl.hashMap) . StmMap.unfoldlM++stmMapFromList :: (Hashable k) => [(k, v)] -> STM (StmMap.Map k v)+stmMapFromList list = do+ m <- StmMap.new+ forM_ list $ \(k, v) -> StmMap.insert v k m+ return m++stmMapToList :: StmMap.Map k v -> STM [(k, v)]+stmMapToList = UnfoldlM.foldM (Foldl.generalize Foldl.list) . StmMap.unfoldlM++-- * Intentional hash collision simulation++-------------------------++newtype TestKey = TestKey Word8+ deriving (Eq, Ord, Show)++instance Arbitrary TestKey where+ arbitrary = TestKey <$> choose (0, 63)++instance Hashable TestKey where+ hashWithSalt salt (TestKey w) =+ if odd w+ then hashWithSalt salt (pred w)+ else hashWithSalt salt w++-- * Tests++-------------------------++tests :: [TestTree]+tests =+ [ testProperty "sizeAndList"+ $ let gen = do+ keys <- nub <$> listOf (choose ('a', 'z'))+ mapM (liftA2 (flip (,)) (choose (0, 99 :: Int)) . pure) keys+ prop list =+ length list == stmMapLength+ where+ stmMapLength =+ unsafePerformIO $ atomically $ do+ x <- stmMapFromList list+ StmMap.size x+ in forAll gen prop,+ testProperty "fromListToListHashMapIsomorphism" $ \(list :: [(Text, Int)]) ->+ let hashMapList = HashMap.toList (HashMap.fromList list)+ stmMapList = unsafePerformIO $ atomically $ stmMapFromList list >>= stmMapToList+ in sort hashMapList === sort stmMapList,+ testProperty "updatesProduceTheSameEffectAsInHashMap" $ \(updates :: [Update.Update TestKey ()]) ->+ let update = sequence_ updates+ hashMap = interpretHashMapUpdate update+ hashMapSize = HashMap.size hashMap+ hashMapList = sort (HashMap.toList hashMap)+ (stmMapList, stmMapSize) = unsafePerformIO $ atomically $ do+ stmMap <- interpretStmMapUpdate update+ size <- StmMap.size stmMap+ stmMapList <- stmMapToList stmMap+ return (sort stmMapList, size)+ in (hashMapSize, hashMapList) === (stmMapSize, stmMapList),+ testCase "focusInsert" $ do+ assertEqual "" (HashMap.fromList [('a', 1), ('b', 2)]) =<< do+ atomically $ do+ m <- StmMap.new+ StmMap.focus (Focus.insert 1) 'a' m+ StmMap.focus (Focus.insert 2) 'b' m+ stmMapToHashMap m,+ testCase "focusInsertAndDelete" $ do+ assertEqual "" (HashMap.fromList [('b', 2)]) =<< do+ atomically $ do+ m <- StmMap.new+ StmMap.focus (Focus.insert 1) 'a' m+ StmMap.focus (Focus.insert 2) 'b' m+ StmMap.focus (Focus.delete) 'a' m+ stmMapToHashMap m,+ testCase "focusInsertAndDeleteWithCollision" $ do+ assertEqual "" (HashMap.fromList [(TestKey 32, 2)]) =<< do+ atomically $ do+ m <- StmMap.new+ StmMap.focus (Focus.insert 2) (TestKey 32) m+ StmMap.focus (Focus.delete) (TestKey 1) m+ stmMapToHashMap m,+ testCase "insert" $ do+ assertEqual "" (HashMap.fromList [('a', 1), ('b', 2), ('c', 3)]) =<< do+ atomically $ do+ m <- StmMap.new+ StmMap.insert 1 'a' m+ StmMap.insert 3 'c' m+ StmMap.insert 2 'b' m+ stmMapToHashMap m,+ testCase "insert2" $ do+ assertEqual "" (HashMap.fromList [(111 :: Int, ()), (207, ())]) =<< do+ atomically $ do+ m <- StmMap.new+ StmMap.insert () 111 m+ StmMap.insert () 207 m+ stmMapToHashMap m,+ testCase "adjust" $ do+ assertEqual "" (HashMap.fromList [('a', 1), ('b', 3)]) =<< do+ atomically $ do+ m <- stmMapFromList [('a', 1), ('b', 2)]+ StmMap.focus (Focus.adjustM (const $ return 3)) 'b' m+ stmMapToHashMap m,+ testCase "focusReachesTheTarget" $ do+ assertEqual "" (Just 2) =<< do+ atomically $ do+ m <- stmMapFromList [('a', 1), ('b', 2)]+ StmMap.focus Focus.lookup 'b' m,+ testCase "notNull" $ do+ assertEqual "" False =<< do+ atomically $ StmMap.null =<< stmMapFromList [('a', ())],+ testCase "nullAfterDeletingTheLastElement" $ do+ assertEqual "" True =<< do+ atomically $ do+ m <- stmMapFromList [('a', ())]+ StmMap.delete 'a' m+ StmMap.null m+ ]
+ test/Suites/Map/Update.hs view
@@ -0,0 +1,52 @@+module Suites.Map.Update where++import Control.Monad.Free+import Control.Monad.Free.TH+import Test.QuickCheck.Instances ()+import Test.Tasty.QuickCheck+import Prelude hiding (delete, insert)++data UpdateF k v c+ = Insert k v c+ | Delete k c+ | Adjust (v -> v) k c+ deriving (Functor)++instance (Show k, Show v, Show c) => Show (UpdateF k v c) where+ showsPrec i =+ showParen (i > 5) . \case+ Insert k v c ->+ showString "Insert "+ . showsPrecInner k+ . showChar ' '+ . showsPrecInner v+ . showChar ' '+ . showsPrecInner c+ Delete k c ->+ showString "Delete "+ . showsPrecInner k+ . showChar ' '+ . showsPrecInner c+ Adjust _ k c ->+ showString "Adjust "+ . showString "<v -> v> "+ . showsPrecInner k+ . showChar ' '+ . showsPrecInner c+ where+ showsPrecInner = showsPrec (succ 5)++instance (Show k, Show v) => Show1 (UpdateF k v) where+ liftShowsPrec = undefined++makeFree ''UpdateF++type Update k v = Free (UpdateF k v) ()++instance (Arbitrary k, Arbitrary v) => Arbitrary (Update k v) where+ arbitrary =+ frequency+ [ (1, delete <$> arbitrary),+ (10, insert <$> arbitrary <*> arbitrary),+ (3, adjust <$> (const <$> arbitrary) <*> arbitrary)+ ]
+ test/Suites/Set.hs view
@@ -0,0 +1,125 @@+module Suites.Set (tests) where++import Control.Concurrent.STM+import qualified Control.Foldl as Foldl+import Control.Monad (forM_)+import Control.Monad.Free+import Data.Hashable+import Data.List (nub, sort, splitAt)+import Data.Word (Word8)+import qualified DeferredFolds.UnfoldlM as UnfoldlM+import qualified Focus+import qualified ListT+import qualified StmContainers.Set as StmSet+import System.IO.Unsafe (unsafePerformIO)+import Test.QuickCheck.Instances ()+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import Prelude hiding (choose, null)++-- helpers++stmSetFromList :: (Hashable a, Eq a) => [a] -> STM (StmSet.Set a)+stmSetFromList xs = do+ s <- StmSet.new+ forM_ xs $ \x -> StmSet.insert x s+ return s++stmSetToList :: StmSet.Set a -> STM [a]+stmSetToList = UnfoldlM.foldM (Foldl.generalize Foldl.list) . StmSet.unfoldlM++-- * Intentional hash collision simulation++newtype TestKey = TestKey Word8+ deriving (Eq, Ord, Show)++instance Arbitrary TestKey where+ arbitrary = TestKey <$> choose (0, 63)++instance Hashable TestKey where+ hashWithSalt salt (TestKey w) =+ if odd w+ then hashWithSalt salt (pred w)+ else hashWithSalt salt w++-- * Tests++tests :: [TestTree]+tests =+ [ testProperty "sizeAndList"+ $ let gen = nub <$> listOf (choose ('a', 'z'))+ prop xs =+ length xs == stmSetSize+ where+ stmSetSize =+ unsafePerformIO $ atomically $ do+ s <- stmSetFromList xs+ StmSet.size s+ in forAll gen prop,+ testProperty "fromListToListSetIsomorphism" $ \(xs :: [Int]) ->+ let setList =+ unsafePerformIO+ $ atomically+ $ stmSetFromList xs+ >>= stmSetToList+ in sort (nub xs) === sort setList,+ testProperty "listTNonAtomicIsomorphism" $ \(xs :: [Int]) ->+ let setList =+ unsafePerformIO $ do+ set <- atomically (stmSetFromList xs)+ ListT.toList (StmSet.listTNonAtomic set)+ in sort (nub xs) === sort setList,+ testProperty "insertDeleteWithCollisions" $ \(ks :: [TestKey]) ->+ let dropped = take (length ks `div` 2) ks+ (finalSize, finalList) =+ unsafePerformIO $ atomically $ do+ s <- StmSet.new+ -- insert all+ forM_ ks $ \k -> StmSet.insert k s+ -- delete ~the first half of them+ forM_ dropped $ \k -> StmSet.delete k s+ sz <- StmSet.size s+ ls <- stmSetToList s+ return (sz, sort ls)+ expected =+ let remaining = nub (filter (`notElem` dropped) ks)+ in (length remaining, sort remaining)+ in (finalSize, finalList) === expected,+ testCase "insert"+ $ assertEqual "" (sort ['a', 'b', 'c'])+ =<< do+ atomically $ do+ s <- StmSet.new+ StmSet.insert 'a' s+ StmSet.insert 'c' s+ StmSet.insert 'b' s+ sort <$> stmSetToList s,+ testCase "focusInsert"+ $ assertEqual "" (sort ['a', 'b'])+ =<< do+ atomically $ do+ s <- StmSet.new+ StmSet.focus (Focus.insert ()) 'a' s+ StmSet.focus (Focus.insert ()) 'b' s+ sort <$> stmSetToList s,+ testCase "insertAndDelete"+ $ assertEqual "" ['b']+ =<< do+ atomically $ do+ s <- StmSet.new+ StmSet.focus (Focus.insert ()) 'a' s+ StmSet.focus (Focus.insert ()) 'b' s+ StmSet.focus Focus.delete 'a' s+ sort <$> stmSetToList s,+ testCase "nullAndNotNull" $ do+ assertEqual "" True =<< atomically (StmSet.null =<< StmSet.new)+ assertEqual "" False =<< atomically (StmSet.null =<< stmSetFromList ['a']),+ testCase "nullAfterDeletingTheLastElement"+ $ assertEqual "" True+ =<< do+ atomically $ do+ s <- stmSetFromList ['a']+ StmSet.delete 'a' s+ StmSet.null s+ ]