streaming 0.1.4.3 → 0.1.4.4
raw patch · 3 files changed
+221/−205 lines, 3 filesdep +containers
Dependencies added: containers
Files
- src/Streaming/Internal.hs +14/−14
- src/Streaming/Prelude.hs +158/−157
- streaming.cabal +49/−34
src/Streaming/Internal.hs view
@@ -738,7 +738,7 @@ {-# INLINABLE unexposed #-} -{- Wrap a new layer of a stream. So, e.g.+{-| Wrap a new layer of a stream. So, e.g. > S.cons :: Monad m => a -> Stream (Of a) m r -> Stream (Of a) m r > S.cons a str = wrap (a :> str)@@ -800,14 +800,12 @@ zipsWith phi s t = loop (s,t) where loop (s1, s2) = Effect (go s1 s2) go s1 s2 = do - e <- inspect s1- case e of- Left r -> return (Return r)- Right fstr -> do - e <- inspect s2- case e of- Left r -> return (Return r)- Right gstr -> return $ Step $ fmap loop (phi fstr gstr)+ e <- inspect s1+ e' <- inspect s2+ case (e,e') of+ (Left r, _) -> return (Return r)+ (_, Left r) -> return (Return r)+ (Right fstr, Right gstr) -> return $ Step $ fmap loop (phi fstr gstr) {-# INLINABLE zipsWith #-} zips :: (Monad m, Functor f, Functor g)@@ -839,13 +837,12 @@ {-| Swap the order of functors in a sum of functors. -->>> S.toListM' $ S.print $ separate $ maps S.switch $ maps (S.distinguish (=='a')) $ S.each "banana"+>>> S.toList $ S.print $ separate $ maps S.switch $ maps (S.distinguish (=='a')) $ S.each "banana" 'a' 'a' 'a' "bnn" :> ()->>> S.toListM' $ S.print $ separate $ maps (S.distinguish (=='a')) $ S.each "banana"+>>> S.toList $ S.print $ separate $ maps (S.distinguish (=='a')) $ S.each "banana" 'b' 'n' 'n'@@ -859,8 +856,9 @@ {-| Given a stream on a sum of functors, make it a stream on the left functor, with the streaming on the other functor as the governing monad. This is- useful for acting on one or the other functor with a fold. It generalizes- 'Data.Either.partitionEithers' massively, but actually streams properly.+ useful for acting on one or the other functor with a fold, leaving the+ other material for another treatment. It generalizes+ 'Data.Either.partitionEithers', but actually streams properly. >>> let odd_even = S.maps (S.distinguish even) $ S.each [1..10::Int] >>> :t separate odd_even@@ -907,6 +905,8 @@ (effect . lift) return {-#INLINABLE separate #-}++ unseparate :: (Monad m, Functor f, Functor g) => Stream f (Stream g m) r -> Stream (Sum f g) m r unseparate str = destroyExposed
src/Streaming/Prelude.hs view
@@ -1,14 +1,15 @@ {-| This names exported by this module are closely modeled on those in @Prelude@ and @Data.List@, but also on- <http://hackage.haskell.org/package/pipes-4.1.9/docs/Pipes-Prelude.html @Pipes.Prelude@>,- <http://hackage.haskell.org/package/pipes-group-1.0.3/docs/Pipes-Group.html @Pipes.Group@>- and <http://hackage.haskell.org/package/pipes-parse-3.0.6/docs/Pipes-Parse.html @Pipes.Parse@>.+ <http://hackage.haskell.org/package/pipes-4.1.9/docs/Pipes-Prelude.html Pipes.Prelude>,+ <http://hackage.haskell.org/package/pipes-group-1.0.3/docs/Pipes-Group.html Pipes.Group>+ and <http://hackage.haskell.org/package/pipes-parse-3.0.6/docs/Pipes-Parse.html Pipes.Parse>. The module may be said to give independent expression to the conception of- Producer / Source / Generator manipulation+ Producer \/ Source \/ Generator manipulation articulated in the latter two modules. Because we dispense with piping and conduiting, the distinction between all of these modules collapses. Some things are- lost but much is gained in that everything comes much closer to ordinary- beginning Haskell programming. The leading type is chosen to permit an api+ lost but much is gained: on the one hand, everything comes much closer to ordinary+ beginning Haskell programming and, on the other, acquires the plasticity of programming + directly with a general free monad type. The leading type, @Stream (Of a) m r@ is chosen to permit an api that is as close as possible to that of @Data.List@ and the @Prelude@. Import qualified thus:@@ -57,8 +58,6 @@ -- $producers , yield , each- , each'- , unfoldr , stdinLn , readLn , fromHandle@@ -74,6 +73,8 @@ , enumFrom , enumFromThen , seconds+ , unfoldr+ -- * Consuming streams of elements@@ -99,7 +100,7 @@ , with , subst , copy- , copy'+ , duplicate , store , chain , sequence@@ -122,8 +123,7 @@ , read , show , cons- , duplicate- , duplicate'+ , slidingWindow -- * Splitting and inspecting streams of elements@@ -131,14 +131,13 @@ , uncons , splitAt , split--- , breaks+ , breaks , break , breakWhen , span , group , groupBy -- , groupedBy- -- , split -- * Sum and Compose manipulation@@ -248,6 +247,7 @@ import Data.Foldable (Foldable) import Data.Traversable (Traversable) import qualified Data.Foldable as Foldable+import qualified Data.Sequence as Seq import Text.Read (readMaybe) import Prelude hiding (map, mapM, mapM_, filter, drop, dropWhile, take, mconcat , sum, product, iterate, repeat, cycle, replicate, splitAt@@ -542,19 +542,19 @@ -- [False] -- -- -}--- breaks--- :: Monad m =>--- (a -> Bool) -> Stream (Of a) m r -> Stream (Stream (Of a) m) m r--- breaks thus = loop where--- loop stream = Effect $ do--- e <- next stream--- return $ case e of--- Left r -> Return r--- Right (a, p') ->--- if not (thus a)--- then Step $ fmap loop (yield a >> break thus p')--- else loop p'--- {-#INLINABLE breaks #-}+breaks+ :: Monad m =>+ (a -> Bool) -> Stream (Of a) m r -> Stream (Stream (Of a) m) m r+breaks thus = loop where+ loop stream = Effect $ do+ e <- next stream+ return $ case e of+ Left r -> Return r+ Right (a, p') ->+ if not (thus a)+ then Step $ fmap loop (yield a >> break thus p')+ else loop p'+{-#INLINABLE breaks #-} {-| Apply an action to all values, re-yielding each @@ -627,7 +627,7 @@ > cycle = forever ->>> rest <- S.print $ S.splitAt 3 $ S.cycle (yield 0 >> yield 1)+>>> rest <- S.print $ S.splitAt 3 $ S.cycle (yield True >> yield False) True False True@@ -757,23 +757,13 @@ 1 2 3->>> S.print $ mapped S.toList $ chunksOf 3 $ S.replicateM 5 getLine-s<Enter>-t<Enter>-u<Enter>-["s","t","u"]-v<Enter>-w<Enter>-["v","w"] + -} each :: (Monad m, Foldable.Foldable f) => f a -> Stream (Of a) m () each = Foldable.foldr (\a p -> Step (a :> p)) (Return ()) {-# INLINABLE each #-} -each' :: (Monad m, Foldable.Foldable f) => f a -> Stream (Of a) m ()-each' = Foldable.foldr (\a p -> Effect (return (Step (a :> p)))) (Return ())-{-# INLINABLE each' #-} -- --------------- -- effects@@ -786,6 +776,7 @@ 3 4 5+ 'effects' should be understood together with 'copy' and is subject to the rules > S.effects . S.copy = id@@ -835,7 +826,7 @@ -- ------ {-| An infinite stream of enumerable values, starting from a given value.- It is the same as `S.iterate succ`.+ It is the same as @S.iterate succ@. Because their return type is polymorphic, @enumFrom@ and @enumFromThen@ (and @iterate@ are useful for example with @zip@ and @zipWith@, which require the same return type in the zipped streams.@@ -897,12 +888,13 @@ filter pred = loop where loop str = case str of Return r -> Return r- Effect m -> Effect (liftM loop m)+ Effect m -> Effect (liftM loop m) Step (a :> as) -> if pred a then Step (a :> loop as) else loop as-{-# INLINABLE filter #-}+{-# INLINE filter #-} -- ~ 10% faster than INLINABLE in simple bench + -- --------------- -- filterM -- ---------------@@ -912,14 +904,13 @@ filterM pred = loop where loop str = case str of Return r -> Return r- Effect m -> Effect $ liftM loop m+ Effect m -> Effect $ liftM loop m Step (a :> as) -> Effect $ do bool <- pred a if bool then return $ Step (a :> loop as) else return $ loop as-{-# INLINABLE filterM #-}-+{-# INLINE filterM #-} -- ~ 10% faster than INLINABLE in simple bench -- -- --------------- -- -- first@@ -1355,12 +1346,12 @@ {-| Reduce a stream to its return value with a monadic action. ->>> S.mapM_ Prelude.print $ each [1..5]+>>> S.mapM_ Prelude.print $ each [1..3] 1 2 3-4-5++ >>> rest <- S.mapM_ Prelude.print $ S.splitAt 3 $ each [1..10] 1 2@@ -1613,6 +1604,7 @@ one<Enter> two<Enter> ["one","two"]+ -} repeatM :: Monad m => m a -> Stream (Of a) m r@@ -1627,7 +1619,7 @@ -- replicate -- --------------- --- | Repeat an element several times+-- | Repeat an element several times. replicate :: Monad m => Int -> a -> Stream (Of a) m () replicate n a | n <= 0 = return () replicate n a = loop n where@@ -1635,7 +1627,7 @@ loop m = Effect (return (Step (a :> loop (m-1)))) {-# INLINABLE replicate #-} -{-| Repeat an action several times, streaming the results.+{-| Repeat an action several times, streaming its results. >>> S.print $ S.replicateM 2 getCurrentTime 2015-08-18 00:57:36.124508 UTC@@ -1648,7 +1640,7 @@ loop 0 = Return () loop n = Effect $ do a <- ma- return (Step $ a :> loop (n-1))+ return (Step (a :> loop (n-1))) {-# INLINABLE replicateM #-} {-| Read an @IORef (Maybe a)@ or a similar device until it reads @Nothing@.@@ -1666,7 +1658,8 @@ Just a -> return (Step (a :> loop)) {-# INLINABLE reread #-} -{-| Strict left scan, streaming, e.g. successive partial results.+{-| Strict left scan, streaming, e.g. successive partial results. The seed + is yielded first, before any action of finding the next element is performed. >>> S.print $ S.scan (++) "" id $ each (words "a b c d")@@ -1686,13 +1679,15 @@ -} scan :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Stream (Of a) m r -> Stream (Of b) m r-scan step begin done = loop begin- where+scan step begin done str = Step (done begin :> loop begin str)+ where loop !acc stream = do case stream of- Return r -> Step (done acc :> Return r)+ Return r -> Return r Effect m -> Effect (liftM (loop acc) m)- Step (a :> rest) -> Step (done acc :> loop (step acc a) rest)+ Step (a :> rest) -> + let !acc' = step acc a + in Step (done acc' :> loop acc' rest) {-#INLINABLE scan #-} {-| Strict left scan, accepting a monadic function. It can be used with@@ -1709,23 +1704,22 @@ -} scanM :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Stream (Of a) m r -> Stream (Of b) m r-scanM step begin done str = do- x <- lift begin- loop x str+scanM step begin done str = Effect $ do+ x <- begin+ b <- done x+ return (Step (b :> loop x str)) where- loop !x stream = do- b <- lift (done x)- yield b- case stream of- Return r -> Return r- Effect m -> Effect (do- stream' <- m- return (loop x stream')- )- Step (a :> rest) -> Effect (do- x' <- step x a- return (loop x' rest)- )+ loop !x stream = case stream of -- note we have already yielded from x+ Return r -> Return r+ Effect m -> Effect (do+ stream' <- m+ return (loop x stream')+ )+ Step (a :> rest) -> Effect (do+ x' <- step x a+ b <- done x'+ return (Step (b :> loop x' rest))+ ) {-# INLINABLE scanM #-} {- Label each element in a stream with a value accumulated according to a fold.@@ -1785,7 +1779,7 @@ five<Enter> ["one","two","three","four","five"] :> () - This is of course does not interrupt an action that has already begun.+ This of course does not interrupt an action that has already begun. -} @@ -2025,14 +2019,20 @@ > mapped toList :: Stream (Stream (Of a)) m r -> Stream (Of [a]) m - Like 'toList_', it breaks streaming; unlike 'toList_' it preserves- the return value and thus is frequently useful with e.g. 'mapped'+ Like 'toList_', 'toList' breaks streaming; unlike 'toList_' it /preserves the return value/ + and thus is frequently useful with e.g. 'mapped' >>> S.print $ mapped S.toList $ chunksOf 3 $ each [1..9] [1,2,3] [4,5,6] [7,8,9]-+>>> S.print $ mapped S.toList $ chunksOf 2 $ S.replicateM 4 getLine+s<Enter>+t<Enter>+["s","t"]+u<Enter>+v<Enter>+["u","v"] -} toList :: Monad m => Stream (Of a) m r -> m (Of [a] r) toList = fold (\diff a ls -> diff (a: ls)) id (\diff -> diff [])@@ -2055,29 +2055,32 @@ {-# INLINABLE uncons #-} -{-| Build a @Stream@ by unfolding steps starting from a seed.+{-| Build a @Stream@ by unfolding steps starting from a seed. In particular note+ that @S.unfoldr S.next = id@. The seed can of course be anything, but this is one natural way to consume a @pipes@ 'Pipes.Producer'. Consider: ->>> S.stdoutLn $ S.take 2 $ S.unfoldr P.next P.stdinLn+>>> S.stdoutLn $ S.take 2 $ S.unfoldr Pipes.next Pipes.stdinLn hello<Enter> hello goodbye<Enter> goodbye ->>> S.stdoutLn $ S.unfoldr P.next (P.stdinLn P.>-> P.take 2)+>>> S.stdoutLn $ S.unfoldr Pipes.next (Pipes.stdinLn >-> Pipes.take 2) hello<Enter> hello goodbye<Enter> goodbye ->>> S.effects $ S.unfoldr P.next (P.stdinLn P.>-> P.take 2 P.>-> P.stdoutLn)+>>> S.effects $ S.unfoldr Pipes.next (Pipes.stdinLn >-> Pipes.take 2 >-> Pipes.stdoutLn) hello<Enter> hello goodbye<Enter> goodbye + @Pipes.unfoldr S.next@ similarly unfolds a @Pipes.Producer@ from a stream.+ -} unfoldr :: Monad m => (s -> m (Either r (a, s))) -> s -> Stream (Of a) m r@@ -2137,18 +2140,17 @@ >>> stdoutLn $ yield "hello" hello ->>> S.sum $ do {yield 1; yield 2}-3+>>> S.sum $ do {yield 1; yield 2; yield 3}+6 ->>> let prompt = putStrLn "Enter a number:"->>> let number = lift (prompt >> readLn) >>= yield :: Stream (Of Int) IO ()+>>> let number = lift (putStrLn "Enter a number:") >> lift readLn >>= yield :: Stream (Of Int) IO () >>> S.toList $ do {number; number; number} Enter a number:-1+1<Enter> Enter a number:-2+2<Enter> Enter a number:-3+3<Enter> [1,2,3] :> () -}@@ -2222,8 +2224,7 @@ -- IO fripperies -- -------------- -{-| View standard input as a 'Stream (Of String) m r'. 'stdoutLn', by- contrast, renders a 'Stream (Of String) m r' to standard output. The names+{-| View standard input as a @Stream (Of String) m r@. By contrast, 'stdoutLn' renders a @Stream (Of String) m r@ to standard output. The names follow @Pipes.Prelude@ >>> stdoutLn stdinLn@@ -2246,26 +2247,28 @@ stdinLn = fromHandle IO.stdin {-# INLINABLE stdinLn #-} -{-| Read values from 'IO.stdin', ignoring failed parses+{-| Read values from 'IO.stdin', ignoring failed parses. ->>> S.sum_ $ S.take 2 S.readLn :: IO Int+>>> :set -XTypeApplications+>>> S.sum $ S.take 2 (S.readLn @IO @Int) 10<Enter> 12<Enter>-22+22 :> () ->>> S.toList $ S.take 3 (S.readLn :: Stream (Of Int) IO ())-1<Enter>-2<Enter>+>>> S.toList $ S.take 2 (S.readLn @IO @Int)+10<Enter> 1@#$%^&*\<Enter>-3<Enter>-[1,2,3] :> ()+12<Enter>+[10,12] :> () -} readLn :: (MonadIO m, Read a) => Stream (Of a) m ()-readLn = for stdinLn $ \str -> case readMaybe str of- Nothing -> return ()- Just n -> yield n+readLn = do+ str <- liftIO getLine+ case readMaybe str of+ Nothing -> readLn+ Just n -> yield n >> readLn {-# INLINABLE readLn #-} @@ -2292,7 +2295,7 @@ {-| Write a succession of strings to a handle as separate lines. ->>> S.toHandle IO.stdout $ each $ words "one two three"+>>> S.toHandle IO.stdout $ each (words "one two three") one two three@@ -2310,9 +2313,9 @@ {-| Print the elements of a stream as they arise. >>> S.print $ S.take 2 S.stdinLn-hello+hello<Enter> "hello"-world+world<Enter> "world" >>> @@ -2321,14 +2324,14 @@ print = loop where loop stream = case stream of Return r -> return r- Effect m -> m >>= loop+ Effect m -> m >>= loop Step (a :> rest) -> do liftIO (Prelude.print a) loop rest {-| Write 'String's to 'IO.stdout' using 'putStrLn'; terminates on a broken output pipe- (This operation is modelled on 'Pipes.Prelude.stdoutLn').+ (The name and implementation are modelled on the @Pipes.Prelude@ @stdoutLn@). >>> S.stdoutLn $ S.take 3 $ S.each $ words "one two three four five" one@@ -2354,29 +2357,22 @@ - {-| Write 'String's to 'IO.stdout' using 'putStrLn' - This does not handle a broken output pipe, but has a polymorphic return- value, which makes this possible:+ Unlike @stdoutLn@, @stdoutLn'@ does not handle a broken output pipe. Thus it can have a polymorphic return+ value, rather than @()@, and this kind of \"connect and resume\" is possible: >>> rest <- S.stdoutLn' $ S.show $ S.splitAt 3 (each [1..5]) 1 2 3->>> S.print rest-4-5+>>> S.toList rest+[4,5] :> () -} stdoutLn' :: MonadIO m => Stream (Of String) m r -> m r-stdoutLn' = loop where- loop stream = case stream of- Return r -> return r- Effect m -> m >>= loop- Step (s :> rest) -> liftIO (putStrLn s) >> loop rest-{-# INLINE stdoutLn' #-}+stdoutLn' = toHandle IO.stdout {-| Read the lines of a file as Haskell 'String's @@ -2398,15 +2394,15 @@ readFile :: MonadResource m => FilePath -> Stream (Of String) m () readFile f = bracketStream (IO.openFile f IO.ReadMode) (IO.hClose) fromHandle -{-| Write a series of strings as lines to a file. The handle is crudely- managed with 'ResourceT':+{-| Write a series of strings as lines to a file. The handle is+ managed with 'ResourceT' (see the remarks on 'readFile'): >>> runResourceT $ S.writeFile "lines.txt" $ S.take 2 S.stdinLn hello<Enter> world<Enter>->>> runResourceT $ S.print $ S.readFile "lines.txt"-"hello"-"world"+>>> runResourceT $ S.stdoutLn $ S.readFile "lines.txt"+hello+world -} writeFile :: MonadResource m => FilePath -> Stream (Of String) m r -> m r@@ -2577,9 +2573,9 @@ > instance (Functor f, MonadIO m) => MonadIO (Stream f m) We thus can't be touching the elements of the stream, or the final return value.- It it is the same with other constraints that @Stream (Of a)@ inherits,- like 'MonadResource'. Thus I can filter and write to one file, but- nub and write to another, or to a database or the like:+ It is the same with other constraints that @Stream (Of a)@ inherits from the underlying monad,+ like 'MonadResource'. Thus I can independently filter and write to one file, but+ nub and write to another, or interact with a database and a logfile and the like: >>> runResourceT $ (S.writeFile "hello2.txt" . S.nub) $ store (S.writeFile "hello.txt" . S.filter (/= "world")) $ each ["hello", "world", "goodbye", "world"] >>> :! cat hello.txt@@ -2608,7 +2604,7 @@ one two - With copy, I can as well do:+ With copy, I can do these simultaneously: >>> S.print $ S.stdoutLn $ S.copy $ each ["one","two"] one@@ -2675,7 +2671,6 @@ Return r -> Return r Effect m -> Effect (liftM loop (lift m)) Step (a :> rest) -> Effect (Step (a :> Return (Step (a :> loop rest))))- {-#INLINABLE copy#-} duplicate@@ -2684,27 +2679,6 @@ duplicate = copy {-#INLINE duplicate #-} --{-| @copy'@ is the same as @copy@ but reverses the order of interleaved effects.- The difference should not be observable at all for pure folds over the data.---}-copy'- :: Monad m =>- Stream (Of a) m r -> Stream (Of a) (Stream (Of a) m) r-copy' = Effect . return . loop where- loop str = case str of- Return r -> Return r- Effect m -> Effect (liftM loop (lift m))- Step (a :> rest) -> Step (a :> Effect (Step (a :> Return (loop rest))))-{-#INLINABLE copy' #-}--duplicate'- :: Monad m =>- Stream (Of a) m r -> Stream (Of a) (Stream (Of a) m) r-duplicate' = copy'-{-#INLINE duplicate' #-}- {-| The type > Data.List.unzip :: [(a,b)] -> ([a],[b])@@ -2761,15 +2735,6 @@ {-#INLINABLE unzip #-} --- "fold/map" forall step begin done f str .--- fold step begin done (map f str) = fold (\x a -> step x $! f a) begin done str;------ "fold/filter" forall step begin done pred str .--- fold step begin done (filter pred str) = fold (\x a -> if pred a then step x a else x) begin done str;------ "scan/map" forall step begin done f str .--- scan step begin done (map f str) = scan (\x a -> step x $! f a) begin done str--- {- $maybes These functions discard the 'Nothing's that they encounter. They are analogous@@ -2777,7 +2742,8 @@ -} {-| The 'catMaybes' function takes a 'Stream' of 'Maybe's and returns- a 'Stream' of all of the 'Just' values.+ a 'Stream' of all of the 'Just' values. 'concat' has the same behavior,+ but is more general; it works for any foldable container type. -} catMaybes :: Monad m => Stream (Of (Maybe a)) m r -> Stream (Of a) m r catMaybes = loop where@@ -2792,6 +2758,7 @@ {-| The 'mapMaybe' function is a version of 'map' which can throw out elements. In particular, the functional argument returns something of type @'Maybe' b@. If this is 'Nothing', no element is added on to the result 'Stream'. If it is @'Just' b@, then @b@ is included in the result 'Stream'.+ -} mapMaybe :: Monad m => (a -> Maybe b) -> Stream (Of a) m r -> Stream (Of b) m r mapMaybe phi = loop where@@ -2803,3 +2770,37 @@ Just b -> Step (b :> loop snext) {-#INLINABLE mapMaybe #-} +{-| 'slidingWindow' accumulates the first @n@ elements of a stream, + update thereafter to form a sliding window of length @n@.+ It follows the behavior of the slidingWindow function in + <https://hackage.haskell.org/package/conduit-combinators-1.0.4/docs/Data-Conduit-Combinators.html#v:slidingWindow conduit-combinators>.++>>> S.print $ slidingWindow 4 $ S.each "123456"+fromList "1234"+fromList "2345"+fromList "3456"++-}++slidingWindow :: Monad m + => Int + -> Stream (Of a) m b + -> Stream (Of (Seq.Seq a)) m b+slidingWindow n = setup (max 1 n :: Int) mempty + where + window !sequ str = do + e <- lift (next str) + case e of + Left r -> return r+ Right (a,rest) -> do + yield (sequ Seq.|> a)+ window (Seq.drop 1 sequ Seq.|> a) rest+ setup 0 !sequ str = do+ yield sequ + window (Seq.drop 1 sequ) str + setup n sequ str = do + e <- lift $ next str + case e of + Left r -> yield sequ >> return r+ Right (x,rest) -> setup (n-1) (sequ Seq.|> x) rest+{-#INLINABLE slidingWindow #-}
streaming.cabal view
@@ -1,60 +1,76 @@ name: streaming-version: 0.1.4.3+version: 0.1.4.4 cabal-version: >=1.10 build-type: Simple synopsis: an elementary streaming prelude and general stream type. -description: @Streaming.Prelude@ exports an elementary streaming prelude focused on+description: This package contains two modules, <http://hackage.haskell.org/package/streaming-0.1.4.3/docs/Streaming.html Streaming> + and <http://hackage.haskell.org/package/streaming-0.1.4.3/docs/Streaming-Prelude.html Streaming.Prelude>.+ The principal module, <http://hackage.haskell.org/package/streaming-0.1.4.3/docs/Streaming-Prelude.html Streaming.Prelude>, exports an elementary streaming prelude focused on a simple \"source\" or \"producer\" type, namely @Stream (Of a) m r@.- @Stream (Of a) m r@ is a sort of effectful version of- @([a],r)@ in which successive elements arise from some sort of monadic- action. Everything in the library is organized to make+ This is a sort of effectful version of+ @([a],r)@ in which successive elements of type @a@ arise from some sort of monadic+ action before the succession ends with a value of type @r@. + Everything in the library is organized to make programming with this type as simple as possible, by the simple expedient of making it as close to @Prelude@ and @Data.List@ as possible. Thus for example the trivial program .- > S.sum (S.take 3 (S.readLn :: Stream (Of Integer) IO ()))+ > >>> S.sum $ S.take 3 (S.readLn :: Stream (Of Int) IO ())+ > 1<Enter>+ > 2<Enter>+ > 3<Enter>+ > 6 :> () . sums the first three valid integers from user input. Similarly, .- > S.stdoutLn (S.map reverse (S.take 3 S.stdinLn))+ > >>> S.stdoutLn $ S.map (map toUpper) $ S.take 2 S.stdinLn + > hello<Enter>+ > HELLO+ > world!<Enter>+ > WORLD! .- reverses the first three lines from stdin as they arise,+ upper-cases the first two lines from stdin as they arise, and sends them to stdout. And so on,- with filtering, mapping, breaking, chunking and so forth.- We program with streams of @Int@s or @String@s directly as- if they constituted something like a list. And we everywhere- oppose \"extracting a list from IO\",+ with filtering, mapping, breaking, chunking, zipping, unzipping, replicating + and so forth: + we program with streams of @Int@s or @String@s directly as+ if they constituted something like a list. That's because streams really do constitute something+ like a list, and the associated operations can mostly have the same names. + (A few, like @reverse@, don't stream and thus disappear; + others like @unzip@ are here given properly streaming formulation for the first time.) + And we everywhere+ oppose \"extracting a pure list from IO\", which is the origin of typical Haskell memory catastrophes. Basically any case where you are tempted to use @mapM@, @replicateM@, @traverse@ or @sequence@ with Haskell lists, you would do better to use something like @Stream (Of a) m r@. The type signatures are a little fancier, but- the programs themselves are mostly the same or simpler. Thus,+ the programs themselves are mostly the same. /In fact, they are mostly simpler./ Thus, consider the trivial demo program mentioned in <http://stackoverflow.com/questions/24068399/haskell-performance-of-iorefs this SO question> . > main = mapM newIORef [1..10^8::Int] >>= mapM readIORef >>= mapM_ print .- It quickly exhausts memory, of course, and this has nothing to do with- the efficiency of @IORefs@. It is immediately cured by writing+ The new user notices that this exhausts memory, and worries about the efficiency of Haskell @IORefs@. + But of course it exhausts memory! Look what it says!+ The problem is immediately cured by writing .- > import qualified Streaming.Prelude as S- > main = S.print (S.mapM readIORef (S.mapM newIORef (S.each [1..10^8::Int])))+ > main = S.print $ S.mapM readIORef $ S.mapM newIORef $ S.each [1..10^8::Int] . which really does what the other program was meant to do,- uses no more memory than @hello-world@, and is simpler anyway, since it- doesn't involve \"extracting a list from IO\". Almost+ uses no more memory than @hello-world@, /and is simpler anyway/, since it+ doesn't involve the detour of \"extracting a list from IO\". Almost every use of list @mapM@, @replicateM@, @traverse@ and @sequence@ produces this problem on a smaller scale. People get used to it, as if it were- characteristic of Haskell programs to use a lot of memory, when- \"extracting a list or sequence from IO\" is just bad practice pure and simple.+ characteristic of Haskell programs to use a lot of memory. But in truth+ \"extracting a list or sequence from IO\" is mostly just bad practice pure and simple. Of course, @mapM@, @replicateM@, @traverse@ and @sequence@ make sense for lists,- under certain conditions. Similarly, @unsafePerformIO@ makes sense under+ under certain conditions! But @unsafePerformIO@ also makes sense under certain conditions. .- The @Streaming@ module exports the general type,+ The <http://hackage.haskell.org/package/streaming-0.1.4.3/docs/Streaming.html Streaming> module exports the general type, @Stream f m r@, which can be used to stream successive distinct steps characterized by /any/ functor @f@, though we are mostly interested in organizing computations@@ -70,23 +86,21 @@ . > group :: Ord a => [a] -> [[a]] > chunksOf :: Int -> [a] -> [[a]]- > lines :: [Char] -> [[Char]] -- but similarly with bytestring, etc.+ > lines :: [Char] -> [[Char]] -- but similarly with byte streams, etc. . to mention a few obviously desirable operations. (This is explained more elaborately in the <https://hackage.haskell.org/package/streaming#readme readme> below.)- One could throw something- like @Stream@ on top of a prior stream concept: this is how @pipes@ and+ .+ One could throw of course throw something+ like the present @Stream@ type on top of a prior stream concept: this is how @pipes@ and @pipes-group@ (which are very much our model here) use @FreeT@. But once one grasps the iterable stream concept needed to express- those functions -- the one here given a somewhat optimized implementation as @Stream f m r@- (the specific optimization again follows the model of the @pipes@ library) -- then one will also see that,+ those functions then one will also see that, with it, one is /already/ in possession of a complete elementary streaming library - since one possesses @Stream ((,) a) m r@ or equivalently @Stream (Of a) m r@. This- is the type of a \'generator\' or \'producer\' or whatever- you call an effectful stream of items.+ is the type of a \'generator\' or \'producer\' or \'source\' or whatever+ you call an effectful stream of items. /The present Streaming.Prelude is thus the simplest streaming library that can replicate anything like the API of the Prelude and Data.List/. . The emphasis of the library is on interoperation; for@@ -103,8 +117,8 @@ a complex framework, but in a way that integrates transparently with the rest of Haskell, using ideas - e.g. rank 2 types, which are here implicit or explicit in most mapping - that the user can carry elsewhere,- rather than binding her intelligence to a so-called streaming IO framework (as- necessary as that is for certain purposes.)+ rather than chaining her understanding to the curiosities of + a so-called streaming IO framework (as necessary as that is for certain purposes.) . See the <https://hackage.haskell.org/package/streaming#readme readme>@@ -206,6 +220,7 @@ , monad-control >=0.3.1 && <1.1 , time , ghc-prim+ , containers hs-source-dirs: src default-language: Haskell2010