@@ -101,7 +101,7 @@ import Data.Function ((&))
102import qualified Streamly.Data.Fold as Fold
102import qualified Streamly.Data.Fold as Fold
103import qualified Streamly.Data.Stream as Stream
103import qualified Streamly.Data.Stream as Stream
104−import qualified Streamly.Internal.FileSystem.File as File
104+import qualified Streamly.FileSystem.File as File
106wcb :: String -> IO Int
106wcb :: String -> IO Int
107wcb file =
107wcb file =
@@ -165,12 +165,11 @@ then combines the outputs from the folds using the supplied combiner
165function (`(,,)`).
165function (`(,,)`).
167```haskell ghci
167```haskell ghci
168−import Streamly.Internal.Data.Fold.Tee (Tee(Tee))
168+import Streamly.Data.Fold (Tee(..))
169−import qualified Streamly.Internal.Data.Fold.Tee as Tee
171-- The fold accepts a stream of `Word8` and returns the three counts.
170-- The fold accepts a stream of `Word8` and returns the three counts.
172countAll :: Fold IO Word8 (Int, Int, Int)
171countAll :: Fold IO Word8 (Int, Int, Int)
173−countAll = Tee.toFold $ (,,) <$> Tee Fold.length <*> Tee nlines <*> Tee nwords
172+countAll = unTee $ (,,) <$> Tee Fold.length <*> Tee nlines <*> Tee nwords
175wc :: String -> IO (Int, Int, Int)
174wc :: String -> IO (Int, Int, Int)
@@ -296,31 +295,22 @@ from each chunk.
296```haskell ghci
295```haskell ghci
297{-# LANGUAGE FlexibleContexts #-}
296{-# LANGUAGE FlexibleContexts #-}
298+import GHC.Conc (numCapabilities)
299−import qualified Streamly.Data.Stream.Concurrent as Concur
299+import qualified Streamly.Data.Stream.Prelude as Stream
301−-- Number of threads we can afford
302−numCapabilities :: Int
305wc :: String -> IO (Bool, Counts)
301wc :: String -> IO (Bool, Counts)
306wc file = do
302wc file = do
307− Stream.unfold File.chunkReader file
303+ File.readChunks file -- Stream IO (Array Word8)
309− ( Concur.maxThreads numCapabilities
310− . Concur.ordered True )
311− partialCounts
304+ & Stream.parMapM cfg partialCounts -- Stream IO (Bool, Counts)
312− & Stream.fold
305+ & Stream.fold add -- IO (Bool, Counts)
313− (Fold.foldl' addCounts (False, Counts 0 0 0 True))
306+
316−Please note that we use the mapping function from
309+ cfg = Stream.maxThreads numCapabilities . Stream.ordered True
317−`Streamly.Data.Stream.Concurrent` module. We explicitly specify what parts of
310+ add = Fold.foldl' addCounts (False, Counts 0 0 0 True)
318−our stream are concurrent.
311+```
320−If we replace `Concur.mapMWith (Concur.maxThreads numCapabilities
313+We can replace `parMapM` with `mapM` to get a serial version of the program.
321−. Concur.ordered True)` with `Stream.mapM`, we would still have a perfectly
322−valid and performant serial program. Notice how succinctly and idiomatically we
323−have expressed the concurrent word counting problem.
325A benchmark with 2 CPUs:
315A benchmark with 2 CPUs:
@@ -376,15 +366,20 @@ fetch w = threadDelay 1000000 >> return (w,w)
376-- connection is closed.
366-- connection is closed.
377lookupWords :: Socket -> IO ()
367lookupWords :: Socket -> IO ()
378lookupWords sk =
368lookupWords sk =
379− Stream.unfold Socket.reader sk
369+ Stream.unfold Socket.reader sk -- Stream IO Word8
380− & Unicode.decodeLatin1
370+ & Unicode.decodeLatin1 -- Stream IO Char
381− & Stream.parseMany (Parser.wordBy isSpace Fold.toList)
371+ & Stream.parseMany word -- Stream IO String
382− & Concur.mapMWith (Concur.ordered True) fetch
372+ & Stream.parMapM cfg fetch -- Stream IO (String, String)
383− & fmap show
373+ & fmap show -- Stream IO String
384− & Stream.intersperse "\n"
374+ & Stream.intersperse "\n" -- Stream IO String
385− & Unicode.encodeStrings Unicode.encodeLatin1
375+ & Unicode.encodeStrings Unicode.encodeLatin1 -- Stream IO (Array Word8)
386 & Stream.fold (Socket.writeChunks sk)
376 & Stream.fold (Socket.writeChunks sk)
380+ word = Parser.wordBy isSpace Fold.toList
381+ cfg = Stream.ordered True
388serve :: Socket -> IO ()
383serve :: Socket -> IO ()
389serve sk = finally (lookupWords sk) (close sk)
384serve sk = finally (lookupWords sk) (close sk)
@@ -393,9 +388,9 @@ serve sk = finally (lookupWords sk) (close sk)
393-- "nc" as a client to try it out.
388-- "nc" as a client to try it out.
394main :: IO ()
389main :: IO ()
396− Stream.unfold TCP.acceptorOnPort 8091
391+ Stream.unfold TCP.acceptorOnPort 8091 -- Stream IO Socket
397− & Concur.mapMWith id serve
392+ & Stream.parMapM id serve -- Stream IO ()
398− & Stream.fold Fold.drain
393+ & Stream.fold Fold.drain -- IO ()
401### Merging Incoming Streams
396### Merging Incoming Streams
@@ -424,9 +419,13 @@ import qualified Streamly.FileSystem.Handle as Handle
424-- buffering for safety.
419-- buffering for safety.
425readLines :: Socket -> Stream IO (Array Char)
420readLines :: Socket -> Stream IO (Array Char)
426readLines sk =
421readLines sk =
427− Stream.unfold Socket.reader sk
422+ Stream.unfold Socket.reader sk -- Stream IO Word8
428− & Unicode.decodeLatin1
423+ & Unicode.decodeLatin1 -- Stream IO Char
424+ & Stream.foldMany line -- Stream IO (Array Char)
429− & Stream.foldMany (Fold.takeEndBy (== '\n') Array.write)
428+ line = Fold.takeEndBy (== '\n') Array.write
431recv :: Socket -> Stream IO (Array Char)
430recv :: Socket -> Stream IO (Array Char)
432recv sk = Stream.finallyIO (close sk) (readLines sk)
431recv sk = Stream.finallyIO (close sk) (readLines sk)
@@ -437,11 +436,11 @@ recv sk = Stream.finallyIO (close sk) (readLines sk)
437-- streams at line boundaries and writes the merged stream to a file.
436-- streams at line boundaries and writes the merged stream to a file.
438server :: Handle -> IO ()
437server :: Handle -> IO ()
439server file =
438server file =
440− Stream.unfold TCP.acceptorOnPort 8090
439+ Stream.unfold TCP.acceptorOnPort 8090 -- Stream IO Socket
441− & Concur.concatMapWith (Concur.eager True) recv
440+ & Stream.parConcatMap (Stream.eager True) recv -- Stream IO (Array Char)
442− & Stream.unfoldMany Array.reader
441+ & Stream.unfoldMany Array.reader -- Stream IO Char
443− & Unicode.encodeLatin1
442+ & Unicode.encodeLatin1 -- Stream IO Word8
444− & Stream.fold (Handle.write file)
443+ & Stream.fold (Handle.write file) -- IO ()
446main :: IO ()
445main :: IO ()
447main = withFile "output.txt" AppendMode server
446main = withFile "output.txt" AppendMode server
@@ -449,63 +448,42 @@ main = withFile "output.txt" AppendMode server
450### Listing Directories Recursively/Concurrently
449### Listing Directories Recursively/Concurrently
452−Our next example lists a directory tree recursively, reading
451+Our next example lists a directory tree recursively, and concurrently.
453−multiple directories concurrently.
455−This example uses the tree traversing combinator `iterateMapLeftsWith`. This
453+This example uses the tree traversing combinator `parConcatIterate`. This
456−combinator maps a stream generator on the `Left` values in its input stream
454+combinator maps a stream generator function on the input stream and then
457−(directory names in this case), feeding the resulting `Left` values back to the
455+recursively on the generated stream as well and flattens the results. We map a
458−input, while it lets the `Right` values (file names in this case) pass through
456+directory to a stream generating its children and a file to a nil stream. This
459−to the output. The joining combinator explicitly sends streams to a channel
457+results in a concurrent recursive depth first traversal of the directory tree.
460−making it iterate on the directories concurrently.
462−Please see the file [ListDir.hs][] for the complete working code,
459+Please see [ListDir.hs][] for the complete working code.
463−including the imports that we have omitted below.
465```haskell ghci
461```haskell ghci
466−import Data.Bifunctor (bimap)
467import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
462import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
468−import qualified Streamly.Internal.Data.Stream as Stream (iterateMapLeftsWith)
463+import qualified Streamly.Internal.FileSystem.Dir as Dir (readEitherPaths)
469−import qualified Streamly.Internal.FileSystem.Dir as Dir (readEither)
470−import qualified Streamly.Internal.Data.Stream.Concurrent.Channel as Concur
472−-- Lists a directory as a stream of (Either Dir File).
473−listDir :: String -> Stream IO (Either String String)
476− & fmap (bimap mkAbs mkAbs)
478− where mkAbs x = dir ++ "/" ++ x
480−-- | List the current directory recursively using concurrent processing.
481main :: IO ()
465main :: IO ()
483− chan <- Concur.newChannel (Concur.ordered True)
484− let combineOrdered s1 s2 = do
485− Stream.fromEffect $ Concur.toChannel chan s1
486− Stream.fromEffect $ Concur.toChannel chan s2
487− Concur.fromChannel chan
488 hSetBuffering stdout LineBuffering
467 hSetBuffering stdout LineBuffering
489 let start = Stream.fromPure (Left ".")
468 let start = Stream.fromPure (Left ".")
490− Stream.iterateMapLeftsWith combineOrdered listDir start
469+ f = either Dir.readEitherPaths (const Stream.nil)
470+ ls = Stream.parConcatIterate id f start
491− & Stream.fold (Fold.drainMapM print)
471+ in Stream.fold (Fold.drainMapM print) ls
494### Rate Limiting
474### Rate Limiting
496−For bounded concurrent streams, a stream yield rate can be specified
476+For concurrent streams, a stream evaluation rate can be specified. For
497−easily. For example, to print "tick" once every second you can simply
477+example, to print "tick" once every second you can simply write:
500```haskell ghci
479```haskell ghci
501import qualified Streamly.Internal.Data.Stream as Stream (timestamped)
480import qualified Streamly.Internal.Data.Stream as Stream (timestamped)
503main :: IO ()
482main :: IO ()
505− Stream.repeat (pure "tick")
484+ Stream.parRepeatM (Stream.avgRate 1) (pure "tick") -- Stream IO String
506− & Concur.sequenceWith (Concur.avgRate 1)
507− & Stream.timestamped
485+ & Stream.timestamped -- Stream IO (AbsTime, String)
508− & Stream.fold (Fold.drainMapM print)
486+ & Stream.fold (Fold.drainMapM print) -- IO ()
511Please see the file [Rate.hs][] for the complete working code.
489Please see the file [Rate.hs][] for the complete working code.
@@ -520,7 +498,7 @@ documentation][Streamly].
520### Reactive Programming
498### Reactive Programming
522Streamly supports reactive (time domain) programming because of its support for
500Streamly supports reactive (time domain) programming because of its support for
523−declarative concurrency. Please see the `Streamly.Internal.Data.Stream.Time`
501+declarative concurrency. Please see the `Streamly.Data.Stream.Prelude`
524module for time-specific and sampling combinators.
502module for time-specific and sampling combinators.
526The examples [AcidRain.hs][] and [CirclingSquare.hs][] demonstrate
504The examples [AcidRain.hs][] and [CirclingSquare.hs][] demonstrate