on master help sign in
Add File IO support
commit38c5c8872c6f084a32f7bc350e9e30adf882d8fe authorHarendra Kumar <harendra.kumar@gmail.com> dateMon 13 May 2019, 02:05 (7 years ago) parentc8955fe
16 files changed+1083−211
Changelog.md+4−3 benchmark/Array.hs+8−8 benchmark/ArrayOps.hs+6−6 benchmark/FileIO.hs+243 benchmark/NanoBenchmarks.hs+33−9 benchmark/StreamDOps.hs+1−1 benchmark/StreamKOps.hs+2−2 src/Streamly/Array.hs+107−135 src/Streamly/FileSystem/File.hs+432 src/Streamly/Fold.hs+1−2 src/Streamly/Prelude.hs+4−4 src/Streamly/Streams/StreamD.hs+1−2 src/Streamly/String.hs+111−4 src/Streamly/Time.hs+56−5 streamly.cabal+62−18 test/Arrays.hs+12−12
Changelog.md+4−3
@@ -7,13 +7,14 @@
77
88### Enhancements
99
10−* MAJOR: Add Streamly.Array module to support efficient fixed size immutable
10+* MAJOR: Add `Streamly.Mem.Array` module to support efficient
1111 arrays for buffering and IO. See the haddock docs for the module.
12−* MAJOR: Add Streamly.Fold module to support composable folds (stream
12+* MAJOR: Add `Streamly.Fold` module to support composable folds (stream
1313 consumers). This brings in stream splitting, grouping, partitioning,
1414 unzipping and nesting features without breaking streaming. See the haddock
1515 docs for the module.
16−* Add the following to "Streamly.Prelude":
16+* MAJOR: Add `Streamly.FileSystem.File` module to support file IO.
17+* Add the following to `Streamly.Prelude`:
1718 * `foldrS` to fold a stream to a stream
1819 * `foldrT` to fold a stream to an arbitrary transformer monad
1920 * `reverse'` a several times faster reverse using Storable instance
benchmark/Array.hs+8−8
@@ -65,13 +65,13 @@ main =
6565 defaultMain
6666 [ bgroup "array"
6767 [ bgroup "generation"
68− [ benchIOSrc "fromStreamN . intFromTo" Ops.sourceIntFromTo
69− , benchIOSrc "fromStream . intFromTo" Ops.sourceIntFromToFromStream
68+ [ benchIOSrc "writeN . intFromTo" Ops.sourceIntFromTo
69+ , benchIOSrc "write . intFromTo" Ops.sourceIntFromToFromStream
7070 , benchIOSrc "fromList . intFromTo" Ops.sourceIntFromToFromList
71− , benchIOSrc "fromStreamN . unfoldr" Ops.sourceUnfoldr
72− , benchIOSrc "fromStreamN . fromList" Ops.sourceFromList
73− , benchPureSrc "fromStreamN . IsList.fromList" Ops.sourceIsList
74− , benchPureSrc "fromStreamN . IsString.fromString" Ops.sourceIsString
71+ , benchIOSrc "writeN . unfoldr" Ops.sourceUnfoldr
72+ , benchIOSrc "writeN . fromList" Ops.sourceFromList
73+ , benchPureSrc "writeN . IsList.fromList" Ops.sourceIsList
74+ , benchPureSrc "writeN . IsString.fromString" Ops.sourceIsString
7575 , mkString `deepseq` (bench "read" $ nf Ops.readInstance mkString)
7676 , benchPureSink "show" Ops.showInstance
7777 ]
@@ -87,8 +87,8 @@ main =
8787 , benchPureSink "min" Ops.ordInstanceMin
8888 , benchPureSink "IsList.toList" GHC.toList
8989 , benchIOSink "foldl'" Ops.pureFoldl'
90− , benchIOSink "toStream" (S.drain . A.toStream)
91− , benchIOSink "toStreamRev" (S.drain . A.toStreamRev)
90+ , benchIOSink "read" (S.drain . A.read)
91+ , benchIOSink "readRev" (S.drain . A.readRev)
9292#ifdef DEVBUILD
9393 , benchPureSink "foldable/foldl'" Ops.foldableFoldl'
9494 , benchPureSink "foldable/sum" Ops.foldableSum
benchmark/ArrayOps.hs+6−6
@@ -49,7 +49,7 @@ type Stream = A.Array
4949
5050{-# INLINE sourceUnfoldr #-}
5151sourceUnfoldr :: MonadIO m => Int -> m (Stream Int)
52−sourceUnfoldr n = A.fromStreamN value $ S.unfoldr step n
52+sourceUnfoldr n = A.writeN value $ S.unfoldr step n
5353 where
5454 step cnt =
5555 if cnt > n + value
@@ -58,18 +58,18 @@ sourceUnfoldr n = A.fromStreamN value $ S.unfoldr step n
5858
5959{-# INLINE sourceIntFromTo #-}
6060sourceIntFromTo :: MonadIO m => Int -> m (Stream Int)
61−sourceIntFromTo n = A.fromStreamN value $ S.enumerateFromTo n (n + value)
61+sourceIntFromTo n = A.writeN value $ S.enumerateFromTo n (n + value)
6262
6363{-# INLINE sourceIntFromToFromStream #-}
6464sourceIntFromToFromStream :: MonadIO m => Int -> m (Stream Int)
65−sourceIntFromToFromStream n = A.fromStream $ S.enumerateFromTo n (n + value)
65+sourceIntFromToFromStream n = A.write $ S.enumerateFromTo n (n + value)
6666
6767sourceIntFromToFromList :: MonadIO m => Int -> m (Stream Int)
6868sourceIntFromToFromList n = P.return $ A.fromList $ [n..n + value]
6969
7070{-# INLINE sourceFromList #-}
7171sourceFromList :: MonadIO m => Int -> m (Stream Int)
72−sourceFromList n = A.fromStreamN value $ S.fromList [n..n+value]
72+sourceFromList n = A.writeN value $ S.fromList [n..n+value]
7373
7474{-# INLINE sourceIsList #-}
7575sourceIsList :: Int -> Stream Int
@@ -280,7 +280,7 @@ onArray
280280 :: MonadIO m => (S.SerialT m Int -> S.SerialT m Int)
281281 -> Stream Int
282282 -> m (Stream Int)
283−onArray f arr = A.fromStreamN value $ f $ A.toStream arr
283+onArray f arr = A.writeN value $ f $ A.read arr
284284
285285scanl' n = composeN n $ onArray $ S.scanl' (+) 0
286286scanl1' n = composeN n $ onArray $ S.scanl1' (+)
@@ -511,7 +511,7 @@ readInstance str =
511511
512512{-# INLINE pureFoldl' #-}
513513pureFoldl' :: MonadIO m => Stream Int -> m Int
514−pureFoldl' = S.foldl' (+) 0 . A.toStream
514+pureFoldl' = S.foldl' (+) 0 . A.read
515515
516516#ifdef DEVBUILD
517517{-# INLINE foldableFoldl' #-}
benchmark/FileIO.hsnew file+243
@@ -0,0 +1,243 @@
1+-- |
2+-- Module : Main
3+-- Copyright : (c) 2019 Harendra Kumar
4+--
5+-- License : BSD3
6+-- Maintainer : harendra.kumar@gmail.com
7+
8+{-# LANGUAGE CPP #-}
9+
10+import Control.DeepSeq (NFData)
11+import Data.Functor.Identity (runIdentity)
12+import System.IO (openFile, IOMode(..), Handle, hClose)
13+import System.Process.Typed (shell, runProcess_)
14+
15+import Data.IORef
16+import Gauge
17+
18+import qualified Streamly.Array as A
19+import qualified Streamly.FileSystem.File as File
20+import qualified Streamly.Prelude as S
21+
22+#ifdef DEVBUILD
23+import Data.Char (ord, chr)
24+import System.IO (hSeek, SeekMode(..))
25+import qualified Streamly.Fold as FL
26+#endif
27+
28+-- Input and output file handles
29+data Handles = Handles Handle Handle
30+
31+scratchDir :: String
32+scratchDir = "benchmark/scratch/"
33+
34+infile :: String
35+infile = scratchDir ++ "in-100MB.txt"
36+
37+outfile :: String
38+outfile = scratchDir ++ "out.txt"
39+
40+blockSize, blockCount :: Int
41+blockSize = 32768
42+blockCount = 3200
43+
44+#ifdef DEVBUILD
45+fileSize :: Int
46+fileSize = blockSize * blockCount
47+
48+foreign import ccall unsafe "u_iswspace"
49+ iswspace :: Int -> Int
50+
51+-- Code copied from base/Data.Char to INLINE it
52+{-# INLINE isSpace #-}
53+isSpace :: Char -> Bool
54+-- isSpace includes non-breaking space
55+-- The magic 0x377 isn't really that magical. As of 2014, all the codepoints
56+-- at or below 0x377 have been assigned, so we shouldn't have to worry about
57+-- any new spaces appearing below there. It would probably be best to
58+-- use branchless ||, but currently the eqLit transformation will undo that,
59+-- so we'll do it like this until there's a way around that.
60+isSpace c
61+ | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
62+ | otherwise = iswspace (ord c) /= 0
63+ where
64+ uc = fromIntegral (ord c) :: Word
65+#endif
66+
67+main :: IO ()
68+main = do
69+ -- XXX will this work on windows/msys?
70+ let cmd = "mkdir -p " ++ scratchDir
71+ ++ "; test -e " ++ infile
72+ ++ " || { echo \"creating input file " ++ infile
73+ ++ "\" && dd if=/dev/random of=" ++ infile
74+ ++ " bs=" ++ show blockSize
75+ ++ " count=" ++ show blockCount
76+ ++ ";}"
77+
78+ runProcess_ (shell cmd)
79+ inHandle <- openFile infile ReadMode
80+ outHandle <- openFile outfile WriteMode
81+ href <- newIORef $ Handles inHandle outHandle
82+
83+-- This is a 500MB text file for text processing benchmarks. We cannot have it
84+-- in the repo, therefore we use it locally with DEVBUILD conditional (enabled
85+-- by "dev" cabal flag).
86+#ifdef DEVBUILD
87+ inText <- openFile "benchmark/text-processing/gutenberg-500.txt" ReadMode
88+#endif
89+
90+ defaultMain
91+ [ bgroup "readArray"
92+ [ mkBench "last" href $ do
93+ Handles inh _ <- readIORef href
94+ let s = File.readArrays inh
95+ larr <- S.last s
96+ return $ case larr of
97+ Nothing -> Nothing
98+ Just arr -> A.readIndex arr (A.length arr - 1)
99+ -- Note: this cannot be fairly compared with GNU wc -c or wc -m as
100+ -- wc uses lseek to just determine the file size rather than reading
101+ -- and counting characters.
102+ , mkBench "length (bytecount)" href $ do
103+ Handles inh _ <- readIORef href
104+ let s = File.readArrays inh
105+ S.sum (S.map A.length s)
106+ , mkBench "sum" href $ do
107+ let foldlArr' f z = runIdentity . S.foldl' f z . A.read
108+ Handles inh _ <- readIORef href
109+ let s = File.readArrays inh
110+ S.foldl' (\acc arr -> acc + foldlArr' (+) 0 arr) 0 s
111+ ]
112+ , bgroup "readStream"
113+ [ mkBench "last" href $ do
114+ Handles inh _ <- readIORef href
115+ S.last $ File.read inh
116+ , mkBench "length (bytecount)" href $ do
117+ Handles inh _ <- readIORef href
118+ S.length $ File.read inh
119+ , mkBench "sum" href $ do
120+ Handles inh _ <- readIORef href
121+ S.sum $ File.read inh
122+ ]
123+ , bgroup "copyArray"
124+ [ mkBench "copy" href $ do
125+ Handles inh outh <- readIORef href
126+ let s = File.readArrays inh
127+ File.writeArrays outh s
128+ ]
129+#ifdef DEVBUILD
130+ -- This takes a little longer therefore put under the dev conditional
131+ , bgroup "copyStream"
132+ [ mkBench "fromToHandle" href $ do
133+ Handles inh outh <- readIORef href
134+ File.write outh (File.read inh)
135+ ]
136+ , bgroup "grouping"
137+ [ mkBench "groupsOf 1 (toArray)" href $ do
138+ Handles inh _ <- readIORef href
139+ S.length $ FL.groupsOf fileSize (A.toArrayN fileSize)
140+ (File.read inh)
141+
142+ , mkBench "groupsOf 1" href $ do
143+ Handles inh _ <- readIORef href
144+ S.length $ FL.groupsOf 1 FL.drain (File.read inh)
145+ , mkBench "groupsOf 10" href $ do
146+ Handles inh _ <- readIORef href
147+ S.length $ FL.groupsOf 10 FL.drain (File.read inh)
148+ , mkBench "groupsOf 1000" href $ do
149+ Handles inh _ <- readIORef href
150+ S.length $ FL.groupsOf 1000 FL.drain (File.read inh)
151+ ]
152+
153+ , let lf = fromIntegral (ord '\n')
154+ lfarr = A.fromList [lf]
155+ isSp = isSpace . chr . fromIntegral
156+ toarr = A.fromList . map (fromIntegral . ord)
157+ in bgroup "splitting"
158+ [ bgroup "predicate"
159+ [ mkBenchText "splitBy \\n (line count)" inText $ do
160+ (S.length $ FL.splitBy (== lf) FL.drain
161+ $ File.read inText) >>= print
162+ , mkBenchText "splitSuffixBy \\n (line count)" inText $ do
163+ (S.length $ FL.splitSuffixBy (== lf) FL.drain
164+ $ File.read inText) >>= print
165+ , mkBenchText "wordsBy isSpace (word count)" inText $ do
166+ (S.length $ FL.wordsBy isSp FL.drain
167+ $ File.read inText) >>= print
168+ ]
169+
170+ , bgroup "empty-pattern"
171+ [ mkBenchText "splitOn \"\"" inText $ do
172+ (S.length $ FL.splitOn (A.fromList []) FL.drain
173+ $ File.read inText) >>= print
174+ , mkBenchText "splitSuffixOn \"\"" inText $ do
175+ (S.length $ FL.splitSuffixOn (A.fromList []) FL.drain
176+ $ File.read inText) >>= print
177+ ]
178+ , bgroup "short-pattern"
179+ [ mkBenchText "splitOn \\n (line count)" inText $ do
180+ (S.length $ FL.splitOn lfarr FL.drain
181+ $ File.read inText) >>= print
182+ , mkBenchText "splitSuffixOn \\n (line count)" inText $ do
183+ (S.length $ FL.splitSuffixOn lfarr FL.drain
184+ $ File.read inText) >>= print
185+ , mkBenchText "splitOn a" inText $ do
186+ (S.length $ FL.splitOn (toarr "a") FL.drain
187+ $ File.read inText) >>= print
188+ , mkBenchText "splitOn \\r\\n" inText $ do
189+ (S.length $ FL.splitOn (toarr "\r\n") FL.drain
190+ $ File.read inText) >>= print
191+ , mkBenchText "splitSuffixOn \\r\\n)" inText $ do
192+ (S.length $ FL.splitSuffixOn (toarr "\r\n") FL.drain
193+ $ File.read inText) >>= print
194+ , mkBenchText "splitOn aa" inText $ do
195+ (S.length $ FL.splitOn (toarr "aa") FL.drain
196+ $ File.read inText) >>= print
197+ , mkBenchText "splitOn aaaa" inText $ do
198+ (S.length $ FL.splitOn (toarr "aaaa") FL.drain
199+ $ File.read inText) >>= print
200+ , mkBenchText "splitOn abcdefgh" inText $ do
201+ (S.length $ FL.splitOn (toarr "abcdefgh") FL.drain
202+ $ File.read inText) >>= print
203+ ]
204+ , bgroup "long-pattern"
205+ [ mkBenchText "splitOn abcdefghi" inText $ do
206+ (S.length $ FL.splitOn (toarr "abcdefghi") FL.drain
207+ $ File.read inText) >>= print
208+ , mkBenchText "splitOn catcatcatcatcat" inText $ do
209+ (S.length $ FL.splitOn (toarr "catcatcatcatcat") FL.drain
210+ $ File.read inText) >>= print
211+ , mkBenchText "splitOn abc...xyz" inText $ do
212+ (S.length $ FL.splitOn
213+ (toarr "abcdefghijklmnopqrstuvwxyz")
214+ FL.drain
215+ $ File.read inText) >>= print
216+ , mkBenchText "splitSuffixOn abc...xyz" inText $ do
217+ (S.length $ FL.splitSuffixOn
218+ (toarr "abcdefghijklmnopqrstuvwxyz")
219+ FL.drain
220+ $ File.read inText) >>= print
221+ ]
222+ ]
223+#endif
224+ ]
225+
226+ where
227+
228+ mkBench :: NFData b => String -> IORef Handles -> IO b -> Benchmark
229+ mkBench name ref action =
230+ bench name $ perRunEnv (do
231+ (Handles inh outh) <- readIORef ref
232+ hClose inh
233+ hClose outh
234+ inHandle <- openFile infile ReadMode
235+ outHandle <- openFile outfile WriteMode
236+ writeIORef ref (Handles inHandle outHandle)
237+ )
238+ (\_ -> action)
239+
240+#ifdef DEVBUILD
241+ mkBenchText name h action =
242+ bench name $ perRunEnv (hSeek h AbsoluteSeek 0) (\_ -> action)
243+#endif
benchmark/NanoBenchmarks.hs+33−9
@@ -5,16 +5,29 @@
55
66{-# LANGUAGE FlexibleContexts #-}
77{-# LANGUAGE ScopedTypeVariables #-}
8+
9+import Streamly (SerialT)
810import Streamly.SVar (MonadAsync)
9−import qualified Streamly.Streams.StreamK as S
11+
12+import qualified Streamly.Array as A
13+import qualified Streamly.FileSystem.File as File
14+import qualified Streamly.Fold as FL
15+import qualified Streamly.Prelude as S
16+import qualified Streamly.Streams.StreamK as K
17+
18+import Data.Char (ord)
1019import Gauge
20+import System.IO (hSeek, SeekMode(..), openFile, IOMode(..))
1121import System.Random
1222
1323maxValue :: Int
1424maxValue = 100000
1525
26+-- type Stream = K.Stream
27+type Stream = SerialT
28+
1629{-# INLINE sourceUnfoldrM #-}
17−sourceUnfoldrM :: MonadAsync m => S.Stream m Int
30+sourceUnfoldrM :: MonadAsync m => Stream m Int
1831sourceUnfoldrM = S.unfoldrM step 0
1932 where
2033 step cnt =
@@ -23,7 +36,7 @@ sourceUnfoldrM = S.unfoldrM step 0
2336 else return (Just (cnt, cnt + 1))
2437
2538{-# INLINE sourceUnfoldrMN #-}
26−sourceUnfoldrMN :: MonadAsync m => Int -> S.Stream m Int
39+sourceUnfoldrMN :: MonadAsync m => Int -> Stream m Int
2740sourceUnfoldrMN n = S.unfoldrM step n
2841 where
2942 step cnt =
@@ -32,7 +45,7 @@ sourceUnfoldrMN n = S.unfoldrM step n
3245 else return (Just (cnt, cnt + 1))
3346
3447{-# INLINE sourceUnfoldr #-}
35−sourceUnfoldr :: Monad m => Int -> S.Stream m Int
48+sourceUnfoldr :: Monad m => Int -> Stream m Int
3649sourceUnfoldr n = S.unfoldr step n
3750 where
3851 step cnt =
@@ -44,14 +57,14 @@ sourceUnfoldr n = S.unfoldr step n
4457-- take-drop composition
4558-------------------------------------------------------------------------------
4659
47−takeAllDropOne :: Monad m => S.Stream m Int -> S.Stream m Int
60+takeAllDropOne :: Monad m => Stream m Int -> Stream m Int
4861takeAllDropOne = S.drop 1 . S.take maxValue
4962
5063-- Requires -fspec-constr-recursive=5 for better fused code
5164-- The number depends on how many times we compose it
5265
5366{-# INLINE takeDrop #-}
54−takeDrop :: Monad m => S.Stream m Int -> m ()
67+takeDrop :: Monad m => Stream m Int -> m ()
5568takeDrop = S.drain .
5669 takeAllDropOne . takeAllDropOne . takeAllDropOne . takeAllDropOne
5770
@@ -59,14 +72,14 @@ takeDrop = S.drain .
5972-- dropWhileFalse composition
6073-------------------------------------------------------------------------------
6174
62−dropWhileFalse :: Monad m => S.Stream m Int -> S.Stream m Int
75+dropWhileFalse :: Monad m => Stream m Int -> Stream m Int
6376dropWhileFalse = S.dropWhile (> maxValue)
6477
6578-- Requires -fspec-constr-recursive=5 for better fused code
6679-- The number depends on how many times we compose it
6780
6881{-# INLINE dropWhileFalseX4 #-}
69−dropWhileFalseX4 :: Monad m => S.Stream m Int -> m ()
82+dropWhileFalseX4 :: Monad m => Stream m Int -> m ()
7083dropWhileFalseX4 = S.drain
7184 . dropWhileFalse . dropWhileFalse . dropWhileFalse . dropWhileFalse
7285
@@ -77,7 +90,7 @@ dropWhileFalseX4 = S.drain
7790{-# INLINE iterateSource #-}
7891iterateSource
7992 :: MonadAsync m
80− => (S.Stream m Int -> S.Stream m Int) -> Int -> Int -> S.Stream m Int
93+ => (Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int
8194iterateSource g i n = f i (sourceUnfoldrMN n)
8295 where
8396 f (0 :: Int) m = g m
@@ -94,3 +107,14 @@ main = do
94107 nfIO $ dropWhileFalseX4 sourceUnfoldrM]
95108 defaultMain [bench "iterate-mapM" $
96109 nfIO $ S.drain $ iterateSource (S.mapM return) 100000 10]
110+
111+ inText <- openFile "benchmark/text-processing/gutenberg-500.txt" ReadMode
112+ defaultMain [mkBenchText "splitOn abc...xyz" inText $ do
113+ (S.length $ FL.splitOn (A.fromList $ map (fromIntegral . ord)
114+ "abcdefghijklmnopqrstuvwxyz") FL.drain
115+ $ File.read inText) >>= print
116+ ]
117+ where
118+
119+ mkBenchText name h action =
120+ bench name $ perRunEnv (hSeek h AbsoluteSeek 0) (\_ -> action)
benchmark/StreamDOps.hs+1−1
@@ -75,7 +75,7 @@ source = sourceUnfoldrM
7575
7676{-# INLINE runStream #-}
7777runStream :: Monad m => Stream m a -> m ()
78−runStream = S.runStream
78+runStream = S.drain
7979
8080{-# INLINE mapM_ #-}
8181mapM_ :: Monad m => Stream m a -> m ()
benchmark/StreamKOps.hs+2−2
@@ -114,7 +114,7 @@ source = sourceUnfoldrM
114114
115115{-# INLINE runStream #-}
116116runStream :: Monad m => Stream m a -> m ()
117−runStream = S.runStream
117+runStream = S.drain
118118
119119{-# INLINE mapM_ #-}
120120mapM_ :: Monad m => Stream m a -> m ()
@@ -131,7 +131,7 @@ uncons s = do
131131init :: (Monad m, S.IsStream t) => t m a -> m ()
132132init s = do
133133 t <- S.init s
134− P.mapM_ S.runStream t
134+ P.mapM_ S.drain t
135135
136136{-# INLINE tail #-}
137137tail :: (Monad m, S.IsStream t) => t m a -> m ()
src/Streamly/Array.hs+107−135
@@ -84,14 +84,15 @@ module Streamly.Array
8484
8585 -- * Construction
8686 -- | When performance matters, the fastest way to generate an array is
87− -- 'fromStreamN'. For regular use, 'IsList' and 'IsString' instances can be
87+ -- 'writeN'. For regular use, 'IsList' and 'IsString' instances can be
8888 -- used to conveniently construct arrays from literal values.
8989 -- 'OverloadedLists' extension or 'fromList' can be used to construct an
9090 -- array from a list literal. Similarly, 'OverloadedStrings' extension or
9191 -- 'fromList' can be used to construct an array from a string literal.
9292
93− , fromStreamN
94− , fromStream
93+ -- , newArray
94+ , writeN
95+ , write
9596 , fromListN
9697 , fromList
9798
@@ -107,8 +108,8 @@ module Streamly.Array
107108 -- 'GHC.Exts.toList' from "GHC.Exts" can be used to convert an array to a
108109 -- list.
109110
110− , toStream
111− , toStreamRev
111+ , read
112+ , readRev
112113 , toList
113114 , flattenArrays
114115 -- , flattenArraysRev
@@ -116,53 +117,55 @@ module Streamly.Array
116117
117118 -- * Random Access
118119 , length
119− , (!!)
120−
121− -- * IO: Single Array
122− -- , fromHandleUpto
123− -- , fromHandleN
124− -- , fromHandlePosUpto
125− -- , fromHandlePosN
126− , toHandle
127−
128− -- * IO: Stream of Arrays
129− -- , fromHandleArraysUpto
130− -- , fromHandleArraysOf
131− , fromHandleArrays
120+ -- , (!!)
121+
122+ , readIndex
123+ {-
124+ , readSlice
125+ , readSliceRev
126+
127+ , writeIndex
128+ , writeSlice
129+ , writeSliceRev
130+ -}
132131 )
133132where
134133
135134import Control.Monad.IO.Class (MonadIO(..))
136−import Data.Word (Word8)
137135import Foreign.ForeignPtr (withForeignPtr)
138−import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
139136import Foreign.Ptr (minusPtr, plusPtr, castPtr)
140137import Foreign.Storable (Storable(..))
141−import System.IO (Handle, hGetBufSome, hPutBuf)
142−import Prelude hiding (length, null, last, map, (!!))
143−
144−import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
138+import Prelude hiding (length, null, last, map, (!!), read)
145139
140+import Streamly.Array.Types hiding (flattenArrays, newArray)
146141import Streamly.Streams.Serial (SerialT)
147−import Streamly.Streams.StreamK.Type (IsStream, mkStream)
148−import Streamly.Array.Types hiding (flattenArrays)
149−import qualified Streamly.Array.Types as A
142+import Streamly.Streams.StreamK.Type (IsStream)
150143
151−import qualified Streamly.Streams.StreamD as D
144+
145+import qualified Streamly.Array.Types as A
152146import qualified Streamly.Prelude as S
147+import qualified Streamly.Streams.StreamD as D
153148
154149-------------------------------------------------------------------------------
155150-- Construction
156151-------------------------------------------------------------------------------
157152
153+{-
154+-- | Create a new uninitialized array of given length.
155+--
156+-- @since 0.7.0
157+newArray :: (MonadIO m, Storable a) => Int -> m (Array a)
158+newArray len = undefined
159+-}
160+
158161-- | Create an 'Array' from the first N elements of a stream. The array is
159162-- allocated to size N, if the stream terminates before N elements then the
160163-- array may hold less than N elements.
161164--
162165-- @since 0.7.0
163−{-# INLINE fromStreamN #-}
164−fromStreamN :: (MonadIO m, Storable a) => Int -> SerialT m a -> m (Array a)
165−fromStreamN n m = fromStreamDN n $ D.toStreamD m
166+{-# INLINE writeN #-}
167+writeN :: (MonadIO m, Storable a) => Int -> SerialT m a -> m (Array a)
168+writeN n m = fromStreamDN n $ D.toStreamD m
166169
167170-------------------------------------------------------------------------------
168171-- Elimination
@@ -171,30 +174,30 @@ fromStreamN n m = fromStreamDN n $ D.toStreamD m
171174-- | Convert an 'Array' into a stream.
172175--
173176-- @since 0.7.0
174−{-# INLINE_EARLY toStream #-}
175−toStream :: (Monad m, IsStream t, Storable a) => Array a -> t m a
176−toStream = D.fromStreamD . toStreamD
177+{-# INLINE_EARLY read #-}
178+read :: (Monad m, IsStream t, Storable a) => Array a -> t m a
179+read = D.fromStreamD . toStreamD
177180-- XXX add fallback to StreamK rule
178−-- {-# RULES "Streamly.Array.toStream fallback to StreamK" [1]
179−-- forall a. S.toStreamK (toStream a) = K.fromArray a #-}
181+-- {-# RULES "Streamly.Array.read fallback to StreamK" [1]
182+-- forall a. S.readK (read a) = K.fromArray a #-}
180183
181184-- | Convert an 'Array' into a stream in reverse order.
182185--
183186-- @since 0.7.0
184−{-# INLINE_EARLY toStreamRev #-}
185−toStreamRev :: (Monad m, IsStream t, Storable a) => Array a -> t m a
186−toStreamRev = D.fromStreamD . toStreamDRev
187+{-# INLINE_EARLY readRev #-}
188+readRev :: (Monad m, IsStream t, Storable a) => Array a -> t m a
189+readRev = D.fromStreamD . toStreamDRev
187190-- XXX add fallback to StreamK rule
188−-- {-# RULES "Streamly.Array.toStreamRev fallback to StreamK" [1]
189−-- forall a. S.toStreamK (toStreamRev a) = K.revFromArray a #-}
191+-- {-# RULES "Streamly.Array.readRev fallback to StreamK" [1]
192+-- forall a. S.toStreamK (readRev a) = K.revFromArray a #-}
190193
191−{-# INLINE null #-}
192−null :: Storable a => Array a -> Bool
193−null arr = length arr == 0
194+{-# INLINE _null #-}
195+_null :: Storable a => Array a -> Bool
196+_null arr = length arr == 0
194197
195198{-# INLINE _last #-}
196199_last :: forall a. Storable a => Array a -> Maybe a
197−_last arr = arr !! (length arr - 1)
200+_last arr = readIndex arr (length arr - 1)
198201
199202-------------------------------------------------------------------------------
200203-- Random Access
@@ -203,13 +206,62 @@ _last arr = arr !! (length arr - 1)
203206-- | /O(1)/ Lookup the element at the given index.
204207--
205208-- @since 0.7.0
206−{-# INLINE (!!) #-}
207−(!!) :: Storable a => Array a -> Int -> Maybe a
208−arr !! i =
209+{-# INLINE readIndex #-}
210+readIndex :: Storable a => Array a -> Int -> Maybe a
211+readIndex arr i =
209212 if i < 0 || i > length arr - 1
210213 then Nothing
211214 else Just $ unsafeIndex arr i
212215
216+{-
217+-- | @readSlice arr i count@ streams a slice of the array @arr@ starting
218+-- at index @i@ and reading up to @count@ elements in the forward direction
219+-- ending at the index @i + count - 1@.
220+--
221+-- @since 0.7.0
222+{-# INLINE readSlice #-}
223+readSlice :: (IsStream t, Monad m, Storable a)
224+ => Array a -> Int -> Int -> t m a
225+readSlice arr i len = undefined
226+
227+-- | @readSliceRev arr i count@ streams a slice of the array @arr@ starting at
228+-- index @i@ and reading up to @count@ elements in the reverse direction ending
229+-- at the index @i - count + 1@.
230+--
231+-- @since 0.7.0
232+{-# INLINE readSliceRev #-}
233+readSliceRev :: (IsStream t, Monad m, Storable a)
234+ => Array a -> Int -> Int -> t m a
235+readSliceRev arr i len = undefined
236+
237+-- | /O(1)/ Write the given element at the given index in the array.
238+--
239+-- @since 0.7.0
240+{-# INLINE writeIndex #-}
241+writeIndex :: (MonadIO m, Storable a) => Array a -> Int -> a -> m ()
242+writeIndex arr i a = undefined
243+
244+-- | @writeSlice arr i count stream@ writes a stream to the array @arr@
245+-- starting at index @i@ and writing up to @count@ elements in the forward
246+-- direction ending at the index @i + count - 1@.
247+--
248+-- @since 0.7.0
249+{-# INLINE writeSlice #-}
250+writeSlice :: (IsStream t, Monad m, Storable a)
251+ => Array a -> Int -> Int -> t m a -> m ()
252+writeSlice arr i len s = undefined
253+
254+-- | @writeSliceRev arr i count stream@ writes a stream to the array @arr@
255+-- starting at index @i@ and writing up to @count@ elements in the reverse
256+-- direction ending at the index @i - count + 1@.
257+--
258+-- @since 0.7.0
259+{-# INLINE writeSliceRev #-}
260+writeSliceRev :: (IsStream t, Monad m, Storable a)
261+ => Array a -> Int -> Int -> t m a -> m ()
262+writeSliceRev arr i len s = undefined
263+-}
264+
213265-------------------------------------------------------------------------------
214266-- Streams of Arrays
215267-------------------------------------------------------------------------------
@@ -254,106 +306,26 @@ spliceArrays s = do
254306 buffered <- S.foldr S.cons S.nil s
255307 len <- S.sum (S.map length buffered)
256308
257− arr <- liftIO $ newArray len
258− end <- S.foldlM' write (aEnd arr) buffered
309+ arr <- liftIO $ A.newArray len
310+ end <- S.foldlM' writeArr (aEnd arr) buffered
259311 return $ arr {aEnd = end}
260312
261313 where
262314
263− write dst Array{..} =
315+ writeArr dst Array{..} =
264316 liftIO $ withForeignPtr aStart $ \src -> do
265317 let len = aEnd `minusPtr` src
266318 memcpy (castPtr dst) (castPtr src) len
267319 return $ dst `plusPtr` len
268320
269321-- | Create an 'Array' from a stream. This is useful when we want to create a
270−-- single array from a stream of unknown size. 'fromStreamN' is at least twice
322+-- single array from a stream of unknown size. 'writeN' is at least twice
271323-- as efficient when the size is already known.
272324--
273325-- Note that if the input stream is too large memory allocation for the array
274326-- may fail. When the stream size is not known, `arraysOf` followed by
275327-- processing of indvidual arrays in the resulting stream should be preferred.
276328--
277−{-# INLINE fromStream #-}
278−fromStream :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
279−fromStream m = A.fromStreamD $ D.toStreamD m
280−
281−{-
282−-- Move this to FileIO
283−-- XXX we should use overWrite/write
284−-- | Write a stream of arrays to a handle.
285−{-# INLINE toHandleArrays #-}
286−toHandleArrays :: (MonadIO m, Storable a) => Handle -> SerialT m (Array a) -> m ()
287−toHandleArrays h m = S.mapM_ (liftIO . toHandle h) m
288−-}
289−
290−-------------------------------------------------------------------------------
291−-- IO (Input)
292−-------------------------------------------------------------------------------
293−
294−-- | Read a 'ByteArray' from a file handle. If no data is available on the
295−-- handle it blocks until some data becomes available. If data is available
296−-- then it immediately returns that data without blocking. It reads a maximum
297−-- of up to the size requested.
298−{-# INLINABLE fromHandleUpto #-}
299−fromHandleUpto :: Int -> Handle -> IO (Array Word8)
300−fromHandleUpto size h = do
301− ptr <- mallocPlainForeignPtrBytes size
302− -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
303− withForeignPtr ptr $ \p -> do
304− n <- hGetBufSome h p size
305− let v = Array
306− { aStart = ptr
307− , aEnd = p `plusPtr` n
308− , aBound = p `plusPtr` size
309− }
310− -- XXX shrink only if the diff is significant
311− shrinkToFit v
312−
313−-------------------------------------------------------------------------------
314−-- IO (output)
315−-------------------------------------------------------------------------------
316−
317−-- | Write an Array to a file handle.
318−--
319−-- @since 0.7.0
320−{-# INLINABLE toHandle #-}
321−toHandle :: Storable a => Handle -> Array a -> IO ()
322−toHandle _ arr | null arr = return ()
323−toHandle h Array{..} = withForeignPtr aStart $ \p -> hPutBuf h p aLen
324− where
325− aLen =
326− let p = unsafeForeignPtrToPtr aStart
327− in aEnd `minusPtr` p
328−
329−-------------------------------------------------------------------------------
330−-- Stream of Arrays IO
331−-------------------------------------------------------------------------------
332−
333−-- | @fromHandleArraysUpto size h@ reads a stream of arrays from file handle @h@.
334−-- The maximum size of a single array is limited to @size@.
335−-- 'fromHandleArraysUpto' ignores the prevailing 'TextEncoding' and 'NewlineMode'
336−-- on the 'Handle'.
337−{-# INLINABLE fromHandleArraysUpto #-}
338−fromHandleArraysUpto :: (IsStream t, MonadIO m)
339− => Int -> Handle -> t m (Array Word8)
340−fromHandleArraysUpto size h = go
341− where
342− -- XXX use cons/nil instead
343− go = mkStream $ \_ yld sng _ -> do
344− vec <- liftIO $ fromHandleUpto size h
345− if length vec < size
346− then sng vec
347− else yld vec go
348−
349−-- XXX read 'Array a' instead of Word8
350−--
351−-- | @fromHandleArrays h@ reads a stream of arrays from file handle @h@.
352−-- The maximum size of a single array is limited to @defaultChunkSize@.
353−-- 'fromHandleArrays' ignores the prevailing 'TextEncoding' and 'NewlineMode'
354−-- on the 'Handle'.
355−--
356−-- @since 0.7.0
357−{-# INLINE fromHandleArrays #-}
358−fromHandleArrays :: (IsStream t, MonadIO m) => Handle -> t m (Array Word8)
359−fromHandleArrays = fromHandleArraysUpto defaultChunkSize
329+{-# INLINE write #-}
330+write :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
331+write m = A.fromStreamD $ D.toStreamD m
src/Streamly/FileSystem/File.hsnew file+432
@@ -0,0 +1,432 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE MagicHash #-}
4+{-# LANGUAGE RecordWildCards #-}
5+{-# LANGUAGE UnboxedTuples #-}
6+
7+#include "../Streams/inline.hs"
8+
9+-- |
10+-- Module : Streamly.FileSystem.File
11+-- Copyright : (c) 2018 Harendra Kumar
12+--
13+-- License : BSD3
14+-- Maintainer : harendra.kumar@gmail.com
15+-- Stability : experimental
16+-- Portability : GHC
17+--
18+-- Read and write streams and arrays to and from files. File IO APIs are quite
19+-- similar to "Streamly.Array" read write APIs. In that regard, arrays can be
20+-- considered as in-memory files or files can be considered as on-disk arrays.
21+-- IO APIs are divided into two categories, sequential streaming IO APIs and
22+-- random access IO APIs. Control over the file reading and writing behavior
23+-- in terms of buffering, encoding, decoding is in the hands of the
24+-- programmer, the 'TextEncoding', 'NewLineMode', and 'Buffering' options of
25+-- the underlying handle provided by GHC are not needed and ignored.
26+--
27+-- > import qualified Streamly.FileSystem.File as File
28+--
29+
30+module Streamly.FileSystem.File
31+ (
32+ -- * Streaming IO
33+ -- | Stream data to or from a file or device sequentially. When reading,
34+ -- the stream is lazy and generated on-demand as the consumer consumes it.
35+ -- Read IO requests to the IO device are performed in chunks limited to a
36+ -- maximum size of 32KiB, this is referred to as @defaultChunkSize@ in the
37+ -- documentation. One IO request may or may not read the full
38+ -- chunk. If the whole stream is not consumed, it is possible that we may
39+ -- read slightly more from the IO device than what the consumer needed.
40+ -- Unless specified otherwise in the API, writes are collected into chunks
41+ -- of @defaultChunkSize@ before they are written to the IO device.
42+
43+ -- Streaming APIs work for all kind of devices, seekable or non-seekable;
44+ -- including disks, files, memory devices, terminals, pipes, sockets and
45+ -- fifos. While random access APIs work only for files or devices that have
46+ -- random access or seek capability for example disks, memory devices.
47+ -- Devices like terminals, pipes, sockets and fifos do not have random
48+ -- access capability.
49+
50+ -- TODO file path based APIs
51+ -- , readFile
52+ -- , writeFile
53+
54+ -- ** Read File to Stream
55+ read
56+ -- , readUtf8
57+ -- , readLines
58+ -- , readFrames
59+ -- , readByChunks
60+
61+ -- -- * Array Read
62+ -- , readArrayUpto
63+ -- , readArrayOf
64+
65+ -- , readArraysUpto
66+ -- , readArraysOf
67+ , readArrays
68+
69+ -- ** Write File from Stream
70+ , write
71+ -- , writeUtf8
72+ -- , writeUtf8ByLines
73+ -- , writeByFrames
74+ , writeByChunks
75+
76+ -- -- * Array Write
77+ , writeArray
78+ , writeArrays
79+
80+ -- -- * Random Access (Seek)
81+ -- -- | Unlike the streaming APIs listed above, these APIs apply to devices or
82+ -- files that have random access or seek capability. This type of devices
83+ -- include disks, files, memory devices and exclude terminals, pipes,
84+ -- sockets and fifos.
85+ --
86+ -- , readIndex
87+ -- , readSlice
88+ -- , readSliceRev
89+ -- , readAt -- read from a given position to th end of file
90+ -- , readSliceArrayUpto
91+ -- , readSliceArrayOf
92+
93+ -- , writeIndex
94+ -- , writeSlice
95+ -- , writeSliceRev
96+ -- , writeAt -- start writing at the given position
97+ -- , writeSliceArray
98+ )
99+where
100+
101+import Control.Monad.IO.Class (MonadIO(..))
102+import Data.Word (Word8)
103+import Foreign.ForeignPtr (withForeignPtr)
104+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
105+import Foreign.Ptr (minusPtr, plusPtr)
106+import Foreign.Storable (Storable(..))
107+import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
108+import System.IO (Handle, hGetBufSome, hPutBuf)
109+import Prelude hiding (read)
110+
111+import Streamly.Array.Types (Array(..))
112+import Streamly.Streams.Serial (SerialT)
113+import Streamly.Streams.StreamK.Type (IsStream, mkStream)
114+-- import Streamly.Fold (Fold)
115+-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
116+
117+import qualified Streamly.Array as A
118+import qualified Streamly.Array.Types as A hiding (flattenArrays)
119+import qualified Streamly.Prelude as S
120+
121+-------------------------------------------------------------------------------
122+-- References
123+-------------------------------------------------------------------------------
124+--
125+-- The following references may be useful to build an understanding about the
126+-- file API design:
127+--
128+-- http://www.linux-mag.com/id/308/ for blocking/non-blocking IO on linux.
129+-- https://lwn.net/Articles/612483/ Non-blocking buffered file read operations
130+-- https://en.wikipedia.org/wiki/C_file_input/output for C APIs.
131+-- https://docs.oracle.com/javase/tutorial/essential/io/file.html for Java API.
132+-- https://www.w3.org/TR/FileAPI/ for http file API.
133+
134+-------------------------------------------------------------------------------
135+-- Array IO (Input)
136+-------------------------------------------------------------------------------
137+
138+-- | Read a 'ByteArray' from a file handle. If no data is available on the
139+-- handle it blocks until some data becomes available. If data is available
140+-- then it immediately returns that data without blocking. It reads a maximum
141+-- of up to the size requested.
142+{-# INLINABLE readArrayUpto #-}
143+readArrayUpto :: Int -> Handle -> IO (Array Word8)
144+readArrayUpto size h = do
145+ ptr <- mallocPlainForeignPtrBytes size
146+ -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
147+ withForeignPtr ptr $ \p -> do
148+ n <- hGetBufSome h p size
149+ let v = Array
150+ { aStart = ptr
151+ , aEnd = p `plusPtr` n
152+ , aBound = p `plusPtr` size
153+ }
154+ -- XXX shrink only if the diff is significant
155+ A.shrinkToFit v
156+
157+-------------------------------------------------------------------------------
158+-- Array IO (output)
159+-------------------------------------------------------------------------------
160+
161+-- | Write an Array to a file handle.
162+--
163+-- @since 0.7.0
164+{-# INLINABLE writeArray #-}
165+writeArray :: Storable a => Handle -> Array a -> IO ()
166+writeArray _ arr | A.length arr == 0 = return ()
167+writeArray h Array{..} = withForeignPtr aStart $ \p -> hPutBuf h p aLen
168+ where
169+ aLen =
170+ let p = unsafeForeignPtrToPtr aStart
171+ in aEnd `minusPtr` p
172+
173+-------------------------------------------------------------------------------
174+-- Stream of Arrays IO
175+-------------------------------------------------------------------------------
176+
177+-- | @readArraysUpto size h@ reads a stream of arrays from file handle @h@.
178+-- The maximum size of a single array is limited to @size@.
179+-- 'fromHandleArraysUpto' ignores the prevailing 'TextEncoding' and 'NewlineMode'
180+-- on the 'Handle'.
181+{-# INLINABLE readArraysUpto #-}
182+readArraysUpto :: (IsStream t, MonadIO m)
183+ => Int -> Handle -> t m (Array Word8)
184+readArraysUpto size h = go
185+ where
186+ -- XXX use cons/nil instead
187+ go = mkStream $ \_ yld sng _ -> do
188+ arr <- liftIO $ readArrayUpto size h
189+ if A.length arr < size
190+ then sng arr
191+ else yld arr go
192+
193+-- XXX read 'Array a' instead of Word8
194+--
195+-- | @readArrays h@ reads a stream of arrays from file handle @h@.
196+-- The maximum size of a single array is limited to @defaultChunkSize@.
197+-- 'readArrays' ignores the prevailing 'TextEncoding' and 'NewlineMode'
198+-- on the 'Handle'.
199+--
200+-- @since 0.7.0
201+{-# INLINE readArrays #-}
202+readArrays :: (IsStream t, MonadIO m) => Handle -> t m (Array Word8)
203+readArrays = readArraysUpto A.defaultChunkSize
204+
205+-------------------------------------------------------------------------------
206+-- Read File to Stream
207+-------------------------------------------------------------------------------
208+
209+-- TODO for concurrent streams implement readahead IO. We can send multiple
210+-- read requests at the same time. For serial case we can use async IO. We can
211+-- also control the read throughput in mbps or IOPS.
212+
213+{-
214+-- | @readByChunksUpto chunkSize handle@ reads a byte stream from a file
215+-- handle, reads are performed in chunks of up to @chunkSize@. The stream ends
216+-- as soon as EOF is encountered.
217+--
218+{-# INLINE readByChunksUpto #-}
219+readByChunksUpto :: (IsStream t, MonadIO m) => Int -> Handle -> t m Word8
220+readByChunksUpto chunkSize h = A.flattenArrays $ readArraysUpto chunkSize h
221+-}
222+
223+-- TODO
224+-- read :: (IsStream t, MonadIO m, Storable a) => Handle -> t m a
225+--
226+-- > read = 'readByChunks' A.defaultChunkSize
227+-- | Generate a stream of elements of the given type from a file 'Handle'. The
228+-- stream ends when EOF is encountered.
229+--
230+-- @since 0.7.0
231+{-# INLINE read #-}
232+read :: (IsStream t, MonadIO m) => Handle -> t m Word8
233+read = A.flattenArrays . readArrays
234+
235+-------------------------------------------------------------------------------
236+-- Writing
237+-------------------------------------------------------------------------------
238+
239+-- | Write a stream of arrays to a handle.
240+--
241+-- @since 0.7.0
242+{-# INLINE writeArrays #-}
243+writeArrays :: (MonadIO m, Storable a) => Handle -> SerialT m (Array a) -> m ()
244+writeArrays h m = S.mapM_ (liftIO . writeArray h) m
245+
246+-- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.
247+--
248+-- XXX test this
249+-- Note that if you use a chunk size less than 8K (GHC's default buffer
250+-- size) then you are advised to use 'NOBuffering' mode on the 'Handle' in case you
251+-- do not want buffering to occur at GHC level as well. Same thing applies to
252+-- writes as well.
253+
254+-- | Like 'write' but provides control over the write buffer. Output will
255+-- be written to the IO device as soon as we collect the specified number of
256+-- input elements.
257+--
258+-- @since 0.7.0
259+{-# INLINE writeByChunks #-}
260+writeByChunks :: MonadIO m => Int -> Handle -> SerialT m Word8 -> m ()
261+writeByChunks n h m = writeArrays h $ A.arraysOf n m
262+
263+-- > write = 'writeByChunks' A.defaultChunkSize
264+--
265+-- | Write a byte stream to a file handle. Combines the bytes in chunks of size
266+-- up to 'A.defaultChunkSize' before writing. Note that the write behavior
267+-- depends on the 'IOMode' and the current seek position of the handle.
268+--
269+-- @since 0.7.0
270+{-# INLINE write #-}
271+write :: MonadIO m => Handle -> SerialT m Word8 -> m ()
272+write = writeByChunks A.defaultChunkSize
273+
274+{-
275+{-# INLINE write #-}
276+write :: (MonadIO m, Storable a) => Handle -> SerialT m a -> m ()
277+write = toHandleWith A.defaultChunkSize
278+-}
279+
280+-------------------------------------------------------------------------------
281+-- IO with encoding/decoding Unicode characters
282+-------------------------------------------------------------------------------
283+
284+{-
285+-- |
286+-- > readUtf8 = decodeUtf8 . read
287+--
288+-- Read a UTF8 encoded stream of unicode characters from a file handle.
289+--
290+-- @since 0.7.0
291+{-# INLINE readUtf8 #-}
292+readUtf8 :: (IsStream t, MonadIO m) => Handle -> t m Char
293+readUtf8 = decodeUtf8 . read
294+
295+-- |
296+-- > writeUtf8 h s = write h $ encodeUtf8 s
297+--
298+-- Encode a stream of unicode characters to UTF8 and write it to the given file
299+-- handle. Default block buffering applies to the writes.
300+--
301+-- @since 0.7.0
302+{-# INLINE writeUtf8 #-}
303+writeUtf8 :: MonadIO m => Handle -> SerialT m Char -> m ()
304+writeUtf8 h s = write h $ encodeUtf8 s
305+
306+-- | Write a stream of unicode characters after encoding to UTF-8 in chunks
307+-- separated by a linefeed character @'\n'@. If the size of the buffer exceeds
308+-- @defaultChunkSize@ and a linefeed is not yet found, the buffer is written
309+-- anyway. This is similar to writing to a 'Handle' with the 'LineBuffering'
310+-- option.
311+--
312+-- @since 0.7.0
313+{-# INLINE writeUtf8ByLines #-}
314+writeUtf8ByLines :: (IsStream t, MonadIO m) => Handle -> t m Char -> m ()
315+writeUtf8ByLines = undefined
316+
317+-- | Read UTF-8 lines from a file handle and apply the specified fold to each
318+-- line. This is similar to reading a 'Handle' with the 'LineBuffering' option.
319+--
320+-- @since 0.7.0
321+{-# INLINE readLines #-}
322+readLines :: (IsStream t, MonadIO m) => Handle -> Fold m Char b -> t m b
323+readLines h f = foldLines (readUtf8 h) f
324+
325+-------------------------------------------------------------------------------
326+-- Framing on a sequence
327+-------------------------------------------------------------------------------
328+
329+-- | Read a stream from a file handle and split it into frames delimited by
330+-- the specified sequence of elements. The supplied fold is applied on each
331+-- frame.
332+--
333+-- @since 0.7.0
334+{-# INLINE readFrames #-}
335+readFrames :: (IsStream t, MonadIO m, Storable a)
336+ => Array a -> Handle -> Fold m a b -> t m b
337+readFrames = undefined -- foldFrames . read
338+
339+-- | Write a stream to the given file handle buffering up to frames separated
340+-- by the given sequence or up to a maximum of @defaultChunkSize@.
341+--
342+-- @since 0.7.0
343+{-# INLINE writeByFrames #-}
344+writeByFrames :: (IsStream t, MonadIO m, Storable a)
345+ => Array a -> Handle -> t m a -> m ()
346+writeByFrames = undefined
347+
348+-------------------------------------------------------------------------------
349+-- Random Access IO (Seek)
350+-------------------------------------------------------------------------------
351+
352+-- XXX handles could be shared, so we may not want to use the handle state at
353+-- all for these APIs. we can use pread and pwrite instead. On windows we will
354+-- need to use readFile/writeFile with an offset argument.
355+
356+-------------------------------------------------------------------------------
357+
358+-- | Read the element at the given index treating the file as an array.
359+--
360+-- @since 0.7.0
361+{-# INLINE readIndex #-}
362+readIndex :: Storable a => Handle -> Int -> Maybe a
363+readIndex arr i = undefined
364+
365+-- NOTE: To represent a range to read we have chosen (start, size) instead of
366+-- (start, end). This helps in removing the ambiguity of whether "end" is
367+-- included in the range or not.
368+--
369+-- We could avoid specifying the range to be read and instead use "take size"
370+-- on the stream, but it may end up reading more and then consume it partially.
371+
372+-- | @readSliceWith chunkSize handle pos len@ reads up to @len@ bytes
373+-- from @handle@ starting at the offset @pos@ from the beginning of the file.
374+--
375+-- Reads are performed in chunks of size @chunkSize@. For block devices, to
376+-- avoid reading partial blocks @chunkSize@ must align with the block size of
377+-- the underlying device. If the underlying block size is unknown, it is a good
378+-- idea to keep it a multiple 4KiB. This API ensures that the start of each
379+-- chunk is aligned with @chunkSize@ from second chunk onwards.
380+--
381+{-# INLINE readSliceWith #-}
382+readSliceWith :: (IsStream t, MonadIO m, Storable a)
383+ => Int -> Handle -> Int -> Int -> t m a
384+readSliceWith chunkSize h pos len = undefined
385+
386+-- | @readSlice h i count@ streams a slice from the file handle @h@ starting
387+-- at index @i@ and reading up to @count@ elements in the forward direction
388+-- ending at the index @i + count - 1@.
389+--
390+-- @since 0.7.0
391+{-# INLINE readSlice #-}
392+readSlice :: (IsStream t, MonadIO m, Storable a)
393+ => Handle -> Int -> Int -> t m a
394+readSlice = readSliceWith A.defaultChunkSize
395+
396+-- | @readSliceRev h i count@ streams a slice from the file handle @h@ starting
397+-- at index @i@ and reading up to @count@ elements in the reverse direction
398+-- ending at the index @i - count + 1@.
399+--
400+-- @since 0.7.0
401+{-# INLINE readSliceRev #-}
402+readSliceRev :: (IsStream t, MonadIO m, Storable a)
403+ => Handle -> Int -> Int -> t m a
404+readSliceRev h i count = undefined
405+
406+-- | Write the given element at the given index in the file.
407+--
408+-- @since 0.7.0
409+{-# INLINE writeIndex #-}
410+writeIndex :: (MonadIO m, Storable a) => Handle -> Int -> a -> m ()
411+writeIndex h i a = undefined
412+
413+-- | @writeSlice h i count stream@ writes a stream to the file handle @h@
414+-- starting at index @i@ and writing up to @count@ elements in the forward
415+-- direction ending at the index @i + count - 1@.
416+--
417+-- @since 0.7.0
418+{-# INLINE writeSlice #-}
419+writeSlice :: (IsStream t, Monad m, Storable a)
420+ => Handle -> Int -> Int -> t m a -> m ()
421+writeSlice h i len s = undefined
422+
423+-- | @writeSliceRev h i count stream@ writes a stream to the file handle @h@
424+-- starting at index @i@ and writing up to @count@ elements in the reverse
425+-- direction ending at the index @i - count + 1@.
426+--
427+-- @since 0.7.0
428+{-# INLINE writeSliceRev #-}
429+writeSliceRev :: (IsStream t, Monad m, Storable a)
430+ => Handle -> Int -> Int -> t m a -> m ()
431+writeSliceRev arr i len s = undefined
432+-}
src/Streamly/Fold.hs+1−2
@@ -1493,8 +1493,7 @@ wordsOn subseq f m = undefined -- D.fromStreamD $ D.wordsOn f subseq (D.toStream
14931493splitOn'
14941494 :: (IsStream t, MonadAsync m, Storable a, Enum a, Eq a)
14951495 => Array a -> Fold m a b -> t m a -> t m b
1496−splitOn' patt f m = S.intersperseM
1497− (foldl' f (A.toStream patt)) $ splitOn patt f m
1496+splitOn' patt f m = S.intersperseM (foldl' f (A.read patt)) $ splitOn patt f m
14981497
14991498-- | Like 'splitSuffixOn' but keeps the suffix intact in the splits.
15001499--
src/Streamly/Prelude.hs+4−4
@@ -105,9 +105,6 @@ module Streamly.Prelude
105105 , K.fromFoldable
106106 , fromFoldableM
107107
108− -- ** From External Containers
109− , fromHandle
110−
111108 -- * Elimination
112109
113110 -- ** Deconstruction
@@ -213,7 +210,6 @@ module Streamly.Prelude
213210 -- -- | Convert or divert a stream into an output structure, container or
214211 -- sink.
215212 , toList
216− , toHandle
217213
218214 -- ** Partial Folds
219215 -- | Folds that may terminate before evaluating the whole stream. These
@@ -465,6 +461,8 @@ module Streamly.Prelude
465461 , runStream
466462 , runN
467463 , runWhile
464+ , fromHandle
465+ , toHandle
468466 )
469467where
470468
@@ -837,6 +835,7 @@ each = K.fromFoldable
837835-- | Read lines from an IO Handle into a stream of Strings.
838836--
839837-- @since 0.1.0
838+{-# DEPRECATED fromHandle "Please use Streamly.FileIO instead." #-}
840839fromHandle :: (IsStream t, MonadIO m) => IO.Handle -> t m String
841840fromHandle h = go
842841 where
@@ -1530,6 +1529,7 @@ _toListRev = D.toListRev . toStreamD
15301529-- Write a stream of Strings to an IO Handle.
15311530--
15321531-- @since 0.1.0
1532+{-# DEPRECATED toHandle "Please use Streamly.FileIO instead." #-}
15331533toHandle :: MonadIO m => IO.Handle -> SerialT m String -> m ()
15341534toHandle h m = go m
15351535 where
src/Streamly/Streams/StreamD.hs+1−2
@@ -128,7 +128,6 @@ module Streamly.Streams.StreamD
128128 -- ** Flattening nested streams
129129 , concatMapM
130130 , concatMap
131− , flattenArrays
132131
133132 -- ** Grouping
134133 , groupsOf
@@ -243,7 +242,7 @@ import Prelude
243242 (!!), scanl, scanl1, concatMap, replicate, enumFromTo, concat,
244243 reverse)
245244
246−import Streamly.Array.Types (Array(..), flattenArrays)
245+import Streamly.Array.Types (Array(..))
247246import Streamly.Fold.Types (Fold(..))
248247import Streamly.SVar (MonadAsync, defState, adaptState)
249248import Streamly.Sink.Types (Sink(..))
src/Streamly/String.hs+111−4
@@ -7,6 +7,13 @@
77-- Stability : experimental
88-- Portability : GHC
99--
10+-- Strings are simply character streams. Stream of 'Char' are the equivalent of
11+-- Haskell Strings but without any performance issues that are usually
12+-- associated with the 'String' type. We can use the usual stream processing
13+-- routines in these 'Char' streams. The processing of streams is very
14+-- efficient in terms of CPU and space usage. Streamly employs stream fusion
15+-- which makes all the stream operations extremely fast.
16+--
1017-- The 'String' type in this module is just a synonym for the type @List Char@.
1118-- It provides better performance compared to the standard Haskell @String@
1219-- type and can be used almost as a drop-in replacement, especially when used
@@ -14,15 +21,115 @@
1421--
1522-- See "Streamly.List", <src/docs/streamly-vs-lists.md> for more details and
1623-- <src/test/PureStreams.hs> for comprehensive usage examples.
17−--
24+
25+-- TBD write a note on performance comparison with text, bytestrings, lists and
26+-- vector.
1827--
1928module Streamly.String
20− (
21− String
29+ ( String
30+ -- * Encoding and Decoding
31+ -- | Streams of 'Word8' read from a file handle can be decoded into a
32+ -- stream of unicode characters, similalrly before we write unicode text to
33+ -- a file we can use an encoding routine to convert it to a stream of bytes
34+ -- before writing.
35+
36+{-
37+ , encodeUtf8
38+ , decodeUtf8
39+
40+ -- * Unicode aware operations
41+ , toCaseFold
42+ , toLower
43+ , toUpper
44+ , toTitle
45+
46+ -- * Operations on character strings
47+ , strip -- (dropAround isSpace)
48+ , stripStart
49+ , stripEnd
50+ , foldLines
51+ , foldWords
52+ , lines
53+ , words
54+ , unlines
55+ , unwords
56+-}
2257 )
2358where
2459
60+-- import Data.Word (Word8)
61+-- import Streamly (IsStream)
2562import Streamly.List (List)
26−import Prelude hiding (String)
63+import Prelude hiding (String, lines, words, unlines, unwords)
64+-- import Streamly.Fold (Fold)
65+-- import Streamly.Array (Array)
2766
2867type String = List Char
68+
69+-------------------------------------------------------------------------------
70+-- Encoding/Decoding Characters
71+-------------------------------------------------------------------------------
72+
73+-- decodeWith :: TextEncoding -> t m Word8 -> t m Char
74+-- decodeWith = undefined
75+
76+-------------------------------------------------------------------------------
77+-- Encoding/Decoding Unicode Characters
78+-------------------------------------------------------------------------------
79+
80+{-
81+-- | Decode a UTF-8 encoded bytestream to a stream of Unicode characters.
82+decodeUtf8 :: IsStream t => t m Word8 -> t m Char
83+decodeUtf8 = undefined
84+
85+-- | Encode a stream of Unicode characters to a UTF-8 encoded bytestream.
86+encodeUtf8 :: IsStream t => t m Char -> t m Word8
87+encodeUtf8 = undefined
88+
89+-------------------------------------------------------------------------------
90+-- Unicode aware operations on strings
91+-------------------------------------------------------------------------------
92+
93+toCaseFold :: IsStream t => t m Char -> t m Char
94+toCaseFold = undefined
95+
96+toLower :: IsStream t => t m Char -> t m Char
97+toLower = undefined
98+
99+toUpper :: IsStream t => t m Char -> t m Char
100+toUpper = undefined
101+
102+toTitle :: IsStream t => t m Char -> t m Char
103+toTitle = undefined
104+
105+-------------------------------------------------------------------------------
106+-- Utility operations on strings
107+-------------------------------------------------------------------------------
108+
109+strip :: IsStream t => t m Char -> t m Char
110+strip = undefined
111+
112+stripStart :: IsStream t => t m Char -> t m Char
113+stripStart = undefined
114+
115+stripEnd :: IsStream t => t m Char -> t m Char
116+stripEnd = undefined
117+
118+foldLines :: IsStream t => t m Char -> Fold m Char b -> t m b
119+foldLines = undefined
120+
121+foldWords :: IsStream t => t m Char -> Fold m Char b -> t m b
122+foldWords = undefined
123+
124+lines :: IsStream t => t m Char -> t m (Array Char)
125+lines = undefined
126+
127+words :: IsStream t => t m Char -> t m (Array Char)
128+words = undefined
129+
130+unlines :: IsStream t => t m (Array Char) -> t m Char
131+unlines = undefined
132+
133+unwords :: IsStream t => t m (Array Char) -> t m Char
134+unwords = undefined
135+-}
src/Streamly/Time.hs+56−5
@@ -1,3 +1,5 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+
13-- |
24-- Module : Streamly.Time
35-- Copyright : (c) 2017 Harendra Kumar
@@ -10,16 +12,26 @@
1012-- Time utilities for reactive programming.
1113
1214module Streamly.Time
13−{-# DEPRECATED
14− "Please use the \"rate\" combinator instead of the functions in this module"
15− #-}
16− ( periodic
15+ ( intervalsOf
16+
17+ -- * Deprecated
18+ , periodic
1719 , withClock
1820 )
1921where
2022
21−import Control.Monad (when)
2223import Control.Concurrent (threadDelay)
24+import Control.Monad (when)
25+import Control.Monad.IO.Class (MonadIO(liftIO))
26+import Data.Maybe (fromJust, isJust)
27+
28+import Streamly (parallel)
29+import Streamly.SVar (MonadAsync)
30+import Streamly.Streams.StreamK (IsStream(..))
31+import Streamly.Fold (Fold)
32+
33+import qualified Streamly.Fold as FL
34+import qualified Streamly.Prelude as S
2335
2436-- | Run an action forever periodically at the given frequency specified in per
2537-- second (Hz).
@@ -72,3 +84,42 @@ withClock clock freq action = do
7284 lastPeriod = (baseTime - lastAdj) `div` freq
7385 newDelay = max 0 (delay + period - lastPeriod)
7486 return (baseTime, newTick, newDelay)
87+
88+-------------------------------------------------------------------------------
89+-- Stream element unaware: Time based buffering
90+-------------------------------------------------------------------------------
91+
92+-- There are two ways in which time based buffering can be implemented.
93+--
94+-- 1) One is to use an API like groupsOf to group elements by time and then
95+-- flush when the group is complete. This will require checking time on each
96+-- element yield.
97+--
98+-- 2) Use an async thread to write. The producer thread would just queue the
99+-- elements to be written on a queue associated with the writer thread. When
100+-- the write thread is scheduled elements that have been buffered will be
101+-- written based on a buffering policy. This will require the producer to
102+-- synchronize on the queue, send a doorbell when the queue has items in it.
103+-- The queue can be an array which can be written directly to the IO device.
104+--
105+-- We can try both and see which one performs better.
106+
107+-- XXX add this example after fixing the serial stream rate control
108+-- >>> S.toList $ S.take 5 $ intervalsOf 1 FL.sum $ constRate 2 $ S.enumerateFrom 1
109+-- > [3,7,11,15,19]
110+--
111+-- | Group the input stream into windows of @n@ second each and then fold each
112+-- group using the provided fold function.
113+--
114+-- @since 0.7.0
115+{-# INLINE intervalsOf #-}
116+intervalsOf
117+ :: (IsStream t, MonadAsync m)
118+ => Double -> Fold m a b -> t m a -> t m b
119+intervalsOf n f m = FL.grouped (catMaybes f) s
120+ where
121+ s = S.map (\x -> (Just x, False)) m `parallel` S.repeatM timeout
122+ timeout = do
123+ liftIO $ threadDelay (round $ n * 1000000)
124+ return (Nothing, True)
125+ catMaybes = FL.lfilter isJust . FL.lmap fromJust
streamly.cabal+62−18
@@ -4,33 +4,45 @@ synopsis: Beautiful Streaming, Concurrent and Reactive Composition
44description:
55 Streamly, short for streaming concurrently, provides monadic streams, with a
66 simple API, almost identical to standard lists, and an in-built support for
7− concurrency. By using stream-style combinators on stream composition,
8− streams can be generated, merged, chained, mapped, zipped, and consumed
9− concurrently – providing a generalized high level programming framework
10− unifying streaming and concurrency. Controlled concurrency allows even
11− infinite streams to be evaluated concurrently. Concurrency is auto scaled
12− based on feedback from the stream consumer. The programmer does not have to
13− be aware of threads, locking or synchronization to write scalable concurrent
14− programs.
7+ concurrency. Streams can be generated, merged, chained, mapped, zipped, and
8+ consumed concurrently – providing a generalized high level programming
9+ framework unifying streaming and concurrency. Controlled concurrency allows
10+ even infinite streams to be evaluated concurrently. Concurrency is auto
11+ scaled based on feedback from the stream consumer. Programmer does not
12+ have to be aware of threads, locking or synchronization to write scalable
13+ concurrent programs.
1514 .
16− The basic streaming functionality of streamly is equivalent to that provided by
15+ In addition to the basic streaming functionality provided by
1716 streaming libraries like
1817 <https://hackage.haskell.org/package/streaming streaming>,
1918 <https://hackage.haskell.org/package/pipes pipes>, and
20− <https://hackage.haskell.org/package/conduit conduit>.
21− In addition to providing streaming functionality, streamly subsumes the
19+ <https://hackage.haskell.org/package/conduit conduit> streamly provides
20+ advanced stream composition including various ways of appending, merging,
21+ zipping, splitting, grouping, distributing, partitioning and unzipping of
22+ streams with true streaming and with concurrency. Streamly subsumes the
2223 functionality of list transformer libraries like @pipes@ or
2324 <https://hackage.haskell.org/package/list-t list-t> and also the logic
2425 programming library <https://hackage.haskell.org/package/logict logict>. On
25− the concurrency side, it subsumes the functionality of the
26− <https://hackage.haskell.org/package/async async> package. Because it
27− supports streaming with concurrency we can write FRP applications similar in
28− concept to <https://hackage.haskell.org/package/Yampa Yampa> or
26+ the concurrency side, it subsumes the basic concurrency functionality
27+ provided by the <https://hackage.haskell.org/package/async async> package and
28+ provides much more powerful high level concurrent streaming composition.
29+ Because it supports streaming with concurrency we can write FRP applications
30+ similar in concept to <https://hackage.haskell.org/package/Yampa Yampa> or
2931 <https://hackage.haskell.org/package/reflex reflex>.
3032 .
31− For file IO, currently the library provides only one API to stream the lines
32− in the file as Strings. Future versions will provide better streaming file
33− IO options. Streamly interoperates with the popular streaming libraries, see
33+ Streamly focuses on practical engineering with high performance. One can
34+ expect performance competitive to C from well written streamly programs.
35+ High performance streaming eliminates the need for string and text
36+ libraries like
37+ <https://hackage.haskell.org/package/bytestring bytestring>,
38+ <https://hackage.haskell.org/package/text text> and their lazy and strict
39+ flavors, and all the confusion arising from those. The only basic types are
40+ arrays for storage and streams for processing. Strings and text are simply
41+ streams of 'Char' as they should be. Even Haskell lists can be replaced by
42+ pure streams. Arrays provided by streamly have performance at par with or
43+ better than the vector library.
44+ .
45+ Streamly interoperates with popular streaming libraries, see
3446 the interoperation section in "Streamly.Tutorial".
3547 .
3648 Why use streamly?
@@ -183,6 +195,7 @@ library
183195 , Streamly.Time
184196 , Streamly
185197 , Streamly.Fold
198+ , Streamly.FileSystem.File
186199
187200 , Streamly.Tutorial
188201 , Streamly.Internal
@@ -528,6 +541,7 @@ benchmark nested
528541 default-language: Haskell2010
529542 ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
530543 if flag(dev)
544+ cpp-options: -DDEVBUILD
531545 ghc-options: -Wmissed-specialisations
532546 -Wall-missed-specialisations
533547 -fno-ignore-asserts
@@ -546,6 +560,9 @@ benchmark nested
546560 , deepseq >= 1.4.3 && < 1.5
547561 , random >= 1.0 && < 2.0
548562 , gauge >= 0.2.4 && < 0.3
563+ if impl(ghc < 8.0)
564+ build-depends:
565+ transformers >= 0.4 && < 0.6
549566
550567benchmark array
551568 type: exitcode-stdio-1.0
@@ -578,6 +595,33 @@ benchmark array
578595 build-depends:
579596 transformers >= 0.4 && < 0.6
580597
598+benchmark fileio
599+ type: exitcode-stdio-1.0
600+ hs-source-dirs: benchmark
601+ main-is: FileIO.hs
602+ default-language: Haskell2010
603+ ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
604+ if flag(dev)
605+ cpp-options: -DDEVBUILD
606+ ghc-options: -Wmissed-specialisations
607+ -Wall-missed-specialisations
608+ -fno-ignore-asserts
609+ if impl(ghc >= 8.0)
610+ ghc-options: -Wcompat
611+ -Wunrecognised-warning-flags
612+ -Widentities
613+ -Wincomplete-record-updates
614+ -Wincomplete-uni-patterns
615+ -Wredundant-constraints
616+ -Wnoncanonical-monad-instances
617+ -Wnoncanonical-monadfail-instances
618+ build-depends:
619+ streamly
620+ , base >= 4.8 && < 5
621+ , gauge >= 0.2.4 && < 0.3
622+ , typed-process >= 0.2.3 && < 0.3
623+ , deepseq >= 1.4.3 && < 1.5
624+
581625-------------------------------------------------------------------------------
582626-- Internal benchmarks for unexposed modules
583627-------------------------------------------------------------------------------
test/Arrays.hs+12−12
@@ -42,7 +42,7 @@ testLength =
4242 forAll (choose (0, maxArrLen)) $ \len ->
4343 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
4444 monadicIO $ do
45− arr <- A.fromStreamN len
45+ arr <- A.writeN len
4646 $ S.fromList list
4747 assert (A.length arr == len)
4848
@@ -51,10 +51,10 @@ testFromToStreamN =
5151 forAll (choose (0, maxArrLen)) $ \len ->
5252 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
5353 monadicIO $ do
54− arr <- A.fromStreamN len
54+ arr <- A.writeN len
5555 $ S.fromList list
5656 xs <- S.toList
57− $ A.toStream arr
57+ $ A.read arr
5858 assert (xs == list)
5959
6060testToStreamRev :: Property
@@ -62,10 +62,10 @@ testToStreamRev =
6262 forAll (choose (0, maxArrLen)) $ \len ->
6363 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
6464 monadicIO $ do
65− arr <- A.fromStreamN len
65+ arr <- A.writeN len
6666 $ S.fromList list
6767 xs <- S.toList
68− $ A.toStreamRev arr
68+ $ A.readRev arr
6969 assert (xs == reverse list)
7070
7171testArraysOf :: Property
@@ -74,7 +74,7 @@ testArraysOf =
7474 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
7575 monadicIO $ do
7676 xs <- S.toList
77− $ S.concatMap A.toStream
77+ $ S.concatMap A.read
7878 $ A.arraysOf 240
7979 $ S.fromList list
8080 assert (xs == list)
@@ -95,9 +95,9 @@ testFromToStream =
9595 forAll (choose (0, maxArrLen)) $ \len ->
9696 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
9797 monadicIO $ do
98− arr <- A.fromStream $ S.fromList list
98+ arr <- A.write $ S.fromList list
9999 xs <- S.toList
100− $ A.toStream arr
100+ $ A.read arr
101101 assert (xs == list)
102102
103103main :: IO ()
@@ -106,9 +106,9 @@ main = hspec
106106 $ modifyMaxSuccess (const maxTestCount)
107107 $ do
108108 describe "Construction" $ do
109− prop "length . fromStreamN n === n" $ testLength
110− prop "toStream . fromStreamN n === id" $ testFromToStreamN
111− prop "toStreamRev . fromStream === reverse" $ testToStreamRev
109+ prop "length . writeN n === n" $ testLength
110+ prop "read . writeN n === id" $ testFromToStreamN
111+ prop "readRev . write === reverse" $ testToStreamRev
112112 prop "arraysOf concats to original" $ testArraysOf
113113 prop "flattenArrays concats to original" $ testFlattenArrays
114− prop "toStream . fromStream === id" $ testFromToStream
114+ prop "read . write === id" $ testFromToStream