packages feed

streamly-bytestring 0.1.4 → 0.2.3

raw patch · 7 files changed

Files

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