on master help sign in
Add File IO support
38c5c88Harendra Kumar
Changelog.md+4−3
@@ -7,13 +7,14 @@
7
7
8### Enhancements
8### Enhancements
9
9
10−* MAJOR: Add Streamly.Array module to support efficient fixed size immutable
10+* MAJOR: Add `Streamly.Mem.Array` module to support efficient
11 arrays for buffering and IO. See the haddock docs for the module.
11 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
13 consumers). This brings in stream splitting, grouping, partitioning,
13 consumers). This brings in stream splitting, grouping, partitioning,
14 unzipping and nesting features without breaking streaming. See the haddock
14 unzipping and nesting features without breaking streaming. See the haddock
15 docs for the module.
15 docs for the module.
16+* MAJOR: Add `Streamly.FileSystem.File` module to support file IO.
16−* Add the following to "Streamly.Prelude":
17+* Add the following to `Streamly.Prelude`:
17 * `foldrS` to fold a stream to a stream
18 * `foldrS` to fold a stream to a stream
18 * `foldrT` to fold a stream to an arbitrary transformer monad
19 * `foldrT` to fold a stream to an arbitrary transformer monad
19 * `reverse'` a several times faster reverse using Storable instance
20 * `reverse'` a several times faster reverse using Storable instance
benchmark/Array.hs+8−8
@@ -65,13 +65,13 @@ main =
65 defaultMain
65 defaultMain
66 [ bgroup "array"
66 [ bgroup "array"
67 [ bgroup "generation"
67 [ bgroup "generation"
68− [ benchIOSrc "fromStreamN . intFromTo" Ops.sourceIntFromTo
68+ [ benchIOSrc "writeN . intFromTo" Ops.sourceIntFromTo
69− , benchIOSrc "fromStream . intFromTo" Ops.sourceIntFromToFromStream
69+ , benchIOSrc "write . intFromTo" Ops.sourceIntFromToFromStream
70 , benchIOSrc "fromList . intFromTo" Ops.sourceIntFromToFromList
70 , benchIOSrc "fromList . intFromTo" Ops.sourceIntFromToFromList
71− , benchIOSrc "fromStreamN . unfoldr" Ops.sourceUnfoldr
71+ , benchIOSrc "writeN . unfoldr" Ops.sourceUnfoldr
72− , benchIOSrc "fromStreamN . fromList" Ops.sourceFromList
72+ , benchIOSrc "writeN . fromList" Ops.sourceFromList
73− , benchPureSrc "fromStreamN . IsList.fromList" Ops.sourceIsList
73+ , benchPureSrc "writeN . IsList.fromList" Ops.sourceIsList
74− , benchPureSrc "fromStreamN . IsString.fromString" Ops.sourceIsString
74+ , benchPureSrc "writeN . IsString.fromString" Ops.sourceIsString
75 , mkString `deepseq` (bench "read" $ nf Ops.readInstance mkString)
75 , mkString `deepseq` (bench "read" $ nf Ops.readInstance mkString)
76 , benchPureSink "show" Ops.showInstance
76 , benchPureSink "show" Ops.showInstance
77 ]
77 ]
@@ -87,8 +87,8 @@ main =
87 , benchPureSink "min" Ops.ordInstanceMin
87 , benchPureSink "min" Ops.ordInstanceMin
88 , benchPureSink "IsList.toList" GHC.toList
88 , benchPureSink "IsList.toList" GHC.toList
89 , benchIOSink "foldl'" Ops.pureFoldl'
89 , benchIOSink "foldl'" Ops.pureFoldl'
90− , benchIOSink "toStream" (S.drain . A.toStream)
90+ , benchIOSink "read" (S.drain . A.read)
91− , benchIOSink "toStreamRev" (S.drain . A.toStreamRev)
91+ , benchIOSink "readRev" (S.drain . A.readRev)
92#ifdef DEVBUILD
92#ifdef DEVBUILD
93 , benchPureSink "foldable/foldl'" Ops.foldableFoldl'
93 , benchPureSink "foldable/foldl'" Ops.foldableFoldl'
94 , benchPureSink "foldable/sum" Ops.foldableSum
94 , benchPureSink "foldable/sum" Ops.foldableSum
benchmark/ArrayOps.hs+6−6
@@ -49,7 +49,7 @@ type Stream = A.Array
49
49
50{-# INLINE sourceUnfoldr #-}
50{-# INLINE sourceUnfoldr #-}
51sourceUnfoldr :: MonadIO m => Int -> m (Stream Int)
51sourceUnfoldr :: 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
53 where
53 where
54 step cnt =
54 step cnt =
55 if cnt > n + value
55 if cnt > n + value
@@ -58,18 +58,18 @@ sourceUnfoldr n = A.fromStreamN value $ S.unfoldr step n
58
58
59{-# INLINE sourceIntFromTo #-}
59{-# INLINE sourceIntFromTo #-}
60sourceIntFromTo :: MonadIO m => Int -> m (Stream Int)
60sourceIntFromTo :: 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)
62
62
63{-# INLINE sourceIntFromToFromStream #-}
63{-# INLINE sourceIntFromToFromStream #-}
64sourceIntFromToFromStream :: MonadIO m => Int -> m (Stream Int)
64sourceIntFromToFromStream :: 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)
66
66
67sourceIntFromToFromList :: MonadIO m => Int -> m (Stream Int)
67sourceIntFromToFromList :: MonadIO m => Int -> m (Stream Int)
68sourceIntFromToFromList n = P.return $ A.fromList $ [n..n + value]
68sourceIntFromToFromList n = P.return $ A.fromList $ [n..n + value]
69
69
70{-# INLINE sourceFromList #-}
70{-# INLINE sourceFromList #-}
71sourceFromList :: MonadIO m => Int -> m (Stream Int)
71sourceFromList :: 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]
73
73
74{-# INLINE sourceIsList #-}
74{-# INLINE sourceIsList #-}
75sourceIsList :: Int -> Stream Int
75sourceIsList :: Int -> Stream Int
@@ -280,7 +280,7 @@ onArray
280 :: MonadIO m => (S.SerialT m Int -> S.SerialT m Int)
280 :: MonadIO m => (S.SerialT m Int -> S.SerialT m Int)
281 -> Stream Int
281 -> Stream Int
282 -> m (Stream Int)
282 -> 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
284
284
285scanl' n = composeN n $ onArray $ S.scanl' (+) 0
285scanl' n = composeN n $ onArray $ S.scanl' (+) 0
286scanl1' n = composeN n $ onArray $ S.scanl1' (+)
286scanl1' n = composeN n $ onArray $ S.scanl1' (+)
@@ -511,7 +511,7 @@ readInstance str =
511
511
512{-# INLINE pureFoldl' #-}
512{-# INLINE pureFoldl' #-}
513pureFoldl' :: MonadIO m => Stream Int -> m Int
513pureFoldl' :: MonadIO m => Stream Int -> m Int
514−pureFoldl' = S.foldl' (+) 0 . A.toStream
514+pureFoldl' = S.foldl' (+) 0 . A.read
515
515
516#ifdef DEVBUILD
516#ifdef DEVBUILD
517{-# INLINE foldableFoldl' #-}
517{-# 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 @@
5
5
6{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleContexts #-}
7{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8+
9+import Streamly (SerialT)
8import Streamly.SVar (MonadAsync)
10import Streamly.SVar (MonadAsync)
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
9−import qualified Streamly.Streams.StreamK as S
16+import qualified Streamly.Streams.StreamK as K
17+
18+import Data.Char (ord)
10import Gauge
19import Gauge
20+import System.IO (hSeek, SeekMode(..), openFile, IOMode(..))
11import System.Random
21import System.Random
12
22
13maxValue :: Int
23maxValue :: Int
14maxValue = 100000
24maxValue = 100000
15
25
26+-- type Stream = K.Stream
27+type Stream = SerialT
28+
16{-# INLINE sourceUnfoldrM #-}
29{-# INLINE sourceUnfoldrM #-}
17−sourceUnfoldrM :: MonadAsync m => S.Stream m Int
30+sourceUnfoldrM :: MonadAsync m => Stream m Int
18sourceUnfoldrM = S.unfoldrM step 0
31sourceUnfoldrM = S.unfoldrM step 0
19 where
32 where
20 step cnt =
33 step cnt =
@@ -23,7 +36,7 @@ sourceUnfoldrM = S.unfoldrM step 0
23 else return (Just (cnt, cnt + 1))
36 else return (Just (cnt, cnt + 1))
24
37
25{-# INLINE sourceUnfoldrMN #-}
38{-# INLINE sourceUnfoldrMN #-}
26−sourceUnfoldrMN :: MonadAsync m => Int -> S.Stream m Int
39+sourceUnfoldrMN :: MonadAsync m => Int -> Stream m Int
27sourceUnfoldrMN n = S.unfoldrM step n
40sourceUnfoldrMN n = S.unfoldrM step n
28 where
41 where
29 step cnt =
42 step cnt =
@@ -32,7 +45,7 @@ sourceUnfoldrMN n = S.unfoldrM step n
32 else return (Just (cnt, cnt + 1))
45 else return (Just (cnt, cnt + 1))
33
46
34{-# INLINE sourceUnfoldr #-}
47{-# INLINE sourceUnfoldr #-}
35−sourceUnfoldr :: Monad m => Int -> S.Stream m Int
48+sourceUnfoldr :: Monad m => Int -> Stream m Int
36sourceUnfoldr n = S.unfoldr step n
49sourceUnfoldr n = S.unfoldr step n
37 where
50 where
38 step cnt =
51 step cnt =
@@ -44,14 +57,14 @@ sourceUnfoldr n = S.unfoldr step n
44-- take-drop composition
57-- take-drop composition
45-------------------------------------------------------------------------------
58-------------------------------------------------------------------------------
46
59
47−takeAllDropOne :: Monad m => S.Stream m Int -> S.Stream m Int
60+takeAllDropOne :: Monad m => Stream m Int -> Stream m Int
48takeAllDropOne = S.drop 1 . S.take maxValue
61takeAllDropOne = S.drop 1 . S.take maxValue
49
62
50-- Requires -fspec-constr-recursive=5 for better fused code
63-- Requires -fspec-constr-recursive=5 for better fused code
51-- The number depends on how many times we compose it
64-- The number depends on how many times we compose it
52
65
53{-# INLINE takeDrop #-}
66{-# INLINE takeDrop #-}
54−takeDrop :: Monad m => S.Stream m Int -> m ()
67+takeDrop :: Monad m => Stream m Int -> m ()
55takeDrop = S.drain .
68takeDrop = S.drain .
56 takeAllDropOne . takeAllDropOne . takeAllDropOne . takeAllDropOne
69 takeAllDropOne . takeAllDropOne . takeAllDropOne . takeAllDropOne
57
70
@@ -59,14 +72,14 @@ takeDrop = S.drain .
59-- dropWhileFalse composition
72-- dropWhileFalse composition
60-------------------------------------------------------------------------------
73-------------------------------------------------------------------------------
61
74
62−dropWhileFalse :: Monad m => S.Stream m Int -> S.Stream m Int
75+dropWhileFalse :: Monad m => Stream m Int -> Stream m Int
63dropWhileFalse = S.dropWhile (> maxValue)
76dropWhileFalse = S.dropWhile (> maxValue)
64
77
65-- Requires -fspec-constr-recursive=5 for better fused code
78-- Requires -fspec-constr-recursive=5 for better fused code
66-- The number depends on how many times we compose it
79-- The number depends on how many times we compose it
67
80
68{-# INLINE dropWhileFalseX4 #-}
81{-# INLINE dropWhileFalseX4 #-}
69−dropWhileFalseX4 :: Monad m => S.Stream m Int -> m ()
82+dropWhileFalseX4 :: Monad m => Stream m Int -> m ()
70dropWhileFalseX4 = S.drain
83dropWhileFalseX4 = S.drain
71 . dropWhileFalse . dropWhileFalse . dropWhileFalse . dropWhileFalse
84 . dropWhileFalse . dropWhileFalse . dropWhileFalse . dropWhileFalse
72
85
@@ -77,7 +90,7 @@ dropWhileFalseX4 = S.drain
77{-# INLINE iterateSource #-}
90{-# INLINE iterateSource #-}
78iterateSource
91iterateSource
79 :: MonadAsync m
92 :: 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
81iterateSource g i n = f i (sourceUnfoldrMN n)
94iterateSource g i n = f i (sourceUnfoldrMN n)
82 where
95 where
83 f (0 :: Int) m = g m
96 f (0 :: Int) m = g m
@@ -94,3 +107,14 @@ main = do
94 nfIO $ dropWhileFalseX4 sourceUnfoldrM]
107 nfIO $ dropWhileFalseX4 sourceUnfoldrM]
95 defaultMain [bench "iterate-mapM" $
108 defaultMain [bench "iterate-mapM" $
96 nfIO $ S.drain $ iterateSource (S.mapM return) 100000 10]
109 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
75
75
76{-# INLINE runStream #-}
76{-# INLINE runStream #-}
77runStream :: Monad m => Stream m a -> m ()
77runStream :: Monad m => Stream m a -> m ()
78−runStream = S.runStream
78+runStream = S.drain
79
79
80{-# INLINE mapM_ #-}
80{-# INLINE mapM_ #-}
81mapM_ :: Monad m => Stream m a -> m ()
81mapM_ :: Monad m => Stream m a -> m ()
benchmark/StreamKOps.hs+2−2
@@ -114,7 +114,7 @@ source = sourceUnfoldrM
114
114
115{-# INLINE runStream #-}
115{-# INLINE runStream #-}
116runStream :: Monad m => Stream m a -> m ()
116runStream :: Monad m => Stream m a -> m ()
117−runStream = S.runStream
117+runStream = S.drain
118
118
119{-# INLINE mapM_ #-}
119{-# INLINE mapM_ #-}
120mapM_ :: Monad m => Stream m a -> m ()
120mapM_ :: Monad m => Stream m a -> m ()
@@ -131,7 +131,7 @@ uncons s = do
131init :: (Monad m, S.IsStream t) => t m a -> m ()
131init :: (Monad m, S.IsStream t) => t m a -> m ()
132init s = do
132init s = do
133 t <- S.init s
133 t <- S.init s
134− P.mapM_ S.runStream t
134+ P.mapM_ S.drain t
135
135
136{-# INLINE tail #-}
136{-# INLINE tail #-}
137tail :: (Monad m, S.IsStream t) => t m a -> m ()
137tail :: (Monad m, S.IsStream t) => t m a -> m ()
src/Streamly/Array.hs+107−135
@@ -84,14 +84,15 @@ module Streamly.Array
84
84
85 -- * Construction
85 -- * Construction
86 -- | When performance matters, the fastest way to generate an array is
86 -- | 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
88 -- used to conveniently construct arrays from literal values.
88 -- used to conveniently construct arrays from literal values.
89 -- 'OverloadedLists' extension or 'fromList' can be used to construct an
89 -- 'OverloadedLists' extension or 'fromList' can be used to construct an
90 -- array from a list literal. Similarly, 'OverloadedStrings' extension or
90 -- array from a list literal. Similarly, 'OverloadedStrings' extension or
91 -- 'fromList' can be used to construct an array from a string literal.
91 -- 'fromList' can be used to construct an array from a string literal.
92
92
93− , fromStreamN
93+ -- , newArray
94− , fromStream
94+ , writeN
95+ , write
95 , fromListN
96 , fromListN
96 , fromList
97 , fromList
97
98
@@ -107,8 +108,8 @@ module Streamly.Array
107 -- 'GHC.Exts.toList' from "GHC.Exts" can be used to convert an array to a
108 -- 'GHC.Exts.toList' from "GHC.Exts" can be used to convert an array to a
108 -- list.
109 -- list.
109
110
110− , toStream
111+ , read
111− , toStreamRev
112+ , readRev
112 , toList
113 , toList
113 , flattenArrays
114 , flattenArrays
114 -- , flattenArraysRev
115 -- , flattenArraysRev
@@ -116,53 +117,55 @@ module Streamly.Array
116
117
117 -- * Random Access
118 -- * Random Access
118 , length
119 , length
119− , (!!)
120+ -- , (!!)
120−
121+
121− -- * IO: Single Array
122+ , readIndex
122− -- , fromHandleUpto
123+ {-
123− -- , fromHandleN
124+ , readSlice
124− -- , fromHandlePosUpto
125+ , readSliceRev
125− -- , fromHandlePosN
126+
126− , toHandle
127+ , writeIndex
127−
128+ , writeSlice
128− -- * IO: Stream of Arrays
129+ , writeSliceRev
129− -- , fromHandleArraysUpto
130+ -}
130− -- , fromHandleArraysOf
131− , fromHandleArrays
132 )
131 )
133where
132where
134
133
135import Control.Monad.IO.Class (MonadIO(..))
134import Control.Monad.IO.Class (MonadIO(..))
136−import Data.Word (Word8)
137import Foreign.ForeignPtr (withForeignPtr)
135import Foreign.ForeignPtr (withForeignPtr)
138−import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
139import Foreign.Ptr (minusPtr, plusPtr, castPtr)
136import Foreign.Ptr (minusPtr, plusPtr, castPtr)
140import Foreign.Storable (Storable(..))
137import Foreign.Storable (Storable(..))
141−import System.IO (Handle, hGetBufSome, hPutBuf)
142−import Prelude hiding (length, null, last, map, (!!))
138+import Prelude hiding (length, null, last, map, (!!), read)
143−
144−import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
145
139
140+import Streamly.Array.Types hiding (flattenArrays, newArray)
146import Streamly.Streams.Serial (SerialT)
141import Streamly.Streams.Serial (SerialT)
147−import Streamly.Streams.StreamK.Type (IsStream, mkStream)
142+import Streamly.Streams.StreamK.Type (IsStream)
148−import Streamly.Array.Types hiding (flattenArrays)
149−import qualified Streamly.Array.Types as A
150
143
144+
151−import qualified Streamly.Streams.StreamD as D
145+import qualified Streamly.Array.Types as A
152import qualified Streamly.Prelude as S
146import qualified Streamly.Prelude as S
147+import qualified Streamly.Streams.StreamD as D
153
148
154-------------------------------------------------------------------------------
149-------------------------------------------------------------------------------
155-- Construction
150-- Construction
156-------------------------------------------------------------------------------
151-------------------------------------------------------------------------------
157
152
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+
158-- | Create an 'Array' from the first N elements of a stream. The array is
161-- | Create an 'Array' from the first N elements of a stream. The array is
159-- allocated to size N, if the stream terminates before N elements then the
162-- allocated to size N, if the stream terminates before N elements then the
160-- array may hold less than N elements.
163-- array may hold less than N elements.
161--
164--
162-- @since 0.7.0
165-- @since 0.7.0
163−{-# INLINE fromStreamN #-}
166+{-# INLINE writeN #-}
164−fromStreamN :: (MonadIO m, Storable a) => Int -> SerialT m a -> m (Array a)
167+writeN :: (MonadIO m, Storable a) => Int -> SerialT m a -> m (Array a)
165−fromStreamN n m = fromStreamDN n $ D.toStreamD m
168+writeN n m = fromStreamDN n $ D.toStreamD m
166
169
167-------------------------------------------------------------------------------
170-------------------------------------------------------------------------------
168-- Elimination
171-- Elimination
@@ -171,30 +174,30 @@ fromStreamN n m = fromStreamDN n $ D.toStreamD m
171-- | Convert an 'Array' into a stream.
174-- | Convert an 'Array' into a stream.
172--
175--
173-- @since 0.7.0
176-- @since 0.7.0
174−{-# INLINE_EARLY toStream #-}
177+{-# INLINE_EARLY read #-}
175−toStream :: (Monad m, IsStream t, Storable a) => Array a -> t m a
178+read :: (Monad m, IsStream t, Storable a) => Array a -> t m a
176−toStream = D.fromStreamD . toStreamD
179+read = D.fromStreamD . toStreamD
177-- XXX add fallback to StreamK rule
180-- XXX add fallback to StreamK rule
178−-- {-# RULES "Streamly.Array.toStream fallback to StreamK" [1]
181+-- {-# RULES "Streamly.Array.read fallback to StreamK" [1]
179−-- forall a. S.toStreamK (toStream a) = K.fromArray a #-}
182+-- forall a. S.readK (read a) = K.fromArray a #-}
180
183
181-- | Convert an 'Array' into a stream in reverse order.
184-- | Convert an 'Array' into a stream in reverse order.
182--
185--
183-- @since 0.7.0
186-- @since 0.7.0
184−{-# INLINE_EARLY toStreamRev #-}
187+{-# INLINE_EARLY readRev #-}
185−toStreamRev :: (Monad m, IsStream t, Storable a) => Array a -> t m a
188+readRev :: (Monad m, IsStream t, Storable a) => Array a -> t m a
186−toStreamRev = D.fromStreamD . toStreamDRev
189+readRev = D.fromStreamD . toStreamDRev
187-- XXX add fallback to StreamK rule
190-- XXX add fallback to StreamK rule
188−-- {-# RULES "Streamly.Array.toStreamRev fallback to StreamK" [1]
191+-- {-# RULES "Streamly.Array.readRev fallback to StreamK" [1]
189−-- forall a. S.toStreamK (toStreamRev a) = K.revFromArray a #-}
192+-- forall a. S.toStreamK (readRev a) = K.revFromArray a #-}
190
193
191−{-# INLINE null #-}
194+{-# INLINE _null #-}
192−null :: Storable a => Array a -> Bool
195+_null :: Storable a => Array a -> Bool
193−null arr = length arr == 0
196+_null arr = length arr == 0
194
197
195{-# INLINE _last #-}
198{-# INLINE _last #-}
196_last :: forall a. Storable a => Array a -> Maybe a
199_last :: forall a. Storable a => Array a -> Maybe a
197−_last arr = arr !! (length arr - 1)
200+_last arr = readIndex arr (length arr - 1)
198
201
199-------------------------------------------------------------------------------
202-------------------------------------------------------------------------------
200-- Random Access
203-- Random Access
@@ -203,13 +206,62 @@ _last arr = arr !! (length arr - 1)
203-- | /O(1)/ Lookup the element at the given index.
206-- | /O(1)/ Lookup the element at the given index.
204--
207--
205-- @since 0.7.0
208-- @since 0.7.0
206−{-# INLINE (!!) #-}
209+{-# INLINE readIndex #-}
207−(!!) :: Storable a => Array a -> Int -> Maybe a
210+readIndex :: Storable a => Array a -> Int -> Maybe a
208−arr !! i =
211+readIndex arr i =
209 if i < 0 || i > length arr - 1
212 if i < 0 || i > length arr - 1
210 then Nothing
213 then Nothing
211 else Just $ unsafeIndex arr i
214 else Just $ unsafeIndex arr i
212
215
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+
213-------------------------------------------------------------------------------
265-------------------------------------------------------------------------------
214-- Streams of Arrays
266-- Streams of Arrays
215-------------------------------------------------------------------------------
267-------------------------------------------------------------------------------
@@ -254,106 +306,26 @@ spliceArrays s = do
254 buffered <- S.foldr S.cons S.nil s
306 buffered <- S.foldr S.cons S.nil s
255 len <- S.sum (S.map length buffered)
307 len <- S.sum (S.map length buffered)
256
308
257− arr <- liftIO $ newArray len
309+ arr <- liftIO $ A.newArray len
258− end <- S.foldlM' write (aEnd arr) buffered
310+ end <- S.foldlM' writeArr (aEnd arr) buffered
259 return $ arr {aEnd = end}
311 return $ arr {aEnd = end}
260
312
261 where
313 where
262
314
263− write dst Array{..} =
315+ writeArr dst Array{..} =
264 liftIO $ withForeignPtr aStart $ \src -> do
316 liftIO $ withForeignPtr aStart $ \src -> do
265 let len = aEnd `minusPtr` src
317 let len = aEnd `minusPtr` src
266 memcpy (castPtr dst) (castPtr src) len
318 memcpy (castPtr dst) (castPtr src) len
267 return $ dst `plusPtr` len
319 return $ dst `plusPtr` len
268
320
269-- | Create an 'Array' from a stream. This is useful when we want to create a
321-- | 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
271-- as efficient when the size is already known.
323-- as efficient when the size is already known.
272--
324--
273-- Note that if the input stream is too large memory allocation for the array
325-- Note that if the input stream is too large memory allocation for the array
274-- may fail. When the stream size is not known, `arraysOf` followed by
326-- may fail. When the stream size is not known, `arraysOf` followed by
275-- processing of indvidual arrays in the resulting stream should be preferred.
327-- processing of indvidual arrays in the resulting stream should be preferred.
276--
328--
277−{-# INLINE fromStream #-}
329+{-# INLINE write #-}
278−fromStream :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
330+write :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
279−fromStream m = A.fromStreamD $ D.toStreamD m
331+write 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
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
1493splitOn'
1493splitOn'
1494 :: (IsStream t, MonadAsync m, Storable a, Enum a, Eq a)
1494 :: (IsStream t, MonadAsync m, Storable a, Enum a, Eq a)
1495 => Array a -> Fold m a b -> t m a -> t m b
1495 => Array a -> Fold m a b -> t m a -> t m b
1496−splitOn' patt f m = S.intersperseM
1496+splitOn' patt f m = S.intersperseM (foldl' f (A.read patt)) $ splitOn patt f m
1497− (foldl' f (A.toStream patt)) $ splitOn patt f m
1498
1497
1499-- | Like 'splitSuffixOn' but keeps the suffix intact in the splits.
1498-- | Like 'splitSuffixOn' but keeps the suffix intact in the splits.
1500--
1499--
src/Streamly/Prelude.hs+4−4
@@ -105,9 +105,6 @@ module Streamly.Prelude
105 , K.fromFoldable
105 , K.fromFoldable
106 , fromFoldableM
106 , fromFoldableM
107
107
108− -- ** From External Containers
109− , fromHandle
110−
111 -- * Elimination
108 -- * Elimination
112
109
113 -- ** Deconstruction
110 -- ** Deconstruction
@@ -213,7 +210,6 @@ module Streamly.Prelude
213 -- -- | Convert or divert a stream into an output structure, container or
210 -- -- | Convert or divert a stream into an output structure, container or
214 -- sink.
211 -- sink.
215 , toList
212 , toList
216− , toHandle
217
213
218 -- ** Partial Folds
214 -- ** Partial Folds
219 -- | Folds that may terminate before evaluating the whole stream. These
215 -- | Folds that may terminate before evaluating the whole stream. These
@@ -465,6 +461,8 @@ module Streamly.Prelude
465 , runStream
461 , runStream
466 , runN
462 , runN
467 , runWhile
463 , runWhile
464+ , fromHandle
465+ , toHandle
468 )
466 )
469where
467where
470
468
@@ -837,6 +835,7 @@ each = K.fromFoldable
837-- | Read lines from an IO Handle into a stream of Strings.
835-- | Read lines from an IO Handle into a stream of Strings.
838--
836--
839-- @since 0.1.0
837-- @since 0.1.0
838+{-# DEPRECATED fromHandle "Please use Streamly.FileIO instead." #-}
840fromHandle :: (IsStream t, MonadIO m) => IO.Handle -> t m String
839fromHandle :: (IsStream t, MonadIO m) => IO.Handle -> t m String
841fromHandle h = go
840fromHandle h = go
842 where
841 where
@@ -1530,6 +1529,7 @@ _toListRev = D.toListRev . toStreamD
1530-- Write a stream of Strings to an IO Handle.
1529-- Write a stream of Strings to an IO Handle.
1531--
1530--
1532-- @since 0.1.0
1531-- @since 0.1.0
1532+{-# DEPRECATED toHandle "Please use Streamly.FileIO instead." #-}
1533toHandle :: MonadIO m => IO.Handle -> SerialT m String -> m ()
1533toHandle :: MonadIO m => IO.Handle -> SerialT m String -> m ()
1534toHandle h m = go m
1534toHandle h m = go m
1535 where
1535 where
src/Streamly/Streams/StreamD.hs+1−2
@@ -128,7 +128,6 @@ module Streamly.Streams.StreamD
128 -- ** Flattening nested streams
128 -- ** Flattening nested streams
129 , concatMapM
129 , concatMapM
130 , concatMap
130 , concatMap
131− , flattenArrays
132
131
133 -- ** Grouping
132 -- ** Grouping
134 , groupsOf
133 , groupsOf
@@ -243,7 +242,7 @@ import Prelude
243 (!!), scanl, scanl1, concatMap, replicate, enumFromTo, concat,
242 (!!), scanl, scanl1, concatMap, replicate, enumFromTo, concat,
244 reverse)
243 reverse)
245
244
246−import Streamly.Array.Types (Array(..), flattenArrays)
245+import Streamly.Array.Types (Array(..))
247import Streamly.Fold.Types (Fold(..))
246import Streamly.Fold.Types (Fold(..))
248import Streamly.SVar (MonadAsync, defState, adaptState)
247import Streamly.SVar (MonadAsync, defState, adaptState)
249import Streamly.Sink.Types (Sink(..))
248import Streamly.Sink.Types (Sink(..))
src/Streamly/String.hs+111−4
@@ -7,6 +7,13 @@
7-- Stability : experimental
7-- Stability : experimental
8-- Portability : GHC
8-- Portability : GHC
9--
9--
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+--
10-- The 'String' type in this module is just a synonym for the type @List Char@.
17-- The 'String' type in this module is just a synonym for the type @List Char@.
11-- It provides better performance compared to the standard Haskell @String@
18-- It provides better performance compared to the standard Haskell @String@
12-- type and can be used almost as a drop-in replacement, especially when used
19-- type and can be used almost as a drop-in replacement, especially when used
@@ -14,15 +21,115 @@
14--
21--
15-- See "Streamly.List", <src/docs/streamly-vs-lists.md> for more details and
22-- See "Streamly.List", <src/docs/streamly-vs-lists.md> for more details and
16-- <src/test/PureStreams.hs> for comprehensive usage examples.
23-- <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.
18--
27--
19module Streamly.String
28module 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+-}
22 )
57 )
23where
58where
24
59
60+-- import Data.Word (Word8)
61+-- import Streamly (IsStream)
25import Streamly.List (List)
62import 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)
27
66
28type String = List Char
67type 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+
1-- |
3-- |
2-- Module : Streamly.Time
4-- Module : Streamly.Time
3-- Copyright : (c) 2017 Harendra Kumar
5-- Copyright : (c) 2017 Harendra Kumar
@@ -10,16 +12,26 @@
10-- Time utilities for reactive programming.
12-- Time utilities for reactive programming.
11
13
12module Streamly.Time
14module Streamly.Time
13−{-# DEPRECATED
15+ ( intervalsOf
14− "Please use the \"rate\" combinator instead of the functions in this module"
16+
15− #-}
17+ -- * Deprecated
16− ( periodic
18+ , periodic
17 , withClock
19 , withClock
18 )
20 )
19where
21where
20
22
21−import Control.Monad (when)
22import Control.Concurrent (threadDelay)
23import 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
23
35
24-- | Run an action forever periodically at the given frequency specified in per
36-- | Run an action forever periodically at the given frequency specified in per
25-- second (Hz).
37-- second (Hz).
@@ -72,3 +84,42 @@ withClock clock freq action = do
72 lastPeriod = (baseTime - lastAdj) `div` freq
84 lastPeriod = (baseTime - lastAdj) `div` freq
73 newDelay = max 0 (delay + period - lastPeriod)
85 newDelay = max 0 (delay + period - lastPeriod)
74 return (baseTime, newTick, newDelay)
86 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
4description:
4description:
5 Streamly, short for streaming concurrently, provides monadic streams, with a
5 Streamly, short for streaming concurrently, provides monadic streams, with a
6 simple API, almost identical to standard lists, and an in-built support for
6 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
7+ concurrency. Streams can be generated, merged, chained, mapped, zipped, and
9− concurrently – providing a generalized high level programming framework
8+ consumed concurrently – providing a generalized high level programming
10− unifying streaming and concurrency. Controlled concurrency allows even
9+ framework unifying streaming and concurrency. Controlled concurrency allows
11− infinite streams to be evaluated concurrently. Concurrency is auto scaled
10+ even infinite streams to be evaluated concurrently. Concurrency is auto
12− based on feedback from the stream consumer. The programmer does not have to
11+ scaled based on feedback from the stream consumer. Programmer does not
13− be aware of threads, locking or synchronization to write scalable concurrent
12+ have to be aware of threads, locking or synchronization to write scalable
14− programs.
13+ concurrent programs.
15 .
14 .
16− The basic streaming functionality of streamly is equivalent to that provided by
15+ In addition to the basic streaming functionality provided by
17 streaming libraries like
16 streaming libraries like
18 <https://hackage.haskell.org/package/streaming streaming>,
17 <https://hackage.haskell.org/package/streaming streaming>,
19 <https://hackage.haskell.org/package/pipes pipes>, and
18 <https://hackage.haskell.org/package/pipes pipes>, and
20− <https://hackage.haskell.org/package/conduit conduit>.
19+ <https://hackage.haskell.org/package/conduit conduit> streamly provides
21− In addition to providing streaming functionality, streamly subsumes the
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
22 functionality of list transformer libraries like @pipes@ or
23 functionality of list transformer libraries like @pipes@ or
23 <https://hackage.haskell.org/package/list-t list-t> and also the logic
24 <https://hackage.haskell.org/package/list-t list-t> and also the logic
24 programming library <https://hackage.haskell.org/package/logict logict>. On
25 programming library <https://hackage.haskell.org/package/logict logict>. On
25− the concurrency side, it subsumes the functionality of the
26+ the concurrency side, it subsumes the basic concurrency functionality
26− <https://hackage.haskell.org/package/async async> package. Because it
27+ provided by the <https://hackage.haskell.org/package/async async> package and
28+ provides much more powerful high level concurrent streaming composition.
27− supports streaming with concurrency we can write FRP applications similar in
29+ Because it supports streaming with concurrency we can write FRP applications
28− concept to <https://hackage.haskell.org/package/Yampa Yampa> or
30+ similar in concept to <https://hackage.haskell.org/package/Yampa Yampa> or
29 <https://hackage.haskell.org/package/reflex reflex>.
31 <https://hackage.haskell.org/package/reflex reflex>.
30 .
32 .
31− For file IO, currently the library provides only one API to stream the lines
33+ Streamly focuses on practical engineering with high performance. One can
32− in the file as Strings. Future versions will provide better streaming file
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+ .
33− IO options. Streamly interoperates with the popular streaming libraries, see
45+ Streamly interoperates with popular streaming libraries, see
34 the interoperation section in "Streamly.Tutorial".
46 the interoperation section in "Streamly.Tutorial".
35 .
47 .
36 Why use streamly?
48 Why use streamly?
@@ -183,6 +195,7 @@ library
183 , Streamly.Time
195 , Streamly.Time
184 , Streamly
196 , Streamly
185 , Streamly.Fold
197 , Streamly.Fold
198+ , Streamly.FileSystem.File
186
199
187 , Streamly.Tutorial
200 , Streamly.Tutorial
188 , Streamly.Internal
201 , Streamly.Internal
@@ -528,6 +541,7 @@ benchmark nested
528 default-language: Haskell2010
541 default-language: Haskell2010
529 ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
542 ghc-options: -O2 -Wall -fspec-constr-recursive=10 -with-rtsopts "-T"
530 if flag(dev)
543 if flag(dev)
544+ cpp-options: -DDEVBUILD
531 ghc-options: -Wmissed-specialisations
545 ghc-options: -Wmissed-specialisations
532 -Wall-missed-specialisations
546 -Wall-missed-specialisations
533 -fno-ignore-asserts
547 -fno-ignore-asserts
@@ -546,6 +560,9 @@ benchmark nested
546 , deepseq >= 1.4.3 && < 1.5
560 , deepseq >= 1.4.3 && < 1.5
547 , random >= 1.0 && < 2.0
561 , random >= 1.0 && < 2.0
548 , gauge >= 0.2.4 && < 0.3
562 , gauge >= 0.2.4 && < 0.3
563+ if impl(ghc < 8.0)
564+ build-depends:
565+ transformers >= 0.4 && < 0.6
549
566
550benchmark array
567benchmark array
551 type: exitcode-stdio-1.0
568 type: exitcode-stdio-1.0
@@ -578,6 +595,33 @@ benchmark array
578 build-depends:
595 build-depends:
579 transformers >= 0.4 && < 0.6
596 transformers >= 0.4 && < 0.6
580
597
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+
581-------------------------------------------------------------------------------
625-------------------------------------------------------------------------------
582-- Internal benchmarks for unexposed modules
626-- Internal benchmarks for unexposed modules
583-------------------------------------------------------------------------------
627-------------------------------------------------------------------------------
test/Arrays.hs+12−12
@@ -42,7 +42,7 @@ testLength =
42 forAll (choose (0, maxArrLen)) $ \len ->
42 forAll (choose (0, maxArrLen)) $ \len ->
43 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
43 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
44 monadicIO $ do
44 monadicIO $ do
45− arr <- A.fromStreamN len
45+ arr <- A.writeN len
46 $ S.fromList list
46 $ S.fromList list
47 assert (A.length arr == len)
47 assert (A.length arr == len)
48
48
@@ -51,10 +51,10 @@ testFromToStreamN =
51 forAll (choose (0, maxArrLen)) $ \len ->
51 forAll (choose (0, maxArrLen)) $ \len ->
52 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
52 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
53 monadicIO $ do
53 monadicIO $ do
54− arr <- A.fromStreamN len
54+ arr <- A.writeN len
55 $ S.fromList list
55 $ S.fromList list
56 xs <- S.toList
56 xs <- S.toList
57− $ A.toStream arr
57+ $ A.read arr
58 assert (xs == list)
58 assert (xs == list)
59
59
60testToStreamRev :: Property
60testToStreamRev :: Property
@@ -62,10 +62,10 @@ testToStreamRev =
62 forAll (choose (0, maxArrLen)) $ \len ->
62 forAll (choose (0, maxArrLen)) $ \len ->
63 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
63 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
64 monadicIO $ do
64 monadicIO $ do
65− arr <- A.fromStreamN len
65+ arr <- A.writeN len
66 $ S.fromList list
66 $ S.fromList list
67 xs <- S.toList
67 xs <- S.toList
68− $ A.toStreamRev arr
68+ $ A.readRev arr
69 assert (xs == reverse list)
69 assert (xs == reverse list)
70
70
71testArraysOf :: Property
71testArraysOf :: Property
@@ -74,7 +74,7 @@ testArraysOf =
74 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
74 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
75 monadicIO $ do
75 monadicIO $ do
76 xs <- S.toList
76 xs <- S.toList
77− $ S.concatMap A.toStream
77+ $ S.concatMap A.read
78 $ A.arraysOf 240
78 $ A.arraysOf 240
79 $ S.fromList list
79 $ S.fromList list
80 assert (xs == list)
80 assert (xs == list)
@@ -95,9 +95,9 @@ testFromToStream =
95 forAll (choose (0, maxArrLen)) $ \len ->
95 forAll (choose (0, maxArrLen)) $ \len ->
96 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
96 forAll (vectorOf len (arbitrary :: Gen Int)) $ \list ->
97 monadicIO $ do
97 monadicIO $ do
98− arr <- A.fromStream $ S.fromList list
98+ arr <- A.write $ S.fromList list
99 xs <- S.toList
99 xs <- S.toList
100− $ A.toStream arr
100+ $ A.read arr
101 assert (xs == list)
101 assert (xs == list)
102
102
103main :: IO ()
103main :: IO ()
@@ -106,9 +106,9 @@ main = hspec
106 $ modifyMaxSuccess (const maxTestCount)
106 $ modifyMaxSuccess (const maxTestCount)
107 $ do
107 $ do
108 describe "Construction" $ do
108 describe "Construction" $ do
109− prop "length . fromStreamN n === n" $ testLength
109+ prop "length . writeN n === n" $ testLength
110− prop "toStream . fromStreamN n === id" $ testFromToStreamN
110+ prop "read . writeN n === id" $ testFromToStreamN
111− prop "toStreamRev . fromStream === reverse" $ testToStreamRev
111+ prop "readRev . write === reverse" $ testToStreamRev
112 prop "arraysOf concats to original" $ testArraysOf
112 prop "arraysOf concats to original" $ testArraysOf
113 prop "flattenArrays concats to original" $ testFlattenArrays
113 prop "flattenArrays concats to original" $ testFlattenArrays
114− prop "toStream . fromStream === id" $ testFromToStream
114+ prop "read . write === id" $ testFromToStream