streamly-bytestring 0.1.4 → 0.2.3
raw patch · 7 files changed
Files
- Changelog.md +18/−0
- README.md +13/−11
- benchmark/Main.hs +17/−16
- src/Streamly/External/ByteString.hs +150/−33
- src/Streamly/External/ByteString/Lazy.hs +59/−27
- streamly-bytestring.cabal +24/−23
- test/Main.hs +47/−18
Changelog.md view
@@ -1,5 +1,23 @@ # Changelog for streamly-bytestring +## 0.2.3 (Sep 2025)++* Support streamly-core-0.3.0++## 0.2.2 (Jul 2024)++* Fix a bug that uses an unpinned array in the foreign pointer++## 0.2.1 (Dec 2023)++* Support streamly-core-0.2.0++## 0.2.0 (Mar 2023)++* Support streamly-core-0.1.0+* Support bytestring >= 0.3.0+* Rename read to reader+ ## 0.1.4 (Dec 2021) * Support streamly-0.8.1
README.md view
@@ -2,8 +2,8 @@ Library for streamly and bytestring interoperation. -If you are writing code from scratch, please use `Streamly.Data.Array.Foreign`-which is a generalization of `ByteString` and better integrated with streamly.+If you are writing code from scratch, please use `Streamly.Data.Array` which is+a generalization of `ByteString` and better integrated with streamly. This library is to enable interoperation of streamly with existing code that uses `ByteString`.@@ -13,15 +13,16 @@ Word8`. The interconversion in the case of strict `Bytestring` and streamly `Array-Word8` has no overhead as the underlying representation of ByteString and Array-are the same, we just need to rewrap the data in a different type.+Word8` has no overhead for GHC allocated memory. For foreign allocator allocated+memory there is a copy involved. ## Usage This is a dumb program that counts the number of bytes in a file. -```-import qualified Streamly.Prelude as S+```haskell+import qualified Streamly.Data.Stream as S+import qualified Streamly.Data.Fold as FL import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL@@ -32,13 +33,14 @@ import System.IO (FilePath) strictByteStringSize :: BS.ByteString -> IO Int-strictByteStringSize bs = S.length $ S.unfold Strict.read bs+strictByteStringSize bs = S.fold FL.length $ S.unfold Strict.reader bs lazyByteStringSize :: BSL.ByteString -> IO Int-lazyByteStringSize bsl = S.foldl' (+) 0- $ S.mapM strictByteStringSize- $ S.map Strict.fromArray- $ Lazy.toChunks bsl+lazyByteStringSize bsl =+ S.fold (FL.foldl' (+) 0)+ $ S.mapM strictByteStringSize+ $ fmap Strict.fromArray+ $ Lazy.toChunks bsl fileSize :: FilePath -> IO Int fileSize path = do
benchmark/Main.hs view
@@ -4,7 +4,8 @@ import qualified Streamly.External.ByteString.Lazy as Lazy import Control.Monad.IO.Class (MonadIO) -import qualified Streamly.Prelude as S+import qualified Streamly.Data.Stream as S+import qualified Streamly.Data.Fold as Fold import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL @@ -31,45 +32,45 @@ fromChunks :: Monad m => Int -> m BSL.ByteString fromChunks n = Lazy.fromChunks $- S.map Strict.toArray $- S.map BS.singleton $- S.map fromIntegral $- S.map (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numChunks)+ fmap Strict.toArray $+ fmap BS.singleton $+ fmap fromIntegral $+ fmap (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numChunks) {-# INLINE fromChunksIO #-} fromChunksIO :: Int -> IO BSL.ByteString fromChunksIO n = Lazy.fromChunksIO $- S.map Strict.toArray $- S.map BS.singleton $- S.map fromIntegral $- S.map (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numChunks)+ fmap Strict.toArray $+ fmap BS.singleton $+ fmap fromIntegral $+ fmap (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numChunks) {-# INLINE toChunks #-} toChunks :: Monad m => BSL.ByteString -> m ()-toChunks = S.drain . Lazy.toChunks+toChunks = S.fold Fold.drain . Lazy.toChunks {-# INLINE strictWrite #-} strictWrite :: MonadIO m => Int -> m BS.ByteString strictWrite n = S.fold Strict.write $- S.map fromIntegral $- S.map (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numElements)+ fmap fromIntegral $+ fmap (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numElements) {-# INLINE strictWriteN #-} strictWriteN :: MonadIO m => Int -> m BS.ByteString strictWriteN n = S.fold (Strict.writeN numElements) $- S.map fromIntegral $- S.map (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numElements)+ fmap fromIntegral $+ fmap (\x -> x `mod` 256) $ S.enumerateFromTo n (n + numElements) {-# INLINE strictRead #-} strictRead :: MonadIO m => BS.ByteString -> m ()-strictRead = S.drain . S.unfold Strict.read+strictRead = S.fold Fold.drain . S.unfold Strict.reader {-# INLINE lazyRead #-} lazyRead :: MonadIO m => BSL.ByteString -> m ()-lazyRead = S.drain . S.unfold Lazy.read+lazyRead = S.fold Fold.drain . S.unfold Lazy.reader main :: IO () main =
src/Streamly/External/ByteString.hs view
@@ -6,69 +6,186 @@ ( toArray , fromArray - , read+ , reader+ , writeN , write++ -- Deprecated+ , read ) where import Control.Monad.IO.Class (MonadIO) import Data.Word (Word8)-import GHC.ForeignPtr (ForeignPtr(..))-import GHC.Ptr (Ptr(..), minusPtr, nullPtr, plusPtr)-import Streamly.Data.Unfold (Unfold, lmap)+import Foreign.ForeignPtr (withForeignPtr)+import Foreign.Storable (peek)+import GHC.Exts+ ( Addr#+ , MutableByteArray#+ , RealWorld+ , byteArrayContents#+ , minusAddr#+ , plusAddr#+ , unsafeCoerce#+ )+import GHC.ForeignPtr (ForeignPtr(..), ForeignPtrContents(..))+import GHC.Int (Int(..))+import GHC.Ptr (Ptr(..), nullPtr, plusPtr) import Streamly.Data.Fold (Fold)+import Streamly.Data.Unfold (Unfold, lmap) -- Internal imports import Data.ByteString.Internal (ByteString(..))-import Streamly.Internal.Data.Array.Foreign.Type (Array(..))-import Streamly.Internal.Data.Array.Foreign.Mut.Type- (ArrayContents, arrayToFptrContents, fptrToArrayContents, nilArrayContents)+import Streamly.Internal.System.IO (unsafeInlineIO) -import qualified Streamly.Data.Array.Foreign as A+import qualified Streamly.Data.Array as Array+import qualified Streamly.Internal.Data.Unfold as Unfold (fold, mkUnfoldrM) +#if !(MIN_VERSION_bytestring(0,11,0))+import GHC.ForeignPtr (plusForeignPtr)+#endif++#if MIN_VERSION_streamly_core(0,2,0)+import Streamly.Internal.Data.Array (Array(..))+import Streamly.Internal.Data.MutByteArray (MutByteArray(..))+import qualified Streamly.Internal.Data.Array as Array+import qualified Streamly.Internal.Data.MutByteArray as MutBA+import qualified Streamly.Internal.Data.Stream as StreamD (Step(Yield))+#else+import Streamly.Internal.Data.Array.Type (Array(..))+import Streamly.Internal.Data.Unboxed (MutableByteArray(..))+import qualified Streamly.Internal.Data.Unboxed as MutBA+import qualified Streamly.Internal.Data.Stream.StreamD as StreamD (Step(Yield))+#endif+ import Prelude hiding (read) +#if MIN_VERSION_streamly_core(0,2,0)+#define MUT_BYTE_ARRAY MutByteArray+#else+#define MUT_BYTE_ARRAY MutableByteArray+#endif++#if MIN_VERSION_streamly_core(0,2,2)+#define NIL MutBA.empty+#else+#define NIL MutBA.nil+#endif++#if MIN_VERSION_bytestring(0,11,0)+#define CONSTRUCTOR(a, b, c) BS a c+#define WHEN_0_10_12(x)+#else+#define CONSTRUCTOR(a, b, c) PS a b c+#define WHEN_0_10_12(x) x+#endif+++{-# INLINE ensurePinned #-}+ensurePinned :: Array a -> IO (Array a)+{-# INLINE pinnedCreateOf #-}+pinnedCreateOf :: MonadIO m => Int -> Fold m Word8 (Array Word8)+{-# INLINE pinnedCreate #-}+pinnedCreate :: MonadIO m => Fold m Word8 (Array Word8)++#if MIN_VERSION_streamly_core(0,3,0)+ensurePinned = Array.pin+pinnedCreateOf = Array.createOf'+pinnedCreate = Array.create'+#elif MIN_VERSION_streamly_core(0,2,2)+ensurePinned = Array.pin+pinnedCreateOf = Array.pinnedCreateOf+pinnedCreate = Array.pinnedCreate+#elif MIN_VERSION_streamly_core(0,2,0)+ensurePinned = Array.pin+pinnedCreateOf = Array.pinnedWriteN+pinnedCreate = Array.pinnedWrite+#else+ensurePinned = pure+pinnedCreateOf = Array.writeN+pinnedCreate = Array.write+#endif++#if MIN_VERSION_streamly_core(0,3,0)+#define CREATE_N Array.createOf+#else+#define CREATE_N Array.writeN+#endif++{-# INLINE mutableByteArrayContents# #-}+mutableByteArrayContents# :: MutableByteArray# RealWorld -> Addr#+mutableByteArrayContents# marr# = byteArrayContents# (unsafeCoerce# marr#)+ -- | Helper function that creates a ForeignPtr-makeForeignPtr :: ArrayContents -> Ptr a -> ForeignPtr a-makeForeignPtr contents (Ptr addr#) =- ForeignPtr addr# (arrayToFptrContents contents)+{-# INLINE makeForeignPtr #-}+makeForeignPtr :: MUT_BYTE_ARRAY -> Int -> ForeignPtr a+makeForeignPtr (MUT_BYTE_ARRAY marr#) (I# off#) =+ ForeignPtr+ (mutableByteArrayContents# marr# `plusAddr#` off#)+ (PlainPtr marr#) --- | Convert a 'ByteString' to an array of 'Word8'. This function unwraps the--- 'ByteString' and wraps it with 'Array' constructors and hence the operation--- is performed in constant time.+-- | Convert a 'ByteString' to an array of 'Word8'. It can be done in constant+-- time only for GHC allocated memory. For foreign allocator allocated memory+-- there is a copy involved. {-# INLINE toArray #-} toArray :: ByteString -> Array Word8-toArray (PS (ForeignPtr addr# _) _ _)- | Ptr addr# == nullPtr = Array nilArrayContents nullPtr nullPtr-toArray (PS (ForeignPtr addr# fpcontents) off len) =- Array (fptrToArrayContents fpcontents) startPtr endPtr- where- startPtr = Ptr addr# `plusPtr` off- endPtr = startPtr `plusPtr` len+toArray (CONSTRUCTOR((ForeignPtr addr# _), _, _))+ | Ptr addr# == nullPtr = Array NIL 0 0+toArray (CONSTRUCTOR((ForeignPtr addr# (PlainPtr marr#)), off0, len)) =+ let off = I# (addr# `minusAddr#` mutableByteArrayContents# marr#)+ WHEN_0_10_12(+ off0)+ in Array (MUT_BYTE_ARRAY marr#) off (off + len)+toArray (CONSTRUCTOR(fptr, off, len)) =+ unsafeInlineIO+ $ withForeignPtr (fptr WHEN_0_10_12(`plusForeignPtr` off))+ $ Unfold.fold (CREATE_N len) generator --- | Convert an array of 'Word8' to a 'ByteString'. This function unwraps the--- 'Array' and wraps it with 'ByteString' constructors and hence the operation--- is performed in constant time.+ where++ generator =+ Unfold.mkUnfoldrM+ (\ptr -> flip StreamD.Yield (ptr `plusPtr` 1) <$> peek ptr)++-- | Convert an array of 'Word8' to a 'ByteString'.+--+-- Please ensure that the array is pinned when using this function.++-- If the array is pinned, the operation is performed in constant time. Whereas+-- for an unpinned array a copy is involved to pin it.+-- {-# INLINE fromArray #-} fromArray :: Array Word8 -> ByteString-fromArray Array {..}+fromArray arr | aLen == 0 = mempty- | otherwise = PS (makeForeignPtr arrContents arrStart) 0 aLen- where- aLen = aEnd `minusPtr` arrStart+ | otherwise = unsafeInlineIO $ do+ Array{..} <- ensurePinned arr+ pure $ CONSTRUCTOR((makeForeignPtr arrContents arrStart), 0, aLen) + where++ aLen = arrEnd arr - arrStart arr+ -- | Unfold a strict ByteString to a stream of Word8.-{-# INLINE read #-}-read :: Monad m => Unfold m ByteString Word8-read = lmap toArray A.read+{-# INLINE reader #-}+reader :: Monad m => Unfold m ByteString Word8+reader = lmap toArray Array.reader -- | Fold a stream of Word8 to a strict ByteString of given size in bytes. {-# INLINE writeN #-} writeN :: MonadIO m => Int -> Fold m Word8 ByteString-writeN i = fromArray <$> A.writeN i+writeN i = fromArray <$> pinnedCreateOf i -- | Fold a stream of Word8 to a strict ByteString of appropriate size. {-# INLINE write #-} write :: MonadIO m => Fold m Word8 ByteString-write = fromArray <$> A.write+write = fromArray <$> pinnedCreate++--------------------------------------------------------------------------------+-- Deprecated+--------------------------------------------------------------------------------++{-# DEPRECATED read "Please use reader instead." #-}+{-# INLINE read #-}+read :: Monad m => Unfold m ByteString Word8+read = reader
src/Streamly/External/ByteString/Lazy.hs view
@@ -1,47 +1,67 @@+{-# LANGUAGE CPP #-}+ module Streamly.External.ByteString.Lazy- ( readChunks- , read+ ( chunkReader+ , reader , toChunks , fromChunks , fromChunksIO++ -- Deprecated+ , read+ , readChunks ) where import Data.Word (Word8)-import Streamly.Data.Unfold (many)-import Streamly.Data.Array.Foreign (Array)+import Streamly.Data.Array (Array) import System.IO.Unsafe (unsafeInterleaveIO)+import Streamly.Data.Stream (Stream) -- Internal imports import Data.ByteString.Lazy.Internal (ByteString(..), chunk)-import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))++#if MIN_VERSION_streamly_core(0,2,0)+import Streamly.Internal.Data.Unfold (Unfold(..))+import Streamly.Internal.Data.Stream (Step(..))+#else import Streamly.Internal.Data.Unfold.Type (Unfold(..))+import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))+#endif import qualified Streamly.External.ByteString as Strict-import qualified Streamly.Data.Array.Foreign as A-import qualified Streamly.Prelude as S+import qualified Streamly.Data.Array as Array+import qualified Streamly.Data.Unfold as Unfold+import qualified Streamly.Data.Stream as Stream -import Prelude hiding (concat, read)+import Prelude hiding (read) +#if MIN_VERSION_streamly_core(0,3,0)+#define UNFOLD_EACH Unfold.unfoldEach+#else+#define UNFOLD_EACH Unfold.many+#endif+ -- | Unfold a lazy ByteString to a stream of 'Array' 'Words'.-{-# INLINE readChunks #-}-readChunks :: Monad m => Unfold m ByteString (Array Word8)-readChunks = Unfold step seed+{-# INLINE chunkReader #-}+chunkReader :: Monad m => Unfold m ByteString (Array Word8)+chunkReader = Unfold step seed where seed = return step (Chunk bs bl) = return $ Yield (Strict.toArray bs) bl step Empty = return Stop -- | Unfold a lazy ByteString to a stream of Word8-{-# INLINE read #-}-read :: Monad m => Unfold m ByteString Word8-read = many readChunks A.read+{-# INLINE reader #-}+reader :: Monad m => Unfold m ByteString Word8+reader = UNFOLD_EACH Array.reader readChunks +-- TODO: "toChunks" should be called "read" instead -- | Convert a lazy 'ByteString' to a serial stream of 'Array' 'Word8'. {-# INLINE toChunks #-}-toChunks :: Monad m => ByteString -> S.SerialT m (Array Word8)-toChunks = S.unfold readChunks+toChunks :: Monad m => ByteString -> Stream m (Array Word8)+toChunks = Stream.unfold readChunks {- newtype LazyIO a = LazyIO { runLazy :: IO a } deriving (Functor, Applicative)@@ -80,21 +100,33 @@ -- /fromChunks/ can then be used as, -- @ -- {-# INLINE fromChunksIO #-}--- fromChunksIO :: SerialT IO (Array Word8) -> IO ByteString--- fromChunksIO str = runLazy (fromChunks (S.hoist liftToLazy str))+-- fromChunksIO :: Stream IO (Array Word8) -> IO ByteString+-- fromChunksIO str = runLazy (fromChunks (Stream.hoist liftToLazy str)) -- @ {-# INLINE fromChunks #-}-fromChunks :: Monad m => S.SerialT m (Array Word8) -> m ByteString-fromChunks = S.foldr chunk Empty . S.map Strict.fromArray+fromChunks :: Monad m => Stream m (Array Word8) -> m ByteString+fromChunks = Stream.foldr chunk Empty . fmap Strict.fromArray -- | Convert a serial stream of 'Array' 'Word8' to a lazy 'ByteString' in the -- /IO/ monad. {-# INLINE fromChunksIO #-}-fromChunksIO :: S.SerialT IO (Array Word8) -> IO ByteString+fromChunksIO :: Stream IO (Array Word8) -> IO ByteString fromChunksIO =--- Although the /IO/ monad is strict in nature we emulate laziness using--- 'unsafeInterleaveIO'.- S.foldrM- (\x b -> chunk x <$> unsafeInterleaveIO b)- (pure Empty) .- S.map Strict.fromArray+ -- Although the /IO/ monad is strict in nature we emulate laziness using+ -- 'unsafeInterleaveIO'.+ Stream.foldrM (\x b -> chunk x <$> unsafeInterleaveIO b) (pure Empty)+ . fmap Strict.fromArray++--------------------------------------------------------------------------------+-- Deprecated+--------------------------------------------------------------------------------++{-# DEPRECATED readChunks "Please use chunkReader instead." #-}+{-# INLINE readChunks #-}+readChunks :: Monad m => Unfold m ByteString (Array Word8)+readChunks = chunkReader++{-# DEPRECATED read "Please use reader instead." #-}+{-# INLINE read #-}+read :: Monad m => Unfold m ByteString Word8+read = reader
streamly-bytestring.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name: streamly-bytestring-version: 0.1.4+version: 0.2.3 synopsis: Library for streamly and bytestring interoperation. description: Please see the README on GitHub at <https://github.com/psibi/streamly-bytestring#readme> category: Streamly, Stream, ByteString@@ -15,6 +15,13 @@ extra-source-files: README.md Changelog.md+tested-with: GHC==8.6.5+ , GHC==8.8.4+ , GHC==8.10.7+ , GHC==9.0.1+ , GHC==9.2.7+ , GHC==9.4.4+ , GHC==9.6.2 source-repository head type: git@@ -31,14 +38,19 @@ ghc-options: -Wall -O2 build-depends: base >=4.7 && <5- , bytestring >=0.10.0 && <=0.11.1.0- , streamly >= 0.8.1 && < 0.8.2- if impl(ghc < 8.1)- build-depends:- base-compat >=0.11- if impl(ghc < 8)- build-depends:- transformers >=0.4+ , bytestring == 0.10.12.*+ || == 0.11.0.*+ || == 0.11.1.*+ || == 0.11.2.*+ -- bytestring-0.11.3.0 causes panics and other issues on+ -- windows.+ || ( >= 0.11.3.1 && < 0.11.4 )+ || == 0.11.4.*+ || == 0.11.5.*+ || == 0.12.0.*+ || == 0.12.1.*+ || == 0.12.2.*+ , streamly-core >= 0.1.0 && < 0.3.1 default-language: Haskell2010 test-suite sb-test@@ -58,15 +70,10 @@ , hspec-discover , quickcheck-instances , random- , streamly+ , streamly-core , streamly-bytestring , temporary- if impl(ghc < 8.1)- build-depends:- base-compat >=0.11- if impl(ghc < 8)- build-depends:- transformers >=0.4+ , QuickCheck default-language: Haskell2010 benchmark sb-benchmark@@ -83,12 +90,6 @@ , deepseq , gauge , random- , streamly+ , streamly-core , streamly-bytestring- if impl(ghc < 8.1)- build-depends:- base-compat >=0.11- if impl(ghc < 8)- build-depends:- transformers >=0.4 default-language: Haskell2010
test/Main.hs view
@@ -1,13 +1,17 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-} module Main where +import Control.Monad (when)+import System.Mem (performMajorGC)+import Control.Concurrent (threadDelay) import Data.ByteString (ByteString) import Data.Word (Word8) import GHC.IO.Handle (Handle)-import GHC.Ptr (minusPtr) import System.Random (randomIO)-import Streamly.FileSystem.Handle (readChunks)+import Streamly.FileSystem.Handle (chunkReader) import System.IO (openFile, IOMode(ReadMode)) import System.IO.Temp (withSystemTempFile) import Test.Hspec@@ -15,31 +19,39 @@ import Test.QuickCheck.Instances.ByteString () -- Internal imports-import Streamly.Internal.Data.Array.Foreign.Type (Array(..))+#if MIN_VERSION_streamly_core(0,2,0)+import Streamly.Internal.Data.Array (Array(..))+#else+import Streamly.Internal.Data.Array.Type (Array(..))+#endif + import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import qualified Streamly.External.ByteString as Strict import qualified Streamly.External.ByteString.Lazy as Lazy-import qualified Streamly.Prelude as S+import qualified Streamly.Data.Stream as S+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Internal.Data.Array as Array -streamToByteString :: S.MonadAsync m => S.SerialT m (Array Word8) -> m ByteString-streamToByteString stream = S.foldl' (<>) mempty $ S.map Strict.fromArray stream+streamToByteString :: Monad m => S.Stream m (Array Word8) -> m ByteString+streamToByteString stream =+ S.fold (Fold.foldl' (<>) mempty) $ fmap Strict.fromArray stream checkFileContent :: FilePath -> Handle -> IO () checkFileContent filename handle' = do print $ "Checking " <> filename bsContent <- BS.hGetContents handle' handle <- openFile filename ReadMode- bsStreamly <- streamToByteString $ S.unfold readChunks handle+ bsStreamly <- streamToByteString $ S.unfold chunkReader handle bsContent `shouldBe` bsStreamly word8List :: Int -> IO [Word8] word8List l =- S.toList $+ S.fold Fold.toList $ S.take l $- S.map fromIntegral $- S.map (\x -> abs x `mod` 256) $ S.repeatM (randomIO :: IO Int)+ fmap fromIntegral $+ fmap (\x -> abs x `mod` 256) $ S.repeatM (randomIO :: IO Int) propFoldTestStrict :: [Word8] -> Spec propFoldTestStrict bl =@@ -62,7 +74,7 @@ prop ("Strict: toList . unfold read . pack = id" ++ " -- Size: " ++ show (length bl) ++ " bytes") $ do- bl' <- S.toList (S.unfold Strict.read (BS.pack bl))+ bl' <- S.fold Fold.toList (S.unfold Strict.reader (BS.pack bl)) bl' `shouldBe` bl propUnfoldTestLazy :: [Word8] -> Spec@@ -70,20 +82,20 @@ prop ("Lazy: toList . unfold read . pack = id" ++ " -- Size: " ++ show (length bl) ++ " bytes") $ do- bl' <- S.toList (S.unfold Lazy.read (BSL.pack bl))+ bl' <- S.fold Fold.toList (S.unfold Lazy.reader (BSL.pack bl)) bl' `shouldBe` bl propFromChunks :: Spec propFromChunks = prop "Lazy.fromChunks = BSL.fromChunks" $ \aL -> do- x1 <- Lazy.fromChunks $ S.map Strict.toArray $ S.fromList aL+ x1 <- Lazy.fromChunks $ fmap Strict.toArray $ S.fromList aL let x2 = BSL.fromChunks aL x1 `shouldBe` x2 propFromChunksIO :: Spec propFromChunksIO = prop "Lazy.fromChunks = BSL.fromChunks" $ \aL -> do- x1 <- Lazy.fromChunksIO $ S.map Strict.toArray $ S.fromList aL+ x1 <- Lazy.fromChunksIO $ fmap Strict.toArray $ S.fromList aL let x2 = BSL.fromChunks aL x1 `shouldBe` x2 @@ -94,11 +106,28 @@ S.fromList (Strict.toArray (BS.singleton h) : undefined) return $ BSL.head lbs +checkPinnedNature :: IO ()+checkPinnedNature = do+ (arr :: Array Word8) <-+ Array.fromStream (S.fromList (take 1000 (cycle [0..255])))+ performMajorGC+ threadDelay 50000+ threadDelay 50000+ performMajorGC+ threadDelay 50000+ threadDelay 50000+ (_ :: Array Word8) <-+ Array.fromStream (S.fromList (take 10000 (cycle [0..255])))+ let bs = Strict.fromArray arr+ lst1 = BS.unpack bs+ lst2 = Array.toList arr+ when (lst1 /= lst2) $ error "Pinned nature isn't ensured" main :: IO () main = hspec $ do describe "Array tests" $ do+ it "Pinned in nature" $ checkPinnedNature it "Strict fromArray" $ mapM_ (flip withSystemTempFile checkFileContent . show)@@ -106,12 +135,12 @@ prop "Strict Identity" $ \bs -> bs `shouldBe` Strict.fromArray (Strict.toArray bs) prop "Strict Identity (with offset)" $ \bs ->- let bs' = BS.drop 5 bs- in bs' `shouldBe` Strict.fromArray (Strict.toArray bs')+ let bs1 = BS.drop 5 bs+ in bs1 `shouldBe` Strict.fromArray (Strict.toArray bs1) prop "toArray never produces negative length" $ \bs -> -- 'BS.drop 5' to trigger non-zero offset- let (Array _ sPtr ePtr) = Strict.toArray (BS.drop 5 bs)- in (ePtr `minusPtr` sPtr) >= 0 `shouldBe` True+ let (Array _ startI endI) = Strict.toArray (BS.drop 5 bs)+ in (endI - startI) >= 0 `shouldBe` True prop "Lazy Identity" $ \bs -> do bs2 <- Lazy.fromChunks . Lazy.toChunks $ bs bs `shouldBe` bs2