rrb-vector 0.1.0.0 → 0.1.1.0
raw patch · 15 files changed
+489/−248 lines, 15 filesdep +ghc-heap-viewdep +tastydep +tasty-benchdep −QuickCheckdep −gaugedep −hspecdep ~deepseqdep ~indexed-traversablePVP ok
version bump matches the API change (PVP)
Dependencies added: ghc-heap-view, tasty, tasty-bench, tasty-quickcheck
Dependencies removed: QuickCheck, gauge, hspec
Dependency ranges changed: deepseq, indexed-traversable
API changes (from Hackage documentation)
+ Data.RRBVector: replicate :: Int -> a -> Vector a
Files
- CHANGELOG.md +12/−0
- README.md +2/−1
- bench/Main.hs +13/−11
- bench/Traverse.hs +13/−9
- rrb-vector.cabal +18/−10
- src/Data/RRBVector.hs +2/−2
- src/Data/RRBVector/Internal.hs +144/−110
- src/Data/RRBVector/Internal/Array.hs +20/−13
- src/Data/RRBVector/Internal/Buffer.hs +1/−1
- src/Data/RRBVector/Internal/IntRef.hs +1/−8
- test/Arbitrary.hs +11/−0
- test/Main.hs +13/−0
- test/Properties.hs +166/−0
- test/Spec.hs +0/−83
- test/Strictness.hs +73/−0
+ CHANGELOG.md view
@@ -0,0 +1,12 @@+# 0.1.1.0 - August 2021++* Add `replicate`+* Various optimizations+* Switch to the [`tasty` framework](https://hackage.haskell.org/package/tasty) for benchmarks and tests+* Extend the test suite+ - Add properties for more functions and some typeclass instances+ - Add strictness tests++# 0.1.0.0 - June 2021++* Initial release
README.md view
@@ -1,5 +1,6 @@ # rbb-vector -An implementation of a Relaxed Radix Balanced Vector (RRB-Vector).+An implementation of a Relaxed Radix Balanced Vector (RRB-Vector), an efficient sequence data structure.+It supports fast indexing, iteration, concatenation and splitting. For more information, see [`rrb-vector` on Hackage](https://hackage.haskell.org/package/rrb-vector).
bench/Main.hs view
@@ -1,22 +1,24 @@+{-# LANGUAGE BangPatterns #-}+ import Data.Functor ((<&>)) -import Gauge.Main+import Test.Tasty.Bench import qualified Data.RRBVector as RRB main :: IO ()-main = defaultMain $ [10, 100, 1_000, 10_000, 100_000] <&> \n ->- let v = RRB.fromList [1..n]- idx = [n `div` 10, n `div` 2, n - n `div` 10]+main = defaultMain $ [10, 100, 1000, 10000, 100000] <&> \n ->+ let !v = RRB.fromList [1..n]+ !idx = n `div` 2 in bgroup (show n) [ bench "fromList" $ nf RRB.fromList [1..n]- , bench "><" $ nf (\vec -> vec RRB.>< vec) v- , bench "|>" $ nf (RRB.|> 42) v- , bench "<|" $ nf (42 RRB.<|) v- , bgroup "take" $ idx <&> \i -> bench (show i) $ nf (RRB.take i) v- , bgroup "drop" $ idx <&> \i -> bench (show i) $ nf (RRB.drop i) v- , bgroup "lookup" $ idx <&> \i -> bench (show i) $ nf (RRB.lookup i) v- , bgroup "adjust" $ idx <&> \i -> bench (show i) $ nf (RRB.adjust i (+ 1)) v+ , bench "><" $ whnf (\vec -> vec RRB.>< vec) v+ , bench "|>" $ whnf (RRB.|> 42) v+ , bench "<|" $ whnf (42 RRB.<|) v+ , bench "take" $ whnf (RRB.take idx) v+ , bench "drop" $ whnf (RRB.drop idx) v+ , bench "index" $ nf (RRB.lookup idx) v+ , bench "adjust" $ whnf (RRB.adjust idx (+ 1)) v , bench "foldl" $ nf (foldl (+) 0) v , bench "foldr" $ nf (foldr (+) 0) v ]
bench/Traverse.hs view
@@ -1,21 +1,25 @@ {-# LANGUAGE BangPatterns #-} +import Control.Applicative (liftA2)+import Control.Monad (replicateM) import Data.Foldable (foldl', toList) import Data.Functor -import Gauge.Main+import Test.Tasty.Bench import qualified Data.RRBVector as RRB main :: IO ()-main = defaultMain $ [10, 100, 1_000] <&> \n ->+main = defaultMain $ [100, 1000, 10000] <&> \n -> let !v = RRB.fromList [1..n]+ !idx = n `div` 2 in bgroup (show n)- [ bench "f1 (where)" $ nf (\v -> f1 v v) v- , bench "f2" $ nf (\v -> f2 v v) v+ [ bench "foldr" $ nf (foldr (+) 0) v+ , bench "ifoldr" $ nf (RRB.ifoldr (\i x acc -> i + x + acc) 0) v+ , bench "foldl" $ nf (foldl (+) 0) v+ , bench "ifoldl" $ nf (RRB.ifoldl (\i acc x -> i + acc + x) 0) v+ , bench "map" $ nf (RRB.map (+ 1)) v+ , bench "imap" $ nf (RRB.imap (+)) v+ , bench "traverse" $ nf (traverse (Just $!)) v+ , bench "itraverse" $ nf (RRB.itraverse (\i x -> Just $! i + x)) v ]- where- f1 xs ys = foldl' (\acc x -> acc RRB.>< RRB.fromList (replicate n x)) RRB.empty xs- where- n = length ys- f2 xs ys = foldl' (\acc x -> acc RRB.>< RRB.fromList (replicate (length ys) x)) RRB.empty xs
rrb-vector.cabal view
@@ -1,5 +1,5 @@ name: rrb-vector-version: 0.1.0.0+version: 0.1.1.0 synopsis: Efficient RRB-Vectors description: An RRB-Vector is an efficient sequence data structure.@@ -18,13 +18,15 @@ copyright: 2021 konsumlamm category: Data Structures build-type: Simple-extra-source-files: README.md+extra-source-files:+ CHANGELOG.md+ README.md cabal-version: 2.0 tested-with: GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.5, GHC == 9.0.1 source-repository head type: git- location: https://github.com/konsumlamm/rbb-vector+ location: https://github.com/konsumlamm/rrb-vector library hs-source-dirs: src@@ -43,12 +45,18 @@ test-suite test hs-source-dirs: test- main-is: Spec.hs+ main-is: Main.hs+ other-modules:+ Arbitrary+ Properties+ Strictness type: exitcode-stdio-1.0 ghc-options: -Wall -Wno-orphans -Wno-type-defaults- build-depends: base >= 4.11 && < 5, rrb-vector, hspec, QuickCheck+ build-depends: base >= 4.11 && < 5, deepseq, indexed-traversable, rrb-vector, tasty, tasty-quickcheck+ if impl(ghc == 8.10.*)+ build-depends: ghc-heap-view default-language: Haskell2010- default-extensions: ExtendedDefaultRules, NumericUnderscores+ default-extensions: ExtendedDefaultRules, ScopedTypeVariables benchmark rrb-bench hs-source-dirs: bench@@ -56,8 +64,8 @@ type: exitcode-stdio-1.0 default-language: Haskell2010 ghc-options: -O2- build-depends: base >= 4.11 && < 5, gauge, rrb-vector- default-extensions: ExtendedDefaultRules, NumericUnderscores+ build-depends: base >= 4.11 && < 5, rrb-vector, tasty-bench+ default-extensions: ExtendedDefaultRules benchmark traverse hs-source-dirs: bench@@ -65,5 +73,5 @@ type: exitcode-stdio-1.0 default-language: Haskell2010 ghc-options: -O2- build-depends: base >= 4.11 && < 5, gauge, rrb-vector, primitive- default-extensions: ExtendedDefaultRules, NumericUnderscores+ build-depends: base >= 4.11 && < 5, primitive, rrb-vector, tasty-bench+ default-extensions: ExtendedDefaultRules
src/Data/RRBVector.hs view
@@ -21,7 +21,7 @@ module Data.RRBVector ( Vector -- * Construction- , empty, singleton, fromList+ , empty, singleton, fromList, replicate -- ** Concatenation , (<|), (|>), (><) -- * Deconstruction@@ -45,7 +45,7 @@ , zip, zipWith, unzip ) where -import Prelude hiding (lookup, take, drop, splitAt, map, reverse, zip, zipWith, unzip)+import Prelude hiding (replicate, lookup, take, drop, splitAt, map, reverse, zip, zipWith, unzip) import Data.Foldable.WithIndex import Data.Functor.WithIndex
src/Data/RRBVector/Internal.hs view
@@ -10,7 +10,7 @@ -- * Internal , blockShift, blockSize, treeSize, computeSizes, up -- * Construction- , empty, singleton, fromList+ , empty, singleton, fromList, replicate -- ** Concatenation , (<|), (|>), (><) -- * Deconstruction@@ -43,12 +43,13 @@ import Data.Foldable (Foldable(..), for_) import Data.Functor.Classes import Data.Functor.Identity (Identity(..))-import Data.Maybe (fromMaybe) import qualified Data.List as List+import Data.Maybe (fromMaybe)+import Data.Semigroup import qualified GHC.Exts as Exts import GHC.Stack (HasCallStack)+import Prelude hiding (replicate, lookup, map, take, drop, splitAt, head, last, reverse, zip, zipWith, unzip) import Text.Read-import Prelude hiding (lookup, map, take, drop, splitAt, head, last, reverse, zip, zipWith, unzip) import Data.Functor.WithIndex import Data.Foldable.WithIndex@@ -63,13 +64,15 @@ infixr 5 <| infixl 5 |> +type Shift = Int+ -- Invariant: Children of a Balanced node are always balanced. -- A Leaf node is considered balanced. -- Nodes are always non-empty. data Tree a- = Balanced !(A.Array (Tree a))- | Unbalanced !(A.Array (Tree a)) !(PrimArray Int)- | Leaf !(A.Array a)+ = Balanced {-# UNPACK #-} !(A.Array (Tree a))+ | Unbalanced {-# UNPACK #-} !(A.Array (Tree a)) !(PrimArray Int)+ | Leaf {-# UNPACK #-} !(A.Array a) -- | A vector. --@@ -78,35 +81,31 @@ = Empty | Root !Int -- size- !Int -- shift (blockShift * height)+ !Shift -- shift (blockShift * height) !(Tree a) -- The number of bits used per level.-blockShift :: Int+blockShift :: Shift blockShift = 4-{-# INLINE blockShift #-} -- The maximum size of a block. blockSize :: Int-blockSize = 1 `shiftL` blockShift+blockSize = 1 `unsafeShiftL` blockShift -- The mask used to extract the index into the array. blockMask :: Int blockMask = blockSize - 1 -up :: Int -> Int+up :: Shift -> Shift up sh = sh + blockShift-{-# INLINE up #-} -down :: Int -> Int+down :: Shift -> Shift down sh = sh - blockShift-{-# INLINE down #-} -radixIndex :: Int -> Int -> Int-radixIndex i sh = i `shiftR` sh .&. blockMask-{-# INLINE radixIndex #-}+radixIndex :: Int -> Shift -> Int+radixIndex i sh = i `unsafeShiftR` sh .&. blockMask -relaxedRadixIndex :: PrimArray Int -> Int -> Int -> (Int, Int)+relaxedRadixIndex :: PrimArray Int -> Int -> Shift -> (Int, Int) relaxedRadixIndex sizes i sh = let guess = radixIndex i sh -- guess <= idx idx = loop guess@@ -116,7 +115,6 @@ loop idx = let current = indexPrimArray sizes idx -- idx will always be in range for a well-formed tree in if i < current then idx else loop (idx + 1)-{-# INLINE relaxedRadixIndex #-} treeToArray :: Tree a -> A.Array (Tree a) treeToArray (Balanced arr) = arr@@ -129,44 +127,52 @@ treeBalanced (Leaf _) = True -- @treeSize sh@ is the size of a tree with shift @sh@.-treeSize :: Int -> Tree a -> Int+treeSize :: Shift -> Tree a -> Int treeSize = go 0 where- go acc _ (Leaf arr) = acc + length arr+ go !acc !_ (Leaf arr) = acc + length arr go acc _ (Unbalanced _ sizes) = acc + indexPrimArray sizes (sizeofPrimArray sizes - 1) go acc sh (Balanced arr) = let i = length arr - 1- in go (acc + i * (1 `shiftL` sh)) (down sh) (A.index arr i)+ in go (acc + i * (1 `unsafeShiftL` sh)) (down sh) (A.index arr i) {-# INLINE treeSize #-} -- @computeSizes sh@ turns an array into a tree node by computing the sizes of its subtrees. -- @sh@ is the shift of the resulting tree.-computeSizes :: Int -> A.Array (Tree a) -> Tree a-computeSizes sh arr = runST $ do- let len = length arr- maxSize = 1 `shiftL` sh -- the maximum size of a subtree- sizes <- newPrimArray len- let loop acc isBalanced i- | i < len =- let subtree = A.index arr i- size = treeSize (down sh) subtree- acc' = acc + size- isBalanced' = isBalanced && if i == len - 1 then treeBalanced subtree else size == maxSize- in writePrimArray sizes i acc' *> loop acc' isBalanced' (i + 1)- | otherwise = pure isBalanced- isBalanced <- loop 0 True 0- if isBalanced then- pure $ Balanced arr- else do- sizes <- unsafeFreezePrimArray sizes -- safe because the mutable @sizes@ isn't used afterwards- pure $ Unbalanced arr sizes+computeSizes :: Shift -> A.Array (Tree a) -> Tree a+computeSizes !sh arr+ | isBalanced = Balanced arr+ | otherwise = runST $ do+ sizes <- newPrimArray (length arr)+ let loop acc i+ | i < len =+ let size = treeSize (down sh) (A.index arr i)+ acc' = acc + size+ in writePrimArray sizes i acc' *> loop acc' (i + 1)+ | otherwise = do+ sizes <- unsafeFreezePrimArray sizes -- safe because the mutable @sizes@ isn't used afterwards+ pure $ Unbalanced arr sizes+ loop 0 0+ where+ maxSize = 1 `unsafeShiftL` sh -- the maximum size of a subtree + len = length arr++ lenM1 = len - 1++ isBalanced = go 0+ where+ go i+ | i < lenM1 = treeSize (down sh) subtree == maxSize && go (i + 1)+ | otherwise = treeBalanced subtree+ where+ subtree = A.index arr i+ -- Integer log base 2. log2 :: Int -> Int log2 x = bitSizeMinus1 - countLeadingZeros x where bitSizeMinus1 = finiteBitSize (0 :: Int) - 1-{-# INLINE log2 #-} instance Show1 Vector where liftShowsPrec sp sl p v = showsUnaryWith (liftShowsPrec sp sl) "fromList" p (toList v)@@ -195,7 +201,8 @@ compare = compare1 instance Semigroup (Vector a) where- v1 <> v2 = v1 >< v2+ (<>) = (><)+ stimes = stimesMonoid instance Monoid (Vector a) where mempty = empty@@ -243,45 +250,47 @@ null Empty = True null Root{} = False- {-# INLINE null #-} length Empty = 0 length (Root s _ _) = s- {-# INLINE length #-} instance FoldableWithIndex Int Vector where- ifoldr f z0 v = foldr (\x g !i -> f i x (g (i + 1))) (const z0) v 0+ ifoldr f z0 v = foldr' (\x g !i -> f i x (g (i + 1))) (const z0) v 0+ {-# INLINE ifoldr #-} - ifoldl f z0 v = foldl (\g x !i -> f i (g (i - 1)) x) (const z0) v (length v - 1)+ ifoldl f z0 v = foldl' (\g x !i -> f i (g (i - 1)) x) (const z0) v (length v - 1)+ {-# INLINE ifoldl #-} instance Functor Vector where fmap = map- x <$ v = fromList (replicate (length v) x)+ x <$ v = replicate (length v) x instance FunctorWithIndex Int Vector where imap f v = runIdentity $ evalIndexed (traverse (Indexed . f') v) 0 where- f' x i = i `seq` WithIndex (i + 1) (Identity (f i x))+ f' x !i = WithIndex (i + 1) (Identity (f i x)) instance Traversable Vector where traverse _ Empty = pure Empty traverse f (Root size sh tree) = Root size sh <$> traverseTree tree where traverseTree (Balanced arr) = Balanced <$> A.traverse' traverseTree arr- traverseTree (Unbalanced arr sizes) = Unbalanced <$> A.traverse' traverseTree arr <*> pure sizes+ traverseTree (Unbalanced arr sizes) = flip Unbalanced sizes <$> A.traverse' traverseTree arr traverseTree (Leaf arr) = Leaf <$> A.traverse f arr+ {-# INLINE traverse #-} instance TraversableWithIndex Int Vector where itraverse f v = evalIndexed (traverse (Indexed . f') v) 0 where- f' x i = i `seq` WithIndex (i + 1) (f i x)+ f' x !i = WithIndex (i + 1) (f i x)+ {-# INLINE itraverse #-} instance Applicative Vector where pure = singleton fs <*> xs = foldl' (\acc f -> acc >< map f xs) empty fs liftA2 f xs ys = foldl' (\acc x -> acc >< map (f x) ys) empty xs xs *> ys = foldl' (\acc _ -> acc >< ys) empty xs- xs <* ys = foldl' (\acc x -> acc >< fromList (replicate (length ys) x)) empty xs+ xs <* ys = foldl' (\acc x -> acc >< replicate (length ys) x) empty xs instance Monad Vector where xs >>= f = foldl' (\acc x -> acc >< f x) empty xs@@ -343,14 +352,16 @@ buffer <- Buffer.new blockSize let loop [] = do result <- Buffer.get buffer- pure [f result]+ let !x = f result+ pure [x] loop (t : ts) = do size <- Buffer.size buffer if size == blockSize then do result <- Buffer.get buffer Buffer.push buffer t rest <- loop ts- pure (f result : rest)+ let !x = f result+ pure (x : rest) else do Buffer.push buffer t loop ts@@ -361,6 +372,27 @@ [tree] -> Root (treeSize sh tree) sh tree trees' -> iterateNodes (up sh) trees' +-- | \(O(\log n)\). @replicate n x@ creates a vector of length @n@ with every element set to @x@.+--+-- >>> replicate 5 42+-- fromList [42,42,42,42,42]+--+-- @since 0.1.1.0+replicate :: Int -> a -> Vector a+replicate n x+ | n <= 0 = Empty+ | n <= blockSize = Root n 0 (Leaf $ A.replicate n x)+ | otherwise = iterateNodes blockShift (Leaf $ A.replicate blockSize x) (Leaf $ A.replicate (lastIdx .&. blockMask + 1) x)+ where+ lastIdx = n - 1++ -- @full@ is a full subtree, @rest@ is the last subtree+ iterateNodes !sh !full !rest =+ let subtreesM1 = lastIdx `unsafeShiftR` sh -- the number of subtrees minus 1+ full' = Balanced $ A.replicate blockSize full+ rest' = Balanced $ A.replicateSnoc (subtreesM1 .&. blockMask) full rest+ in if subtreesM1 < blockSize then Root n sh rest' else iterateNodes (up sh) full' rest'+ -- | \(O(\log n)\). The element at the index or 'Nothing' if the index is out of range. lookup :: Int -> Vector a -> Maybe a lookup _ Empty = Nothing@@ -377,6 +409,7 @@ -- | \(O(\log n)\). The element at the index. Calls 'error' if the index is out of range. index :: HasCallStack => Int -> Vector a -> a index i = fromMaybe (error "AMT.index: index out of range") . lookup i+{-# INLINE index #-} -- | \(O(\log n)\). A flipped version of 'lookup'. (!?) :: Vector a -> Int -> Maybe a@@ -451,7 +484,7 @@ -- -- > zip = zipWith (,) zip :: Vector a -> Vector b -> Vector (a, b)-zip v1 v2 = fromList $ List.zip (toList v1) (toList v2)+zip = zipWith (,) -- | \(O(\min(n_1, n_2))\). 'zipWith' generalizes 'zip' by zipping with the function. zipWith :: (a -> b -> c) -> Vector a -> Vector b -> Vector c@@ -462,7 +495,10 @@ -- >>> unzip (fromList [(1, "a"), (2, "b"), (3, "c")]) -- (fromList [1,2,3],fromList ["a","b","c"]) unzip :: Vector (a, b) -> (Vector a, Vector b)-unzip v = (map fst v, map snd v)+unzip v =+ let !left = map fst v+ !right = map snd v+ in (left, right) -- | \(O(\log n)\). The first element and the vector without the first element, or 'Nothing' if the vector is empty. --@@ -516,46 +552,50 @@ v >< Empty = v Root size1 sh1 tree1 >< Root size2 sh2 tree2 = let maxShift = max sh1 sh2- newTree = mergeTrees tree1 sh1 tree2 sh2- in case singleTree newTree of- Just newTree -> Root (size1 + size2) maxShift newTree- Nothing -> Root (size1 + size2) (up maxShift) newTree+ upMaxShift = up maxShift+ newArr = mergeTrees tree1 sh1 tree2 sh2+ in if length newArr == 1+ then Root (size1 + size2) maxShift (A.head newArr)+ else Root (size1 + size2) upMaxShift (computeSizes upMaxShift newArr) where- mergeTrees (Leaf arr1) _ (Leaf arr2) _ = Balanced $- if length arr1 == blockSize then A.from2 (Leaf arr1) (Leaf arr2)- else if length arr1 + length arr2 <= blockSize then A.singleton (Leaf (arr1 <> arr2))- else+ mergeTrees tree1@(Leaf arr1) !_ tree2@(Leaf arr2) !_+ | length arr1 == blockSize = A.from2 tree1 tree2+ | length arr1 + length arr2 <= blockSize = A.singleton $! Leaf (arr1 <> arr2)+ | otherwise = let (left, right) = A.splitAt (arr1 <> arr2) blockSize- in A.from2 (Leaf left) (Leaf right)+ !leftTree = Leaf left+ !rightTree = Leaf right+ in A.from2 leftTree rightTree mergeTrees tree1 sh1 tree2 sh2 = case compare sh1 sh2 of LT ->- let right = treeToArray tree2- (rightHead, rightTail) = viewl right+ let !right = treeToArray tree2+ (rightHead, rightTail) = viewlArr right merged = mergeTrees tree1 sh1 rightHead (down sh2)- in mergeRebalance sh2 A.empty (treeToArray merged) rightTail+ in mergeRebalance sh2 A.empty merged rightTail GT ->- let left = treeToArray tree1- (leftInit, leftLast) = viewr left+ let !left = treeToArray tree1+ (leftInit, leftLast) = viewrArr left merged = mergeTrees leftLast (down sh1) tree2 sh2- in mergeRebalance sh1 leftInit (treeToArray merged) A.empty+ in mergeRebalance sh1 leftInit merged A.empty EQ ->- let left = treeToArray tree1- right = treeToArray tree2- (leftInit, leftLast) = viewr left- (rightHead, rightTail) = viewl right+ let !left = treeToArray tree1+ !right = treeToArray tree2+ (leftInit, leftLast) = viewrArr left+ (rightHead, rightTail) = viewlArr right merged = mergeTrees leftLast (down sh1) rightHead (down sh2)- in mergeRebalance sh1 leftInit (treeToArray merged) rightTail+ in mergeRebalance sh1 leftInit merged rightTail where- viewl arr = (A.head arr, A.drop arr 1)- viewr arr = (A.take arr (length arr - 1), A.last arr)+ viewlArr arr = (A.head arr, A.drop arr 1) - -- the type annotations are necessary to compile- mergeRebalance :: forall a. Int -> A.Array (Tree a) -> A.Array (Tree a) -> A.Array (Tree a) -> Tree a- mergeRebalance sh left center right+ viewrArr arr = (A.take arr (length arr - 1), A.last arr)++ -- the type signature is necessary to compile+ mergeRebalance :: forall a. Shift -> A.Array (Tree a) -> A.Array (Tree a) -> A.Array (Tree a) -> A.Array (Tree a)+ mergeRebalance !sh !left !center !right | sh == blockShift = mergeRebalance' (\(Leaf arr) -> arr) Leaf | otherwise = mergeRebalance' treeToArray (computeSizes (down sh)) where- mergeRebalance' :: (Tree a -> A.Array t) -> (A.Array t -> Tree a) -> Tree a+ mergeRebalance' :: (Tree a -> A.Array t) -> (A.Array t -> Tree a) -> A.Array (Tree a) mergeRebalance' extract construct = runST $ do newRoot <- Buffer.new blockSize newSubtree <- Buffer.new blockSize@@ -570,20 +610,14 @@ Buffer.push newNode x pushTo construct newNode newSubtree pushTo (computeSizes sh) newSubtree newRoot- computeSizes (up sh) <$> Buffer.get newRoot+ Buffer.get newRoot {-# INLINE mergeRebalance' #-} pushTo f from to = do result <- Buffer.get from- Buffer.push to (f result)+ Buffer.push to $! f result {-# INLINE pushTo #-} - singleTree (Balanced arr)- | length arr == 1 = Just (A.head arr)- singleTree (Unbalanced arr _)- | length arr == 1 = Just (A.head arr)- singleTree _ = Nothing- -- | \(O(\log n)\). Add an element to the left end of the vector. -- -- >>> 1 <| fromList [2, 3, 4]@@ -591,28 +625,28 @@ (<|) :: a -> Vector a -> Vector a x <| Empty = singleton x x <| Root size sh tree- | insertShift > sh = Root (size + 1) insertShift (computeSizes insertShift (A.from2 (newBranch x sh) tree))+ | insertShift > sh = Root (size + 1) insertShift (computeSizes insertShift (let !new = newBranch x sh in A.from2 new tree)) | otherwise = Root (size + 1) sh (consTree sh tree) where consTree sh (Balanced arr)- | sh == insertShift = computeSizes sh (A.cons arr (newBranch x (down sh)))+ | sh == insertShift = computeSizes sh (A.cons arr $! newBranch x (down sh)) | otherwise = computeSizes sh (A.adjust' arr 0 (consTree (down sh))) consTree sh (Unbalanced arr _)- | sh == insertShift = computeSizes sh (A.cons arr (newBranch x (down sh)))+ | sh == insertShift = computeSizes sh (A.cons arr $! newBranch x (down sh)) | otherwise = computeSizes sh (A.adjust' arr 0 (consTree (down sh))) consTree _ (Leaf arr) = Leaf $ A.cons arr x insertShift = computeShift size sh (up sh) tree -- compute the shift at which the new branch needs to be inserted (0 means there is space in the leaf)- -- the index is computed for efficient calculation of the shift in a balanced subtree- computeShift i sh min (Balanced _) =- let newShift = (log2 i `div` blockShift) * blockShift+ -- the size is computed for efficient calculation of the shift in a balanced subtree+ computeShift !sz !sh !min (Balanced _) =+ let newShift = (log2 sz `div` blockShift) * blockShift in if newShift > sh then min else newShift computeShift _ sh min (Unbalanced arr sizes) =- let i' = indexPrimArray sizes 0 -- the size of the first subtree+ let sz' = indexPrimArray sizes 0 -- the size of the first subtree newMin = if length arr < blockSize then sh else min- in computeShift i' (down sh) newMin (A.head arr)+ in computeShift sz' (down sh) newMin (A.head arr) computeShift _ _ min (Leaf arr) = if length arr < blockSize then 0 else min -- | \(O(\log n)\). Add an element to the right end of the vector.@@ -622,14 +656,14 @@ (|>) :: Vector a -> a -> Vector a Empty |> x = singleton x Root size sh tree |> x- | insertShift > sh = Root (size + 1) insertShift (computeSizes insertShift (A.from2 tree (newBranch x sh)))+ | insertShift > sh = Root (size + 1) insertShift (computeSizes insertShift (A.from2 tree $! newBranch x sh)) | otherwise = Root (size + 1) sh (snocTree sh tree) where snocTree sh (Balanced arr)- | sh == insertShift = Balanced $ A.snoc arr (newBranch x (down sh)) -- the current subtree is fully balanced+ | sh == insertShift = Balanced (A.snoc arr $! newBranch x (down sh)) -- the current subtree is fully balanced | otherwise = Balanced $ A.adjust' arr (length arr - 1) (snocTree (down sh)) snocTree sh (Unbalanced arr sizes)- | sh == insertShift = Unbalanced (A.snoc arr (newBranch x (down sh))) newSizesSnoc+ | sh == insertShift = Unbalanced (A.snoc arr $! newBranch x (down sh)) newSizesSnoc | otherwise = Unbalanced (A.adjust' arr (length arr - 1) (snocTree (down sh))) newSizesAdjust where -- snoc the last size + 1@@ -653,23 +687,23 @@ insertShift = computeShift size sh (up sh) tree -- compute the shift at which the new branch needs to be inserted (0 means there is space in the leaf)- -- the index is computed for efficient calculation of the shift in a balanced subtree- computeShift i sh min (Balanced _) =- let newShift = (countTrailingZeros i `div` blockShift) * blockShift+ -- the size is computed for efficient calculation of the shift in a balanced subtree+ computeShift !sz !sh !min (Balanced _) =+ let newShift = (countTrailingZeros sz `div` blockShift) * blockShift in if newShift > sh then min else newShift computeShift _ sh min (Unbalanced arr sizes) =- let i' = indexPrimArray sizes (sizeofPrimArray sizes - 1) - indexPrimArray sizes (sizeofPrimArray sizes - 2) -- sizes has at least 2 elements, otherwise the node would be balanced+ let lastIdx = sizeofPrimArray sizes - 1+ sz' = indexPrimArray sizes lastIdx - indexPrimArray sizes (lastIdx - 1) -- the size of the last subtree newMin = if length arr < blockSize then sh else min- in computeShift i' (down sh) newMin (A.last arr)+ in computeShift sz' (down sh) newMin (A.last arr) computeShift _ _ min (Leaf arr) = if length arr < blockSize then 0 else min -- create a new tree with shift @sh@-newBranch :: a -> Int -> Tree a+newBranch :: a -> Shift -> Tree a newBranch x = go where- go 0 = Leaf $ A.singleton x- go sh = Balanced $ A.singleton (go (down sh))-{-# INLINE newBranch #-}+ go 0 = Leaf (A.singleton x)+ go sh = Balanced (A.singleton $! go (down sh)) -- splitting
src/Data/RRBVector/Internal/Array.hs view
@@ -8,6 +8,7 @@ module Data.RRBVector.Internal.Array ( Array, MutableArray , empty, singleton, from2+ , replicate, replicateSnoc , index, head, last , update, adjust, adjust' , take, drop, splitAt@@ -24,7 +25,7 @@ import Control.Monad.ST import Data.Foldable (Foldable(..)) import Data.Primitive.SmallArray-import Prelude hiding (take, drop, splitAt, head, last, map, traverse, read)+import Prelude hiding (replicate, take, drop, splitAt, head, last, map, traverse, read) -- start length array data Array a = Array !Int !Int !(SmallArray a)@@ -40,41 +41,35 @@ !len' = len1 + len2 instance Foldable Array where- foldr f = \z (Array start len arr) ->- let !end = start + len+ foldr f z (Array start len arr) =+ let end = start + len go i | i == end = z | (# x #) <- indexSmallArray## arr i = f x (go (i + 1)) in go start- {-# INLINE foldr #-} - foldl f = \z (Array start len arr) ->+ foldl f z (Array start len arr) = let go i | i < start = z | (# x #) <- indexSmallArray## arr i = f (go (i - 1)) x in go (start + len - 1)- {-# INLINE foldl #-} - foldr' f = \z (Array start len arr) ->+ foldr' f z (Array start len arr) = let go i !acc | i < start = acc | (# x #) <- indexSmallArray## arr i = go (i - 1) (f x acc) in go (start + len - 1) z- {-# INLINE foldr' #-} - foldl' f = \z (Array start len arr) ->- let !end = start + len+ foldl' f z (Array start len arr) =+ let end = start + len go i !acc | i == end = acc | (# x #) <- indexSmallArray## arr i = go (i + 1) (f acc x) in go start z- {-# INLINE foldl' #-} null arr = length arr == 0- {-# INLINE null #-} length (Array _ len _) = len- {-# INLINE length #-} instance (NFData a) => NFData (Array a) where rnf = foldl' (\_ x -> rnf x) ()@@ -93,6 +88,18 @@ sma <- newSmallArray 2 x writeSmallArray sma 1 y pure sma++replicate :: Int -> a -> Array a+replicate n x = Array 0 n $ runSmallArray (newSmallArray n x)++-- > replicateSnoc n x y = snoc (replicate n x) y+replicateSnoc :: Int -> a -> a -> Array a+replicateSnoc n x y = Array 0 len $ runSmallArray $ do+ sma <- newSmallArray len x+ writeSmallArray sma n y+ pure sma+ where+ len = n + 1 index :: Array a -> Int -> a index (Array start _ arr) idx = indexSmallArray arr (start + idx)
src/Data/RRBVector/Internal/Buffer.hs view
@@ -23,7 +23,7 @@ push (Buffer buffer offset) x = do idx <- readIntRef offset A.write buffer idx x- modifyIntRef offset (+ 1)+ writeIntRef offset (idx + 1) get :: Buffer s a -> ST s (A.Array a) get (Buffer buffer offset) = do
src/Data/RRBVector/Internal/IntRef.hs view
@@ -3,7 +3,6 @@ , newIntRef , readIntRef , writeIntRef- , modifyIntRef ) where import Control.Monad.ST@@ -14,7 +13,7 @@ newIntRef :: Int -> ST s (IntRef s) newIntRef i = do arr <- newPrimArray 1- setPrimArray arr 0 1 i+ writePrimArray arr 0 i pure (IntRef arr) {-# INLINE newIntRef #-} @@ -25,9 +24,3 @@ writeIntRef :: IntRef s -> Int -> ST s () writeIntRef (IntRef arr) = writePrimArray arr 0 {-# INLINE writeIntRef #-}--modifyIntRef :: IntRef s -> (Int -> Int) -> ST s ()-modifyIntRef (IntRef arr) f = do- i <- readPrimArray arr 0- writePrimArray arr 0 (f i)-{-# INLINE modifyIntRef #-}
+ test/Arbitrary.hs view
@@ -0,0 +1,11 @@+module Arbitrary where++import Test.Tasty.QuickCheck++import qualified Data.RRBVector as V+import Data.RRBVector.Internal.Debug++-- TODO: improve instance+instance (Arbitrary a) => Arbitrary (V.Vector a) where+ arbitrary = oneof [V.fromList <$> arbitrary, fromListUnbalanced <$> arbitrary]+
+ test/Main.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE BangPatterns #-}++import Test.Tasty+import Test.Tasty.QuickCheck++import Properties (properties)+import Strictness (strictness)++main :: IO ()+main = defaultMain . localOption (QuickCheckTests 1000) . localOption (QuickCheckMaxSize 10000) $ testGroup "rrb-vector"+ [ properties+ , strictness+ ]
+ test/Properties.hs view
@@ -0,0 +1,166 @@+module Properties+ ( properties+ ) where++import Data.Foldable (toList)+import Data.List (uncons)+import Prelude hiding ((==)) -- use @===@ instead++import Data.Foldable.WithIndex+import Data.Functor.WithIndex+import Data.Traversable.WithIndex+import qualified Data.RRBVector as V+import Test.Tasty+import Test.Tasty.QuickCheck++import Arbitrary ()++default (Int)++type V = V.Vector++lookupList :: Int -> [a] -> Maybe a+lookupList i ls+ | i < length ls = Just (ls !! i)+ | otherwise = Nothing++updateList :: Int -> a -> [a] -> [a]+updateList i x ls+ | i < length ls = let (left, _ : right) = splitAt i ls in left ++ (x : right)+ | otherwise = ls++adjustList :: Int -> (a -> a) -> [a] -> [a]+adjustList i f ls+ | i < length ls = let (left, x : right) = splitAt i ls in left ++ (f x : right)+ | otherwise = ls++insertAtList :: Int -> a -> [a] -> [a]+insertAtList i x ls = let (left, right) = splitAt i ls in left ++ (x : right)++deleteAtList :: Int -> [a] -> [a]+deleteAtList i ls+ | i < length ls = let (left, _ : right) = splitAt i ls in left ++ right+ | otherwise = ls++unsnoc :: [a] -> Maybe ([a], a)+unsnoc [] = Nothing+unsnoc ls = Just (init ls, last ls)++instances :: TestTree+instances = testGroup "instances"+ [ testGroup "Foldable"+ [ testProperty "foldr" $ \(v :: V Int) -> foldr (:) [] v === foldr (:) [] (toList v)+ , testProperty "foldl" $ \(v :: V Int) -> foldl (flip (:)) [] v === foldl (flip (:)) [] (toList v)+ ]+ , testGroup "FoldableWithIndex"+ [ testProperty "ifoldr" $ \(v :: V Int) -> ifoldr (\i x acc -> (i, x) : acc) [] v === ifoldr (\i x acc -> (i, x) : acc) [] (toList v)+ , testProperty "ifoldl" $ \(v :: V Int) -> ifoldl (\i acc x -> (i, x) : acc) [] v === ifoldl (\i acc x -> (i, x) : acc) [] (toList v)+ , testProperty "satisfies `ifoldr (const f) x v = foldr f x v`" $+ \(v :: V Int) -> ifoldr (const (:)) [] v === foldr (:) [] v+ , testProperty "satisfies `ifoldl (const f) x v = foldl f x v`" $+ \(v :: V Int) -> ifoldl (const (flip (:))) [] v === foldl (flip (:)) [] v+ ]+ , testGroup "Functor"+ [ testProperty "fmap" $ \v -> toList (V.map (+ 1) v) === map (+ 1) (toList v)+ ]+ , testGroup "FunctorWithIndex"+ [ testProperty "imap" $ \(v :: V Int) -> toList (imap (,) v) === imap (,) (toList v)+ , testProperty "satisfies `imap (const f) v = map f v`" $ \v -> imap (const (+ 1)) v === V.map (+ 1) v+ ]+ , testGroup "Traversable"+ [ testProperty "traverse" $+ \(v :: V Int) -> fmap toList (traverse (Just . (+ 1)) v) === traverse (Just . (+ 1)) (toList v)+ ]+ , testGroup "TraversableWithIndex"+ [ testProperty "itraverse" $+ \(v :: V Int) -> fmap toList (itraverse (\i x -> Just (i + x)) v) === itraverse (\i x -> Just (i + x)) (toList v)+ , testProperty "satisfies `itraverse (const f) v = traverse f v`" $+ \(v :: V Int) -> itraverse (const (Just . (+ 1))) v === traverse (Just . (+ 1)) v+ ]+ ]++properties :: TestTree+properties = testGroup "properties"+ [ testGroup "fromList"+ [ testProperty "satisfies `fromList . toList = id`" $ \v -> V.fromList (toList v) === v+ , testProperty "satisfies `toList . fromList = id`" $ \ls -> toList (V.fromList ls) === ls+ , testProperty "satisfies `fromList [] = empty`" $ V.fromList [] === V.empty+ , testProperty "satisfies `fromList [x] = singleton x`" $ \x -> V.fromList [x] === V.singleton x+ ]+ , testGroup "replicate"+ [ testProperty "satisifes `replicate n == fromList . replicate n`" $ \(Positive n) x -> V.replicate n x === V.fromList (replicate n x)+ , testProperty "returns the empty vector for non-positive n" $ \(NonPositive n) x -> V.replicate n x === V.empty+ ]+ , testGroup "<|"+ [ testProperty "prepends an element" $ \x v -> toList (x V.<| v) === x : toList v+ , testProperty "works for the empty vector" $ \x -> x V.<| V.empty === V.singleton x+ ]+ , testGroup "|>"+ [ testProperty "appends an element" $ \v x -> toList (v V.|> x) === toList v ++ [x]+ , testProperty "works for the empty vector" $ \x -> V.empty V.|> x === V.singleton x+ ]+ , testGroup "><"+ [ testProperty "concatenates two vectors" $ \v1 v2 -> toList (v1 V.>< v2) === toList v1 ++ toList v2+ , testProperty "works for the empty vector" $ \v -> (V.empty V.>< v === v) .&&. (v V.>< V.empty === v)+ ]+ , testGroup "lookup"+ [ testProperty "gets the element at the index" $ \v (NonNegative i) -> V.lookup i v === lookupList i (toList v)+ , testProperty "returns Nothing for negative indices" $ \v (Negative i) -> V.lookup i v === Nothing+ ]+ , testGroup "update"+ [ testProperty "updates the element at the index" $ \v (NonNegative i) x -> toList (V.update i x v) === updateList i x (toList v)+ , testProperty "returns the vector for negative indices" $ \v (Negative i) x -> V.update i x v === v+ ]+ , testGroup "adjust"+ [ testProperty "adjusts the element at the index" $ \v (NonNegative i) (Fn f) -> toList (V.adjust i f v) === adjustList i f (toList v)+ , testProperty "returns the vector for negative indices" $ \v (Negative i) (Fn f) -> V.adjust i f v === v+ ]+ , testGroup "adjust'"+ [ testProperty "adjusts the element at the index" $ \v (NonNegative i) (Fn f) -> toList (V.adjust' i f v) === adjustList i f (toList v)+ , testProperty "returns the vector for negative indices" $ \v (Negative i) (Fn f) -> V.adjust' i f v === v+ ]+ , testGroup "viewl"+ [ testProperty "works like uncons" $ \v -> fmap (\(x, xs) -> (x, toList xs)) (V.viewl v) === uncons (toList v)+ , testProperty "works for the empty vector" $ V.viewl V.empty === Nothing+ ]+ , testGroup "viewr"+ [ testProperty "works like unsnoc" $ \v -> fmap (\(xs, x) -> (toList xs, x)) (V.viewr v) === unsnoc (toList v)+ , testProperty "works for the empty vector" $ V.viewr V.empty === Nothing+ ]+ , testGroup "take"+ [ testProperty "takes n elements" $ \v (Positive n) -> toList (V.take n v) === take n (toList v)+ , testProperty "returns the empty vector for non-positive n" $ \v (NonPositive n) -> V.take n v === V.empty+ ]+ , testGroup "drop"+ [ testProperty "drops n elements" $ \v (Positive n) -> toList (V.drop n v) === drop n (toList v)+ , testProperty "returns the vector for non-positive n" $ \v (NonPositive n) -> V.drop n v === v+ ]+ , testGroup "splitAt"+ [ testProperty "splits the vector" $ \v n -> let (v1, v2) = V.splitAt n v in (toList v1, toList v2) === splitAt n (toList v)+ ]+ , testGroup "insertAt"+ [ testProperty "inserts an element" $ \v i x -> toList (V.insertAt i x v) === insertAtList i x (toList v)+ , testProperty "satisfies `insertAt 0 x v = x <| v`" $ \v x -> V.insertAt 0 x v === x V.<| v+ , testProperty "satisfies `insertAt (length v) x v = v |> x`" $ \v x -> V.insertAt (length v) x v === v V.|> x+ ]+ , testGroup "deleteAt"+ [ testProperty "deletes an element" $ \v (NonNegative i) -> toList (V.deleteAt i v) === deleteAtList i (toList v)+ , testProperty "returns the vector for negative indices" $ \v (Negative i) -> V.deleteAt i v === v+ , testProperty "satisfies `deleteAt 0 v = drop 1 v`" $ \v -> V.deleteAt 0 v === V.drop 1 v+ , testProperty "satisfies `deleteAt (length v - 1) v = take (length v - 1) v`" $ \v -> V.deleteAt (length v - 1) v === V.take (length v - 1) v+ ]+ , testGroup "reverse"+ [ testProperty "reverses the vector" $ \v -> toList (V.reverse v) === reverse (toList v)+ ]+ , testGroup "zip"+ [ testProperty "zips two vectors" $ \v1 v2 -> toList (V.zip v1 v2) === zip (toList v1) (toList v2)+ ]+ , testGroup "zipWith"+ [ testProperty "zips two vectors with a function" $ \v1 v2 -> toList (V.zipWith (+) v1 v2) === zipWith (+) (toList v1) (toList v2)+ , testProperty "satisfies `zipWith (,) v1 v2 = zip v1 v2`" $ \v1 v2 -> V.zipWith (,) v1 v2 === V.zip v1 v2+ ]+ , testGroup "unzip"+ [ testProperty "unzips the vector" $ \v -> (\(xs, ys) -> (toList xs, toList ys)) (V.unzip v) === unzip (toList v)+ ]+ , instances+ ]
− test/Spec.hs
@@ -1,83 +0,0 @@-import Data.Foldable (toList)-import Data.List (uncons)--import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck--import qualified Data.RRBVector as V-import Data.RRBVector.Internal.Debug (fromListUnbalanced)--default (Int)--instance (Arbitrary a) => Arbitrary (V.Vector a) where- arbitrary = oneof [V.fromList <$> arbitrary, fromListUnbalanced <$> arbitrary]--lookupList :: Int -> [a] -> Maybe a-lookupList i ls- | i < length ls = Just (ls !! i)- | otherwise = Nothing--updateList :: Int -> a -> [a] -> [a]-updateList i x ls- | i < length ls = let (left, _ : right) = splitAt i ls in left ++ (x : right)- | otherwise = ls--adjustList :: Int -> (a -> a) -> [a] -> [a]-adjustList i f ls- | i < length ls = let (left, x : right) = splitAt i ls in left ++ (f x : right)- | otherwise = ls--unsnoc :: [a] -> Maybe ([a], a)-unsnoc [] = Nothing-unsnoc ls = Just (init ls, last ls)--main :: IO ()-main = hspec . modifyMaxSuccess maxN . modifyMaxSize maxN $ do- prop "satisfies `fromList . toList == id`" $ \v -> V.fromList (toList v) === v- prop "satisfies `toList . fromList == id`" $ \ls -> toList (V.fromList ls) === ls-- describe "lookup" $ do- prop "gets the element at the index" $ \v (NonNegative i) -> V.lookup i v === lookupList i (toList v)- prop "returns Nothing for negative indices" $ \v (Negative i) -> V.lookup i v === Nothing-- describe "update" $ do- prop "updates the element at the index" $ \v (NonNegative i) x -> toList (V.update i x v) === updateList i x (toList v)- prop "returns the vector for negative indices" $ \v (Negative i) x -> V.update i x v === v-- describe "adjust" $ do- prop "adjusts the element at the index" $ \v (NonNegative i) -> toList (V.adjust i (+ 1) v) === adjustList i (+ 1) (toList v)- prop "returns the vector for negative indices" $ \v (Negative i) -> V.adjust i (+ 1) v === v-- describe "><" $ do- prop "concatenates two vectors" $ \v1 v2 -> toList (v1 V.>< v2) === toList v1 ++ toList v2- prop "works for the empty vector" $ \v -> (V.empty V.>< v `shouldBe` v) .&&. (v V.>< V.empty `shouldBe` v)-- describe "|>" $ do- prop "appends an element" $ \v x -> toList (v V.|> x) === toList v ++ [x]- prop "works for the empty vector" $ \x -> V.empty V.|> x `shouldBe` V.singleton x-- describe "<|" $ do- prop "prepends an element" $ \x v -> toList (x V.<| v) === x : toList v- prop "works for the empty vector" $ \x -> x V.<| V.empty `shouldBe` V.singleton x-- describe "take" $ do- prop "takes n elements" $ \v (Positive n) -> toList (V.take n v) === take n (toList v)- prop "returns the empty vector for non-positive n" $ \v (NonPositive n) -> V.take n v === V.empty-- describe "drop" $ do- prop "drops n elements" $ \v (Positive n) -> toList (V.drop n v) === drop n (toList v)- prop "does nothing for non-positive n" $ \v (NonPositive n) -> V.drop n v === v-- describe "viewl" $ do- prop "works like uncons" $ \v -> fmap (\(x, xs) -> (x, toList xs)) (V.viewl v) === uncons (toList v)- prop "works for the empty vector" $ V.viewl V.empty `shouldBe` Nothing-- describe "viewr" $ do- prop "works like unsnoc" $ \v -> fmap (\(xs, x) -> (toList xs, x)) (V.viewr v) === unsnoc (toList v)- prop "works for the empty vector" $ V.viewr V.empty `shouldBe` Nothing-- describe "map" $ do- prop "maps over the vector" $ \v -> toList (V.map (+ 1) v) === map (+ 1) (toList v)- where- maxN = const 10_000
+ test/Strictness.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}++#define GHC_HEAP_VIEW defined(VERSION_ghc_heap_view)++module Strictness+ ( strictness+ ) where++#if GHC_HEAP_VIEW+import Control.DeepSeq (deepseq)+import GHC.AssertNF (isNF)+#endif+import qualified Data.RRBVector as V+import Test.Tasty+import Test.Tasty.QuickCheck++import Arbitrary ()++default (Int)++#if GHC_HEAP_VIEW+testNF :: a -> Property+testNF !x = ioProperty (isNF x)++tailVector :: V.Vector a -> Maybe (V.Vector a)+tailVector v = case V.viewl v of+ Nothing -> Nothing+ Just (_, xs) -> Just xs++initVector :: V.Vector a -> Maybe (V.Vector a)+initVector v = case V.viewr v of+ Nothing -> Nothing+ Just (xs, _) -> Just xs+#endif++strictness :: TestTree+strictness = testGroup "strictness"+#if GHC_HEAP_VIEW+ [ testGroup "nf"+ [ testProperty "empty" $ testNF V.empty+ , testProperty "singleton" $ testNF (V.singleton 42)+ , testProperty "fromList" $ \ls -> ls `deepseq` testNF (V.fromList ls)+ , testProperty "replicate" $ \n -> testNF (V.replicate n 42)+ , testProperty "update" $ \v (NonNegative i) -> v `deepseq` testNF (V.update i 42 v)+ , testProperty "adjust'" $ \v (NonNegative i) -> v `deepseq` testNF (V.adjust' i (+ 1) v)+ , testProperty "<|" $ \v -> v `deepseq` testNF (42 V.<| v)+ , testProperty "|>" $ \v -> v `deepseq` testNF (v V.|> 42)+ , testProperty "><" $ \v1 v2 -> v1 `deepseq` v2 `deepseq` testNF (v1 V.>< v2)+ , testProperty "take" $ \v n -> v `deepseq` testNF (V.take n v)+ , testProperty "drop" $ \v n -> v `deepseq` testNF (V.drop n v)+ , testProperty "splitAt" $ \v n -> v `deepseq` testNF (V.splitAt n v)+ , testProperty "insertAt" $ \v i -> v `deepseq` testNF (V.insertAt i 42 v)+ , testProperty "deleteAt" $ \v i -> v `deepseq` testNF (V.deleteAt i v)+ , testProperty "viewl (tail)" $ \v -> v `deepseq` testNF (tailVector v)+ , testProperty "viewr (init)" $ \v -> v `deepseq` testNF (initVector v)+ , testProperty "reverse" $ \v -> v `deepseq` testNF (V.reverse v)+ ]+ , testGroup "bottom"+#else+ [ testGroup "bottom"+#endif+ [ testProperty "singleton" $ V.singleton undefined `seq` ()+ , testProperty "fromList" $ \n -> V.fromList (replicate n undefined) `seq` ()+ , testProperty "replicate" $ \n -> V.replicate n undefined `seq` ()+ , testProperty "<|" $ \v -> undefined V.<| v `seq` ()+ , testProperty "|>" $ \v -> v V.|> undefined `seq` ()+ , testProperty "update" $ \v i -> V.update i undefined v `seq` ()+ , testProperty "adjust" $ \v i -> V.adjust i (const undefined) v `seq` ()+ , testProperty "insertAt" $ \v i -> V.insertAt i undefined v `seq` ()+ , testProperty "map" $ \v -> V.map (const undefined) v `seq` ()+ ]+ ]