streamly-examples 0.1.2 → 0.1.3
raw patch · 33 files changed
+915/−471 lines, 33 filesdep +streamly-coredep +tasty-benchdep ~basedep ~fusion-plugindep ~hashablenew-component:exe:DateTimeParsernew-component:exe:LogParser
Dependencies added: streamly-core, tasty-bench
Dependency ranges changed: base, fusion-plugin, hashable, streamly, transformers, vector
Files
- Changelog.md +4/−0
- README.md +8/−5
- examples/AcidRain.hs +31/−28
- examples/CSVParser.hs +14/−12
- examples/CamelCase.hs +7/−4
- examples/CirclingSquare.hs +2/−2
- examples/CmdClient.hs +24/−16
- examples/CmdServer.hs +30/−23
- examples/ControlFlow.hs +56/−74
- examples/CoreUtils.hs +27/−28
- examples/CoreUtilsHandle.hs +21/−17
- examples/DateTimeParser.hs +233/−0
- examples/EchoServer.hs +7/−7
- examples/FileSender.hs +7/−7
- examples/FileSystemEvent.hs +5/−4
- examples/Interop/Conduit.hs +6/−5
- examples/Interop/Pipes.hs +7/−6
- examples/Interop/Streaming.hs +7/−6
- examples/Interop/Vector.hs +7/−6
- examples/Intro.hs +42/−35
- examples/ListDir.hs +27/−18
- examples/LogParser.hs +58/−0
- examples/MergeServer.hs +20/−15
- examples/MergeSort.hs +99/−19
- examples/Rate.hs +7/−7
- examples/Split.hs +12/−11
- examples/WordCount.hs +7/−6
- examples/WordCountModular.hs +5/−7
- examples/WordCountParallel.hs +20/−13
- examples/WordCountParallelUTF8.hs +44/−38
- examples/WordFrequency.hs +26/−28
- examples/WordServer.hs +17/−15
- streamly-examples.cabal +28/−9
Changelog.md view
@@ -1,5 +1,9 @@ # Changelog +## 0.1.3 (Mar 2023)++* Support streamly-0.9.0 and streamly-core-0.1.0+ ## 0.1.2 (Mar 2022) * Support streamly-0.8.2
README.md view
@@ -11,16 +11,18 @@ ## Running The Examples -Executable names are the same as the filenames. To run an example:+When running the unstable version (downloaded from the git repository)+you must use `--project-file cabal.project.user` option otherwise the+build might fail. For example: ```-$ cabal run AcidRain+$ cabal run --project-file cabal.project.user AcidRain ``` -For performance sensitive examples use fusion-plugin for best performance:+Executable names are the same as the filenames. To run an example: ```-$ cabal run --flag fusion-plugin WordCount -- streamly-examples.cabal+$ cabal run AcidRain ``` To run SDL2 based examples, make sure that you have the OS package for@@ -43,7 +45,8 @@ * [Intro](examples/Intro.hs): Simple, introductory examples - loops, text processing, networking, concurrency.-* [MergeSort](examples/MergeSort.hs): Merge sorted streams concurrently.+* [MergeSort](examples/MergeSort.hs): Sort a stream concurrently using merge+ sort. * [Rate](examples/Rate.hs): Run an action at a given rate. ### FileSystem
examples/AcidRain.hs view
@@ -7,20 +7,17 @@ -- This example is adapted from Gabriel Gonzalez's pipes-concurrency package. -- https://hackage.haskell.org/package/pipes-concurrency-2.0.8/docs/Pipes-Concurrent-Tutorial.html -#if !(MIN_VERSION_base(4,11,0))-import Data.Semigroup ((<>))-#endif-import Control.Monad (void) import Control.Monad.IO.Class (MonadIO(liftIO))-import Control.Monad.State (MonadState, get, modify, runStateT)+import Control.Monad.State (MonadState, get, modify) import Data.Function ((&))-import Streamly.Prelude (MonadAsync, SerialT)+import Streamly.Data.Stream.Prelude (MonadAsync, Stream) -import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream -data Event = Quit | Harm Int | Heal Int deriving (Show)+data Event = Quit | Harm Int | Heal Int deriving (Eq, Show) -userAction :: MonadAsync m => SerialT m Event+userAction :: MonadAsync m => Stream m Event userAction = Stream.repeatM $ liftIO askUser where askUser = do@@ -31,22 +28,26 @@ "quit" -> return Quit _ -> putStrLn "Type potion or harm or quit" >> askUser -acidRain :: MonadAsync m => SerialT m Event-acidRain =- Stream.repeatM (liftIO $ return $ Harm 1) -- AsyncT m Event- & Stream.constRate 1 -- AsyncT m Event- & Stream.fromAsync -- SerialT m Event+acidRain :: MonadAsync m => Stream m Event+acidRain = Stream.parRepeatM (Stream.constRate 1) (return $ Harm 1) +parallel :: MonadAsync m => [Stream m a] -> Stream m a+parallel = Stream.parList (Stream.eager True)+ data Result = Check | Done -runEvents :: (MonadAsync m, MonadState Int m) => SerialT m Result-runEvents = do- event <- userAction `Stream.parallel` acidRain -- SerialT m Event- case event of- Harm n -> modify (\h -> h - n) >> return Check- Heal n -> modify (\h -> h + n) >> return Check- Quit -> return Done+runEvents :: (MonadAsync m, MonadState Int m) => Stream m Result+runEvents =+ Stream.mapM f $ parallel [userAction, acidRain] + where++ f event =+ case event of+ Harm n -> modify (\h -> h - n) >> return Check+ Heal n -> modify (\h -> h + n) >> return Check+ Quit -> return Done+ data Status = Alive | GameOver deriving Eq getStatus :: (MonadAsync m, MonadState Int m) => Result -> m Status@@ -55,15 +56,17 @@ Done -> liftIO $ putStrLn "You quit!" >> return GameOver Check -> do h <- get- liftIO $ if (h <= 0)- then putStrLn "You die!" >> return GameOver- else putStrLn ("Health = " <> show h) >> return Alive+ liftIO+ $ if (h <= 0)+ then putStrLn "You die!" >> return GameOver+ else putStrLn ("Health = " <> show h) >> return Alive main :: IO () main = do putStrLn "Your health is deteriorating due to acid rain,\\ \ type \"potion\" or \"quit\""- let runGame =- Stream.mapM getStatus runEvents -- SerialT (StateT Int IO) Status- & Stream.drainWhile (== Alive) -- StateT Int IO ()- void $ runStateT runGame 60+ Stream.mapM getStatus runEvents -- Stream (StateT Int IO) Status+ & Stream.runStateT (pure 60) -- Stream IO (Int, Status)+ & fmap snd -- Stream IO Status+ & Stream.fold (Fold.takeEndBy (/= Alive) Fold.drain) -- IO ()+ return ()
examples/CSVParser.hs view
@@ -4,32 +4,34 @@ import Data.Char (chr) import Data.Function ((&)) import Data.Word (Word8)-import Streamly.Prelude (SerialT)+import Streamly.Data.Stream (Stream) import System.Environment (getArgs) import System.IO (IOMode(..)) -import qualified Streamly.Data.Array.Foreign as Array-import qualified Streamly.FileSystem.Handle as Handle+import qualified Streamly.Data.Array as Array import qualified Streamly.Data.Fold as Fold-import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Stream as Stream+import qualified Streamly.FileSystem.Handle as Handle import qualified System.IO as IO-import qualified Streamly.Internal.Data.Array.Stream.Foreign as ArrayStream+import qualified Streamly.Internal.Data.Stream as Stream (splitOn)+import qualified Streamly.Internal.Data.Stream.Chunked as ArrayStream (splitOn) main :: IO () main = do inFile <- fmap head getArgs src <- IO.openFile inFile ReadMode - Stream.unfold Handle.readChunks src -- SerialT IO (Array Word8)- & ArrayStream.splitOn 10 -- SerialT IO (Array Word8)- & Stream.mapM_ parseLine -- IO ()+ Stream.unfold Handle.chunkReader src -- Stream IO (Array Word8)+ & ArrayStream.splitOn 10 -- Stream IO (Array Word8)+ & Stream.fold (Fold.drainMapM parseLine) -- IO () where printList = putStr . map (chr . fromIntegral)+ parseLine arr =- (Stream.unfold Array.read arr :: SerialT IO Word8)- & Stream.splitOn (== 44) Fold.toList -- SerialT IO [Word8]- & Stream.intersperse [32] -- SerialT IO [Word8]- & Stream.mapM_ printList -- IO ()+ (Stream.unfold Array.reader arr :: Stream IO Word8)+ & Stream.splitOn (== 44) Fold.toList -- Stream IO [Word8]+ & Stream.intersperse [32] -- Stream IO [Word8]+ & Stream.fold (Fold.drainMapM printList) -- IO () >> putStrLn ""
examples/CamelCase.hs view
@@ -6,15 +6,16 @@ import System.Environment (getArgs) import System.IO (Handle, IOMode(..), openFile, stdout) -import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream as Stream import qualified Streamly.FileSystem.Handle as Handle -- | @camelCase source-file dest-file@ camelCase :: Handle -> Handle -> IO () camelCase src dst =- Stream.unfold Handle.read src -- SerialT IO Word8- & Stream.scanl' step (True, Nothing) -- SerialT IO (Bool, Maybe Word8)- & Stream.mapMaybe snd -- SerialT IO Word8+ Stream.unfold Handle.reader src -- Stream IO Word8+ & Stream.postscan fold -- Stream IO (Bool, Maybe Word8)+ & Stream.mapMaybe snd -- Stream IO Word8 & Stream.fold (Handle.write dst) -- IO () where@@ -33,6 +34,8 @@ -- Convert lower to upper if preceded by whitespace | isLower x = (False, Just $ if wasSpace then x - 32 else x) | otherwise = (True, Nothing)++ fold = Fold.foldl' step (True, Nothing) main :: IO () main = do
examples/CirclingSquare.hs view
@@ -77,8 +77,8 @@ ref <- newIORef (0,0) _ <- forkIO $ do- Stream.repeatM (updateDisplay ref rend) -- SerialT IO ()- & Stream.delay (1/60) -- SerialT IO ()+ Stream.repeatM (updateDisplay ref rend) -- Stream IO ()+ & Stream.delay (1/60) -- Stream IO () & Stream.drain -- IO () -- MacOS requires pollEvents to run in the Main thread.
examples/CmdClient.hs view
@@ -4,12 +4,12 @@ import Data.Function ((&)) import Data.Word (Word8) import Network.Socket (PortNumber)-import Streamly.Prelude (SerialT)+import Streamly.Data.Stream.Prelude (Stream) import qualified Streamly.Data.Fold as Fold import qualified Streamly.Data.Unfold as Unfold-import qualified Streamly.Internal.Network.Inet.TCP as TCP (processBytes)-import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Stream.Prelude as Stream+import qualified Streamly.Internal.Network.Inet.TCP as TCP (pipeBytes) import qualified Streamly.Unicode.Stream as Unicode remoteAddr :: (Word8,Word8,Word8,Word8)@@ -31,20 +31,28 @@ putStrLn $ tag ++ show (i * chunkSize) return i -sender :: SerialT IO ()+sender :: Stream IO () sender =- Stream.repeat "time\nrandom\n" -- SerialT IO String- & Stream.unfoldMany Unfold.fromList -- SerialT IO Char- & Unicode.encodeLatin1 -- SerialT IO Word8- & TCP.processBytes remoteAddr remotePort -- SerialT IO Word8- & Unicode.decodeLatin1 -- SerialT IO Char- & Stream.splitOnSuffix (== '\n') Fold.drain -- SerialT IO String- & Stream.chunksOf chunkSize Fold.drain -- SerialT IO ()+ Stream.repeat "time\nrandom\n" -- Stream IO String+ & Stream.unfoldMany Unfold.fromList -- Stream IO Char+ & Unicode.encodeLatin1 -- Stream IO Word8+ & TCP.pipeBytes remoteAddr remotePort -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & split (== '\n') Fold.drain -- Stream IO String+ & Stream.foldMany chunk -- Stream IO () + where++ chunk = Fold.take chunkSize Fold.drain+ split p f = Stream.foldMany (Fold.takeEndBy_ p f)+ main :: IO () main = do- Stream.replicate 4 sender -- SerialT IO (SerialT IO ())- & Stream.concatMapWith Stream.async id -- SerialT IO ()- & Stream.postscanlM' (counter "rcvd: ")- (return 0 :: IO Int) -- SerialT IO Int- & Stream.drain -- IO ()+ Stream.replicate 4 sender -- Stream IO (Stream IO ())+ & Stream.parConcat id -- Stream IO ()+ & Stream.postscan counts -- Stream IO Int+ & Stream.fold Fold.drain -- IO ()++ where++ counts = Fold.foldlM' (counter "rcvd") (return 0 :: IO Int)
examples/CmdServer.hs view
@@ -14,20 +14,21 @@ import Control.Monad.Catch (catch, SomeException) import Data.Char (isSpace) import Data.Function ((&))+import Data.Map.Strict (Map) import Network.Socket (Socket) import Streamly.Data.Fold (Fold) import System.Random (randomIO) -import qualified Data.Map.Strict as Map+import qualified Streamly.Data.Array as Array import qualified Streamly.Data.Fold as Fold-import qualified Streamly.Data.Array.Foreign as Array+import qualified Streamly.Data.Parser as Parser+import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.Network.Inet.TCP as TCP import qualified Streamly.Network.Socket as Socket-import qualified Streamly.Prelude as Stream import qualified Streamly.Unicode.Stream as Unicode -import qualified Streamly.Internal.Data.Fold as Fold (demuxDefault)-import qualified Streamly.Internal.Data.Time.Clock as Clock (getTime, Clock(..))+import qualified Streamly.Internal.Data.Time.Clock as Clock+ (getTime, Clock(..)) ------------------------------------------------------------------------------ -- Utility functions@@ -38,7 +39,7 @@ Stream.fromList (show x ++ "\n") & Unicode.encodeLatin1 & Stream.fold (Array.writeN 60)- >>= Socket.writeChunk sk+ >>= Socket.putChunk sk ------------------------------------------------------------------------------ -- Command Handlers@@ -50,17 +51,23 @@ random :: Socket -> IO () random sk = (randomIO :: IO Int) >>= sendValue sk -def :: (String, Socket) -> IO ()-def (str, sk) = sendValue sk ("Unknown command: " ++ str)+def :: String -> Socket -> IO ()+def str sk = sendValue sk ("Unknown command: " ++ str) -commands :: Map.Map String (Fold IO Socket ())-commands = Map.fromList- [ ("time" , Fold.drainBy time)- , ("random", Fold.drainBy random)- ]+commands :: String -> IO (Fold IO Socket ())+commands cmd =+ case cmd of+ "time" -> return (Fold.drainMapM time)+ "random" -> return (Fold.drainMapM random)+ _ -> return (Fold.drainMapM (def cmd)) +{-# INLINE demuxKvToMap #-}+demuxKvToMap :: (Monad m, Ord k) =>+ (k -> m (Fold m a b)) -> Fold m (k, a) (Map k b)+demuxKvToMap f = Fold.demuxToMap fst (\(k, _) -> fmap (Fold.lmap snd) (f k))+ demux :: Fold IO (String, Socket) ()-demux = snd <$> Fold.demuxDefault commands (Fold.drainBy def)+demux = void (demuxKvToMap commands) ------------------------------------------------------------------------------ -- Parse and handle commands on a socket@@ -68,15 +75,17 @@ handler :: Socket -> IO () handler sk =- Stream.unfold Socket.read sk -- SerialT IO Word8- & Unicode.decodeLatin1 -- SerialT IO Char- & Stream.wordsBy isSpace Fold.toList -- SerialT IO String- & Stream.map (, sk) -- SerialT IO (String, Socket)+ Stream.unfold Socket.reader sk -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & Stream.parseMany word -- Stream IO String+ & Stream.catRights+ & fmap (, sk) -- Stream IO (String, Socket) & Stream.fold demux -- IO () + Exceptions & discard -- IO () where + word = Parser.wordBy isSpace Fold.toList discard action = void action `catch` (\(_ :: SomeException) -> return ()) ------------------------------------------------------------------------------@@ -85,11 +94,9 @@ server :: IO () server =- Stream.unfold TCP.acceptOnPort 8091 -- SerialT IO Socket- & Stream.fromSerial -- AsyncT IO Socket- & Stream.mapM (Socket.forSocketM handler) -- AsyncT IO ()- & Stream.fromAsync -- SerialT IO ()- & Stream.drain -- IO ()+ Stream.unfold TCP.acceptorOnPort 8091 -- Stream IO Socket+ & Stream.parMapM id (Socket.forSocketM handler) -- Stream IO ()+ & Stream.fold Fold.drain -- IO () main :: IO () main = server
examples/ControlFlow.hs view
@@ -1,6 +1,6 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-orphans #-} ------------------------------------------------------------------------------- -- Combining control flow manipulating monad transformers (MaybeT, exceptT,@@ -15,12 +15,8 @@ -- This file provides an example where we enter a sequence of characters "x", -- and "y" on separate lines, on the command line. When any other sequence is -- entered the control flow short circuits at the first non-matching char and- -- exits. -#if !(MIN_VERSION_base(4,11,0))-import Data.Semigroup (Semigroup(..))-#endif import Control.Concurrent (threadDelay) import Control.Exception (catch, SomeException) import Control.Monad (when, mzero)@@ -30,9 +26,11 @@ import Control.Monad.Trans.Maybe (MaybeT (..)) import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE, catchE) import Control.Monad.Trans.Cont (ContT(..), callCC)-import Streamly.Prelude (IsStream)+import Streamly.Data.Stream (Stream)+import Streamly.Internal.Data.Stream.StreamD (CrossStream, mkCross, unCross) -import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream as Stream ------------------------------------------------------------------------------- -- Using MaybeT below streamly@@ -41,17 +39,11 @@ -- When streamly is on top MaybeT would terminate all iterations of -- non-determinism. ---getSequenceMaybeBelow- :: ( IsStream t- , Monad m- , MonadTrans t- , MonadIO (t (MaybeT m))- )- => t (MaybeT m) ()-getSequenceMaybeBelow = do+getSequenceMaybeBelow :: MonadIO m => Stream (MaybeT m) ()+getSequenceMaybeBelow = unCross $ do liftIO $ putStrLn "MaybeT below streamly: Enter one char per line: " - i <- Stream.fromFoldable [1..2 :: Int]+ i <- mkCross $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -62,7 +54,7 @@ mainMaybeBelow :: IO () mainMaybeBelow = do- r <- runMaybeT (Stream.drain getSequenceMaybeBelow)+ r <- runMaybeT (Stream.fold Fold.drain getSequenceMaybeBelow) case r of Just _ -> putStrLn "Bingo" Nothing -> putStrLn "Wrong"@@ -70,18 +62,18 @@ ------------------------------------------------------------------------------- -- Using MaybeT above streamly ----------------------------------------------------------------------------------+ -- When MaybeT is on top a Nothing would terminate only the current iteration -- of non-determinism below. -- -- Note that this is redundant configuration as the same behavior can be -- achieved with just streamly, using mzero. ---getSequenceMaybeAbove :: (IsStream t, MonadIO (t m)) => MaybeT (t m) ()+getSequenceMaybeAbove :: MonadIO m => MaybeT (CrossStream m) () getSequenceMaybeAbove = do liftIO $ putStrLn "MaybeT above streamly: Enter one char per line: " - i <- lift $ Stream.fromFoldable [1..2 :: Int]+ i <- lift $ mkCross $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -90,7 +82,7 @@ r2 <- liftIO getLine when (r2 /= "y") mzero -mainMaybeAbove :: (IsStream t, MonadIO (t m)) => MaybeT (t m) ()+mainMaybeAbove :: MonadIO m => MaybeT (CrossStream m) () mainMaybeAbove = do getSequenceMaybeAbove liftIO $ putStrLn "Bingo"@@ -103,17 +95,11 @@ -- -- Note that throwE would terminate all iterations of non-determinism -- altogether.-getSequenceEitherBelow- :: ( IsStream t- , MonadTrans t- , Monad m- , MonadIO (t (ExceptT String m))- )- => t (ExceptT String m) ()-getSequenceEitherBelow = do+getSequenceEitherBelow :: MonadIO m => Stream (ExceptT String m) ()+getSequenceEitherBelow = unCross $ do liftIO $ putStrLn "ExceptT below streamly: Enter one char per line: " - i <- Stream.fromFoldable [1..2 :: Int]+ i <- mkCross $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -125,7 +111,7 @@ mainEitherBelow :: IO () mainEitherBelow = do -- XXX Cannot lift catchE- r <- runExceptT (Stream.drain getSequenceEitherBelow)+ r <- runExceptT (Stream.fold Fold.drain getSequenceEitherBelow) case r of Right _ -> liftIO $ putStrLn "Bingo" Left s -> liftIO $ putStrLn s@@ -133,30 +119,29 @@ ------------------------------------------------------------------------------- -- Using ExceptT below concurrent streamly ----------------------------------------------------------------------------------+ -- XXX does not work correctly yet ---getSequenceEitherAsyncBelow- :: ( IsStream t- , MonadTrans t- , MonadIO m- , MonadIO (t (ExceptT String m))- , Semigroup (t (ExceptT String m) Integer)- )- => t (ExceptT String m) ()-getSequenceEitherAsyncBelow = do++getSequenceEitherAsyncBelow :: MonadIO m => Stream (ExceptT String m) ()+getSequenceEitherAsyncBelow = unCross $ do liftIO $ putStrLn "ExceptT below concurrent streamly: " - i <- (liftIO (threadDelay 1000)- >> lift (throwE "First task")- >> return 1)- <> (lift (throwE "Second task") >> return 2)- <> Stream.fromPure (3 :: Integer)+ i <- mkCross+ $ Stream.consM+ (liftIO (threadDelay 1000)+ >> throwE "First task"+ >> return 1+ )+ (Stream.consM+ (throwE "Second task" >> return 2)+ (Stream.fromPure (3 :: Integer))+ ) liftIO $ putStrLn $ "iteration = " <> show i mainEitherAsyncBelow :: IO () mainEitherAsyncBelow = do- r <- runExceptT (Stream.drain $ Stream.fromAsync getSequenceEitherAsyncBelow)+ r <- runExceptT (Stream.fold Fold.drain getSequenceEitherAsyncBelow) case r of Right _ -> liftIO $ putStrLn "Bingo" Left s -> liftIO $ putStrLn s@@ -173,12 +158,11 @@ -- -- Here we can use catchE directly but will have to use monad-control to lift -- stream operations with stream arguments.-getSequenceEitherAbove :: (IsStream t, MonadIO (t m))- => ExceptT String (t m) ()+getSequenceEitherAbove :: MonadIO m => ExceptT String (CrossStream m) () getSequenceEitherAbove = do liftIO $ putStrLn "ExceptT above streamly: Enter one char per line: " - i <- lift $ Stream.fromFoldable [1..2 :: Int]+ i <- lift $ mkCross $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -187,7 +171,7 @@ r2 <- liftIO getLine when (r2 /= "y") $ throwE $ "Expecting y got: " <> r2 -mainEitherAbove :: (IsStream t, MonadIO (t m)) => ExceptT String (t m) ()+mainEitherAbove :: MonadIO m => ExceptT String (CrossStream m) () mainEitherAbove = catchE (getSequenceEitherAbove >> liftIO (putStrLn "Bingo")) (liftIO . putStrLn)@@ -203,23 +187,26 @@ -- Note that unlike when ExceptT is used on top, MonadThrow terminates all -- iterations of non-determinism rather then just the current iteration. ---getSequenceMonadThrow :: (IsStream t, MonadIO (t m), MonadThrow (t m))- => t m ()-getSequenceMonadThrow = do+getSequenceMonadThrow :: (MonadIO m, MonadThrow m) => Stream m ()+getSequenceMonadThrow = unCross $ do liftIO $ putStrLn "MonadThrow in streamly: Enter one char per line: " - i <- Stream.fromFoldable [1..2 :: Int]+ i <- mkCross $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine- when (r1 /= "x") $ throwM $ Unexpected $ "Expecting x got: " <> r1+ when (r1 /= "x")+ $ mkCross+ $ Stream.fromEffect $ throwM $ Unexpected $ "Expecting x got: " <> r1 r2 <- liftIO getLine- when (r2 /= "y") $ throwM $ Unexpected $ "Expecting y got: " <> r2+ when (r2 /= "y")+ $ mkCross+ $ Stream.fromEffect $ throwM $ Unexpected $ "Expecting y got: " <> r2 mainMonadThrow :: IO () mainMonadThrow =- catch (Stream.drain getSequenceMonadThrow >> liftIO (putStrLn "Bingo"))+ catch (Stream.fold Fold.drain getSequenceMonadThrow >> liftIO (putStrLn "Bingo")) (\(e :: SomeException) -> liftIO $ print e) -------------------------------------------------------------------------------@@ -231,13 +218,11 @@ -- -- XXX need to have a specialized liftCallCC to actually lift callCC ---getSequenceContBelow- :: (IsStream t, MonadTrans t, MonadIO m, MonadIO (t (ContT r m)))- => t (ContT r m) (Either String ())+getSequenceContBelow :: MonadIO m => CrossStream (ContT r m) (Either String ()) getSequenceContBelow = do liftIO $ putStrLn "ContT below streamly: Enter one char per line: " - i <- Stream.fromFoldable [1..2 :: Int]+ i <- mkCross $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r <- lift $ callCC $ \exit -> do@@ -253,10 +238,8 @@ liftIO $ putStrLn $ "done iteration = " <> show i return r -mainContBelow- :: (IsStream t, MonadIO m, MonadTrans t, MonadIO (t (ContT r m)))- => t (ContT r m) ()-mainContBelow = do+mainContBelow :: MonadIO m => Stream (ContT r m) ()+mainContBelow = unCross $ do r <- getSequenceContBelow case r of Right _ -> liftIO $ putStrLn "Bingo"@@ -266,12 +249,11 @@ -- Using ContT above streamly ------------------------------------------------------------------------------- ---getSequenceContAbove :: (IsStream t, MonadIO (t m))- => ContT r (t m) (Either String ())+getSequenceContAbove :: MonadIO m => ContT r (CrossStream m) (Either String ()) getSequenceContAbove = do liftIO $ putStrLn "ContT above streamly: Enter one char per line: " - i <- lift $ Stream.fromFoldable [1..2 :: Int]+ i <- lift $ mkCross $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i callCC $ \exit -> do@@ -285,7 +267,7 @@ then exit $ Left $ "Expecting y got: " <> r2 else return $ Right () -mainContAbove :: (IsStream t, MonadIO (t m)) => ContT r (t m) ()+mainContAbove :: MonadIO m => ContT r (CrossStream m) () mainContAbove = do r <- getSequenceContAbove case r of@@ -300,10 +282,10 @@ main :: IO () main = do mainMaybeBelow- Stream.drain $ runMaybeT mainMaybeAbove- runContT (Stream.drain mainContBelow) return- Stream.drain (runContT mainContAbove return)+ Stream.fold Fold.drain $ unCross $ runMaybeT mainMaybeAbove+ runContT (Stream.fold Fold.drain mainContBelow) return+ Stream.fold Fold.drain $ unCross $ runContT mainContAbove return mainEitherBelow- Stream.drain (runExceptT mainEitherAbove)+ Stream.fold Fold.drain $ unCross $ runExceptT mainEitherAbove mainMonadThrow mainEitherAsyncBelow
examples/CoreUtils.hs view
@@ -1,70 +1,68 @@ -- Implement some simple coreutils commands import Control.Monad (void)-import Data.Char (ord) import Data.Function ((&)) import Data.Word (Word8) import System.Environment (getArgs)-import Streamly.Data.Array.Foreign (Array)+import Streamly.Data.Array (Array) import Streamly.Data.Fold (Fold) import qualified Streamly.Console.Stdio as Stdio-import qualified Streamly.Data.Array.Foreign as Array import qualified Streamly.Data.Fold as Fold-import qualified Streamly.Internal.Data.Stream.IsStream as Stream- (splitOnSeq, tapAsyncK)-import qualified Streamly.Internal.FileSystem.Dir as Dir (toFiles)+import qualified Streamly.Data.Stream as Stream++import qualified Streamly.Internal.FileSystem.Dir as Dir (readFiles) import qualified Streamly.Internal.FileSystem.File as File-import qualified Streamly.Prelude as Stream -- | > cat input.txt cat :: IO () cat =- File.toChunks "input.txt" -- SerialT IO (Array Word8)+ File.readChunks "input.txt" -- Stream IO (Array Word8) & Stream.fold Stdio.writeChunks -- IO () -- | Read all files from a directory and write to a single output file. -- > cat dir/* > output.txt catDirTo :: IO () catDirTo =- Dir.toFiles "dir" -- SerialT IO String- & Stream.unfoldMany File.readChunks -- SerialT IO (Array Word8)- & File.fromChunks "output.txt" -- IO ()+ Dir.readFiles "dir" -- Stream IO String+ & Stream.unfoldMany File.chunkReader -- Stream IO (Array Word8)+ & File.fromChunks "output.txt" -- IO () -- | > cp input.txt output.txt cp :: IO () cp =- File.toChunks "input.txt" -- SerialT IO (Array Word8)- & File.fromChunks "output.txt" -- IO ()+ File.readChunks "input.txt" -- Stream IO (Array Word8)+ & File.fromChunks "output.txt" -- IO () -- | > cat input.txt >> output.txt append :: IO () append =- File.toChunks "input.txt" -- SerialT IO (Array Word8)- & File.appendChunks "output.txt" -- IO ()+ File.readChunks "input.txt" -- Stream IO (Array Word8)+ & File.appendChunks "output.txt" -- IO () -- | > cat input.txt | tee output1.txt > output.txt tap :: IO () tap =- File.toChunks "input.txt" -- SerialT IO (Array Word8)- & Stream.tap (File.writeChunks "output1.txt") -- SerialT IO (Array Word8)+ File.readChunks "input.txt" -- Stream IO (Array Word8)+ & Stream.tap (File.writeChunks "output1.txt") -- Stream IO (Array Word8) & File.fromChunks "output.txt" -- IO ()-+{- -- | > cat input.txt | tee output1.txt > output.txt -- output1.txt is processed/written to in a separate thread. tapAsync :: IO () tapAsync =- Stream.unfold Stdio.readChunks () -- SerialT IO (Array Word8)+ Stream.unfold Stdio.readChunks () -- Stream IO (Array Word8) & Stream.tapAsyncK- (File.fromChunks "output1.txt") -- SerialT IO (Array Word8)+ (File.fromChunks "output1.txt") -- Stream IO (Array Word8) & File.fromChunks "output.txt" -- IO ()+-} -- | > cat input.txt | tee output1.txt > output.txt tee :: IO () tee =- File.toChunks "input.txt" -- SerialT IO (Array Word8)- & Stream.fold t -- IO ((),())- & void -- IO ()+ File.readChunks "input.txt" -- Stream IO (Array Word8)+ & Stream.fold t -- IO ((),())+ & void -- IO () where @@ -73,12 +71,12 @@ t = Fold.tee (File.writeChunks "output1.txt") -- Fold IO (Array Word8) () (File.writeChunks "output.txt") -- Fold IO (Array Word8) ()-+{- -- | > grep -c "the" input.txt grepc :: IO () grepc = do- File.toBytes "input.txt" -- SerialT IO Word8- & Stream.splitOnSeq (Array.fromList pat) Fold.drain -- SerialT IO ()+ File.toBytes "input.txt" -- Stream IO Word8+ & Stream.splitOnSeq (Array.fromList pat) Fold.drain -- Stream IO () & Stream.length -- IO Int >>= print . subtract 1 -- IO () @@ -86,6 +84,7 @@ pat :: [Word8] pat = map (fromIntegral . ord) "the"+-} main :: IO () main = do@@ -97,7 +96,7 @@ "cp" -> putStrLn "cp" >> cp "append" -> putStrLn "append" >> append "tap" -> putStrLn "tap" >> tap- "tapAsync" -> putStrLn "tapAsync" >> tapAsync+ -- "tapAsync" -> putStrLn "tapAsync" >> tapAsync "tee" -> putStrLn "tee" >> tee- "grepc" -> putStrLn "grepc" >> grepc+ -- "grepc" -> putStrLn "grepc" >> grepc _ -> putStrLn $ "Unknown command: " ++ cmd
examples/CoreUtilsHandle.hs view
@@ -6,11 +6,11 @@ import System.IO (IOMode(..), stdin, stdout, Handle, openFile) import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream as Stream import qualified Streamly.FileSystem.Handle as Handle-import qualified Streamly.Prelude as Stream import qualified Streamly.Unicode.Stream as Unicode -import qualified Streamly.Internal.Data.Array.Stream.Foreign as ArrayStream (splitOn)+import qualified Streamly.Internal.Data.Stream.Chunked as ArrayStream (splitOn) -- | Read the contents of a file to stdout. --@@ -20,21 +20,21 @@ -- catBytes :: Handle -> IO () catBytes src =- Stream.unfold Handle.read src -- SerialT IO Word8- & Stream.fold (Handle.write stdout) -- IO ()+ Stream.unfold Handle.reader src -- Stream IO Word8+ & Stream.fold (Handle.write stdout) -- IO () -- | Chunked version, more efficient than the byte stream version above. Reads -- the file in 256KB chunks and writes those chunks to stdout. cat :: Handle -> IO () cat src =- Stream.unfold Handle.readChunksWithBufferOf (256*1024, src) -- SerialT IO (Array Word8)+ Stream.unfold Handle.chunkReaderWith (256*1024, src) -- Stream IO (Array Word8) & Stream.fold (Handle.writeChunks stdout) -- IO () -- | Read from standard input write to standard output echo :: IO () echo =- Stream.unfold Handle.readChunks stdin -- SerialT IO (Array Word8)- & Stream.fold (Handle.writeChunks stdout) -- IO ()+ Stream.unfold Handle.chunkReader stdin -- Stream IO (Array Word8)+ & Stream.fold (Handle.writeChunks stdout) -- IO () -- | Copy a source file to a destination file. --@@ -43,15 +43,15 @@ -- 32KB and writes those chunks to the destination file. cpBytes :: Handle -> Handle -> IO () cpBytes src dst =- Stream.unfold Handle.read src -- SerialT IO Word8- & Stream.fold (Handle.write dst) -- IO ()+ Stream.unfold Handle.reader src -- Stream IO Word8+ & Stream.fold (Handle.write dst) -- IO () -- | Chunked version, more efficient than the byte stream version above. Reads -- the file in 256KB chunks and writes those chunks to stdout. cp :: Handle -> Handle -> IO () cp src dst = Stream.fold (Handle.writeChunks dst)- $ Stream.unfold Handle.readChunksWithBufferOf (256*1024, src)+ $ Stream.unfold Handle.chunkReaderWith (256*1024, src) -- | Count lines like wc -l. --@@ -59,19 +59,23 @@ -- and counts the lines.. wclChar :: Handle -> IO Int wclChar src =- Stream.unfold Handle.read src -- SerialT IO Word8- & Unicode.decodeLatin1 -- SerialT IO Char- & Stream.splitOnSuffix (== '\n') Fold.drain -- SerialT IO ()- & Stream.length -- IO ()+ Stream.unfold Handle.reader src -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & split (== '\n') Fold.drain -- Stream IO ()+ & Stream.fold Fold.length + where++ split p f = Stream.foldMany (Fold.takeEndBy_ p f) -- IO ()+ -- | More efficient chunked version. Reads chunks from the input handles and -- splits the chunks directly instead of converting them into byte stream -- first. wcl :: Handle -> IO Int wcl src =- Stream.unfold Handle.readChunks src -- SerialT IO (Array Word8)- & ArrayStream.splitOn 10 -- SerialT IO (Array Word8)- & Stream.length -- IO ()+ Stream.unfold Handle.chunkReader src -- Stream IO (Array Word8)+ & ArrayStream.splitOn 10 -- Stream IO (Array Word8)+ & Stream.fold Fold.length -- IO () main :: IO () main = do
+ examples/DateTimeParser.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE BangPatterns #-}++-- IMPORTANT: Do not export anything other than "main" from this file. The+-- benchmark timings may change drastically otherwise.++module Main+ ( main+ )+where++import Data.Either (fromRight)+import Data.Maybe (fromJust)+import Streamly.Data.Array (Array)+import Streamly.Internal.Data.Fold (Fold(..), Step(..))+import Test.Tasty.Bench++import qualified Data.Char as Char+import qualified Streamly.Data.Array as Array+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Parser as Parser+import qualified Streamly.Data.ParserK as ParserK+import qualified Streamly.Data.StreamK as StreamK+import qualified Streamly.Data.Stream as Stream+import qualified Streamly.Internal.Data.Fold as Fold (foldt', satisfy)+import qualified Streamly.Unicode.Parser as Parser++-------------------------------------------------------------------------------+-- Monolithic fold - fastest, same as rust speeddate perf+-------------------------------------------------------------------------------++mkTime :: Int -> Int -> Int -> Int -> Int -> Int -> Int+mkTime year month day hr mn sec = year + month + day + hr + mn + sec++{-# INLINE isDigit #-}+isDigit :: Char -> Bool+isDigit c = c >= '0' && c <= '9'++data FoldState =+ Year !Int+ | Month !Int !Int+ | Day !Int !Int+ | Hr !Int !Int+ | Min !Int !Int !Int+ | Sec !Int !Int !Int++{-# INLINE foldDateTime #-}+foldDateTime :: Array Char -> IO (Int, Int)+foldDateTime arr =+ Stream.fold t $ Stream.unfold Array.reader arr++ where++ t = Fold.foldt' step initial extract++ initial = Partial $ Year 0++ dec n ch = n * 10 + fromIntegral (Char.ord ch) - 48++ step (Year n) ch+ | isDigit ch = Partial $ Year (dec n ch)+ | ch == '-' = Partial $ Month (365 * n) 0+ | otherwise = error "parse error"+ step (Month y n) ch+ | isDigit ch = Partial $ Month y (dec n ch)+ | ch == '-' = Partial $ Day (y + n * 30) 0+ | otherwise = error "parse error"+ step (Day x n) ch+ | isDigit ch = Partial $ Day x (dec n ch)+ | ch == 'T' = Partial $ Hr (x + n) 0+ | otherwise = error "parse error"+ step (Hr d n) ch+ | isDigit ch = Partial $ Hr d (dec n ch)+ | ch == ':' = Partial $ Min d (n * 60) 0+ | otherwise = error "parse error"+ step (Min d m n) ch+ | isDigit ch = Partial $ Min d m (dec n ch)+ | ch == ':' = Partial $ Sec d ((m + n) * 60) 0+ | otherwise = error "parse error"+ step (Sec d s n) ch+ | isDigit ch = Partial $ Sec d s (dec n ch)+ | ch == 'Z' = Done (d, s + n)+ | otherwise = error "parse error"++ extract _ = error "incomplete"++-------------------------------------------------------------------------------+-- Modular applicative version - 4x slower+-------------------------------------------------------------------------------++{-# INLINE check #-}+check :: (Char -> Bool) -> Fold m Char a -> Fold m Char a+check p = Fold.lmap (\x -> if p x then x else error "parse failed")++{-# INLINE decimal #-}+decimal :: Monad m => Int -> Fold m Char Int+decimal n = Fold.take n (check isDigit (Fold.foldl' step 0))++ where++ step a c = a * 10 + fromIntegral (Char.ord c - 48)++{-# INLINE char #-}+char :: Monad m => Char -> Fold m Char Char+char c = fromJust <$> Fold.satisfy (== c)++{-# NOINLINE _foldDateTimeAp #-}+_foldDateTimeAp :: Array Char -> IO Int+_foldDateTimeAp arr =+ let t =+ mkTime+ <$> decimal 4 -- year+ <* char '-'+ <*> decimal 2 -- month+ <* char '-'+ <*> decimal 2 -- day+ <* char 'T'+ <*> decimal 2 -- hr+ <* char ':'+ <*> decimal 2 -- min+ <* char ':'+ <*> decimal 2 -- sec+ <* char 'Z'+ in Stream.fold t $ Stream.unfold Array.reader arr++-------------------------------------------------------------------------------+-- Using foldBreak - slower than applicative+-------------------------------------------------------------------------------++{-# INLINE _foldBreakDateTime #-}+_foldBreakDateTime :: Array Char -> IO Int+_foldBreakDateTime arr = do+ let s = Stream.unfold Array.reader arr+ (year, s1) <- Stream.foldBreak (decimal 4) s+ (_, s2) <- Stream.foldBreak (char '-') s1+ (month, s3) <- Stream.foldBreak (decimal 2) s2+ (_, s4) <- Stream.foldBreak (char '-') s3+ (day, s5) <- Stream.foldBreak (decimal 2) s4+ (_, s6) <- Stream.foldBreak (char 'T') s5+ (hr, s7) <- Stream.foldBreak (decimal 2) s6+ (_, s8) <- Stream.foldBreak (char ':') s7+ (mn, s9) <- Stream.foldBreak (decimal 2) s8+ (_, s10) <- Stream.foldBreak (char ':') s9+ (sec, s11) <- Stream.foldBreak (decimal 2) s10+ (_, _) <- Stream.foldBreak (char 'Z') s11+ return (year + month + day + hr + mn + sec)++-------------------------------------------------------------------------------+-- Using parseBreak - slower than foldBreak and parseK+-------------------------------------------------------------------------------++{-# NOINLINE _parseBreakDateTime #-}+_parseBreakDateTime :: Array Char -> IO Int+_parseBreakDateTime arr = do+ let s = StreamK.fromStream $ Stream.fromPure arr+ p = ParserK.fromParser . Parser.fromFold+ (Right year, s1) <- StreamK.parseBreakChunks (p $ decimal 4) s+ (_, s2) <- StreamK.parseBreakChunks (p $ char '-') s1+ (Right month, s3) <- StreamK.parseBreakChunks (p $ decimal 2) s2+ (_, s4) <- StreamK.parseBreakChunks (p $ char '-') s3+ (Right day, s5) <- StreamK.parseBreakChunks (p $ decimal 2) s4+ (_, s6) <- StreamK.parseBreakChunks (p $ char 'T') s5+ (Right hr, s7) <- StreamK.parseBreakChunks (p $ decimal 2) s6+ (_, s8) <- StreamK.parseBreakChunks (p $ char ':') s7+ (Right mn, s9) <- StreamK.parseBreakChunks (p $ decimal 2) s8+ (_, s10) <- StreamK.parseBreakChunks (p $ char ':') s9+ (Right sec, s11) <- StreamK.parseBreakChunks (p $ decimal 2) s10+ (_, _) <- StreamK.parseBreakChunks (p $ char 'Z') s11+ return (year + month + day + hr + mn + sec)++-------------------------------------------------------------------------------+-- Parser -- slower than foldBreak+-------------------------------------------------------------------------------++{-# NOINLINE _parseKDateTime #-}+_parseKDateTime :: Array Char -> IO Int+_parseKDateTime arr = do+ r <- StreamK.parseChunks dateParser $ StreamK.fromPure arr+ return $ fromRight (error "failed") r++ where++ p = ParserK.fromParser++ dateParser = do+ year <- p $ Parser.decimal <* Parser.char '-'+ month <- p $ Parser.decimal <* Parser.char '-'+ day <- p $ Parser.decimal <* Parser.char 'T'+ hr <- p $ Parser.decimal <* Parser.char ':'+ mi <- p $ Parser.decimal <* Parser.char ':'+ sec <- p $ Parser.decimal <* Parser.char 'Z'+ pure (mkTime year month day hr mi sec)++-- Parser Monad should not be used for more than 2-3 compositions, use ParserK+-- instead. Something like this is doomed to not perform well.+{-# NOINLINE _parseDateTime #-}+_parseDateTime :: Array Char -> IO Int+_parseDateTime arr = do+ r <- Stream.parse dateParser $ Stream.unfold Array.reader arr+ return $ fromRight (error "failed") r++ where++ dateParser = do+ year <- Parser.decimal <* Parser.char '-'+ month <- Parser.decimal <* Parser.char '-'+ day <- Parser.decimal <* Parser.char 'T'+ hr <- Parser.decimal <* Parser.char ':'+ mi <- Parser.decimal <* Parser.char ':'+ sec <- Parser.decimal <* Parser.char 'Z'+ pure (mkTime year month day hr mi sec)++-------------------------------------------------------------------------------+-- Benchmarks+-------------------------------------------------------------------------------++timeBench :: Benchmark+timeBench =+ let !(arr :: Array Char) = Array.fromListN 20 "2000-01-01T00:02:03Z"+ in bgroup "parseDateTime"+ -- IMPORTANT: Enable only one benchmark at a time, the benchmark+ -- timings may change drastically otherwise.+ [ bench "fold monolithic" $ nfIO $ foldDateTime arr -- 20 ns+ {- , bench "fold applicative" $ nfIO $ _foldDateTimeAp arr -- 110 ns+ , bench "foldBreak" $ nfIO $ _foldBreakDateTime arr -- 275 ns+ , bench "parseK" $ nfIO $ _parseKDateTime arr -- 340 ns+ , bench "parseBreak" $ nfIO $ _parseBreakDateTime arr -- 700 ns+ , bench "parseD" $ nfIO $ _parseDateTime arr -- 950 ns+ -}+ ]++main :: IO ()+main = defaultMain [timeBench]
examples/EchoServer.hs view
@@ -5,23 +5,23 @@ import Network.Socket (Socket) import qualified Network.Socket as Net+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.Network.Inet.TCP as TCP import qualified Streamly.Network.Socket as Socket-import qualified Streamly.Prelude as Stream main :: IO () main =- Stream.unfold TCP.acceptOnPort 8091 -- ParallelT IO Socket- & Stream.mapM (handleWithM echo) -- ParallelT IO ()- & Stream.fromParallel -- SerialT IO ()- & Stream.drain -- IO ()+ Stream.unfold TCP.acceptorOnPort 8091 -- Stream IO Socket+ & Stream.parMapM id (handleWithM echo) -- Stream IO ()+ & Stream.fold Fold.drain -- IO () where echo :: Socket -> IO () echo sk =- Stream.unfold Socket.readChunksWithBufferOf (32768, sk) -- SerialT IO (Array Word8)- & Stream.fold (Socket.writeChunks sk) -- IO ()+ Stream.unfold Socket.chunkReaderWith (32768, sk) -- Stream IO (Array Word8)+ & Stream.fold (Socket.writeChunks sk) -- IO () handleWithM :: (Socket -> IO ()) -> Socket -> IO () handleWithM f sk = finally (f sk) (Net.close sk)
examples/FileSender.hs view
@@ -12,10 +12,11 @@ import qualified Control.Monad.Catch as Catch import qualified Network.Socket as Net+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.FileSystem.Handle as Handle import qualified Streamly.Network.Inet.TCP as TCP import qualified Streamly.Network.Socket as Socket-import qualified Streamly.Prelude as Stream main :: IO () main = do@@ -29,8 +30,8 @@ fileToSocket :: Handle -> Socket -> IO () fileToSocket fh sk =- Stream.unfold Handle.readChunks fh -- SerialT IO (Array Word8)- & Stream.fold (Socket.writeChunks sk) -- IO ()+ Stream.unfold Handle.chunkReader fh -- Stream IO (Array Word8)+ & Stream.fold (Socket.writeChunks sk) -- IO () sendFile :: String -> IO () sendFile file =@@ -39,7 +40,6 @@ sendAll :: [String] -> IO () sendAll files =- Stream.fromList files -- ParallelT IO String- & Stream.mapM sendFile -- ParallelT IO ()- & Stream.fromParallel -- SerialT IO ()- & Stream.drain -- IO ()+ Stream.fromList files -- Stream IO String+ & Stream.parMapM id sendFile -- Stream IO ()+ & Stream.fold Fold.drain -- IO ()
examples/FileSystemEvent.hs view
@@ -6,11 +6,12 @@ import Data.Function ((&)) import Data.Word (Word8) import System.Environment (getArgs)-import Streamly.Data.Array.Foreign (Array)+import Streamly.Data.Array (Array) import qualified Data.List.NonEmpty as NonEmpty-import qualified Streamly.Data.Array.Foreign as Array-import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Array as Array+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream as Stream import qualified Streamly.Unicode.Stream as Unicode #if darwin_HOST_OS@@ -39,4 +40,4 @@ args <- getArgs paths <- mapM toUtf8 args Event.watch (NonEmpty.fromList paths)- & Stream.mapM_ (putStrLn . Event.showEvent)+ & Stream.fold (Fold.drainMapM (putStrLn . Event.showEvent))
examples/Interop/Conduit.hs view
@@ -1,19 +1,20 @@ {-# LANGUAGE FlexibleContexts #-} -import Streamly.Prelude (IsStream, MonadAsync, SerialT)-import qualified Streamly.Prelude as Stream+import Streamly.Data.Stream (Stream)++import qualified Streamly.Data.Stream as Stream import qualified Data.Conduit as Conduit import qualified Data.Conduit.List as Conduit -- | conduit to streamly-fromConduit :: (IsStream t, MonadAsync m) => Conduit.ConduitT () a m () -> t m a+fromConduit :: Monad m => Conduit.ConduitT () a m () -> Stream m a fromConduit = Stream.unfoldrM Conduit.unconsM . Conduit.sealConduitT -- | streamly to conduit-toConduit :: Monad m => SerialT m a -> Conduit.ConduitT i a m ()+toConduit :: Monad m => Stream m a -> Conduit.ConduitT i a m () toConduit = Conduit.unfoldM Stream.uncons main :: IO () main = do Stream.toList (fromConduit (Conduit.sourceList ([1..3]::[Int]))) >>= print- Conduit.runConduit (toConduit (Stream.fromFoldable ([1..3]::[Int])) Conduit..| Conduit.consume) >>= print+ Conduit.runConduit (toConduit (Stream.fromList ([1..3]::[Int])) Conduit..| Conduit.consume) >>= print
examples/Interop/Pipes.hs view
@@ -1,26 +1,27 @@ {-# LANGUAGE FlexibleContexts #-} -import Streamly.Prelude (IsStream, MonadAsync, SerialT)-import qualified Streamly.Prelude as Stream+import Streamly.Data.Stream (Stream)++import qualified Streamly.Data.Stream as Stream import qualified Pipes as Pipe import qualified Pipes.Prelude as Pipe -- | pipes to streamly-fromPipes :: (IsStream t, MonadAsync m) => Pipe.Producer a m () -> t m a+fromPipes :: Monad m => Pipe.Producer a m () -> Stream m a fromPipes = Stream.unfoldrM unconsP where -- Adapt Pipe.next to return a Maybe instead of Either unconsP p = either (const Nothing) Just <$> Pipe.next p -- | streamly to pipes-toPipes :: Monad m => SerialT m a -> Pipe.Producer a m ()+toPipes :: Monad m => Stream m a -> Pipe.Producer a m () toPipes = Pipe.unfoldr unconsEither where -- Adapt Stream.uncons to return an Either instead of Maybe- unconsEither :: Monad m => SerialT m a -> m (Either () (a, SerialT m a))+ unconsEither :: Monad m => Stream m a -> m (Either () (a, Stream m a)) unconsEither s = maybe (Left ()) Right <$> Stream.uncons s main :: IO () main = do Stream.toList (fromPipes (Pipe.each ([1..3]::[Int]))) >>= print- Pipe.toListM (toPipes (Stream.fromFoldable ([1..3]::[Int]))) >>= print+ Pipe.toListM (toPipes (Stream.fromList ([1..3]::[Int]))) >>= print
examples/Interop/Streaming.hs view
@@ -1,23 +1,24 @@ {-# LANGUAGE FlexibleContexts #-} -import Streamly.Prelude (IsStream, MonadAsync, SerialT)-import qualified Streamly.Prelude as Stream+import Streamly.Data.Stream (Stream)++import qualified Streamly.Data.Stream as Stream import qualified Streaming import qualified Streaming.Prelude as Streaming -- | streaming to streamly-fromStreaming :: (IsStream t, MonadAsync m) => Streaming.Stream (Streaming.Of a) m () -> t m a+fromStreaming :: Monad m => Streaming.Stream (Streaming.Of a) m () -> Stream m a fromStreaming = Stream.unfoldrM Streaming.uncons -- -- | streamly to streaming-toStreaming :: Monad m => SerialT m a -> Streaming.Stream (Streaming.Of a) m ()+toStreaming :: Monad m => Stream m a -> Streaming.Stream (Streaming.Of a) m () toStreaming = Streaming.unfoldr unconsEither where -- Adapt Stream.uncons to return an Either instead of Maybe- unconsEither :: Monad m => SerialT m a -> m (Either () (a, SerialT m a))+ unconsEither :: Monad m => Stream m a -> m (Either () (a, Stream m a)) unconsEither s = maybe (Left ()) Right <$> Stream.uncons s main :: IO () main = do Stream.toList (fromStreaming (Streaming.each ([1..3]::[Int]))) >>= print- Streaming.toList (toStreaming (Stream.fromFoldable ([1..3]::[Int]))) >>= print+ Streaming.toList (toStreaming (Stream.fromList ([1..3]::[Int]))) >>= print
examples/Interop/Vector.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE FlexibleContexts #-} -import Streamly.Prelude (MonadAsync, SerialT)-import qualified Streamly.Prelude as Stream+import Streamly.Data.Stream (Stream)++import qualified Streamly.Data.Stream as Stream import qualified Data.Vector.Fusion.Stream.Monadic as Vector -- | vector to streamly-fromVector :: (Stream.IsStream t, MonadAsync m) => Vector.Stream m a -> t m a+fromVector :: Monad m => Vector.Stream m a -> Stream m a fromVector = Stream.unfoldrM unconsV where unconsV v = do@@ -17,10 +18,10 @@ return $ Just (h, Vector.tail v) -- | streamly to vector-toVector :: Monad m => SerialT m a -> Vector.Stream m a-toVector = Vector.unfoldrM (Stream.uncons . Stream.adapt)+toVector :: Monad m => Stream m a -> Vector.Stream m a+toVector = Vector.unfoldrM Stream.uncons main :: IO () main = do Stream.toList (fromVector (Vector.fromList ([1..3]::[Int]))) >>= print- Vector.toList (toVector (Stream.fromFoldable ([1..3]::[Int]))) >>= print+ Vector.toList (toVector (Stream.fromList ([1..3]::[Int]))) >>= print
examples/Intro.hs view
@@ -8,22 +8,21 @@ import Data.Word (Word8) import System.Environment (getArgs) import System.IO (stdout)--import Streamly.Prelude (SerialT)-import Streamly.Data.Fold (Fold)-import Streamly.Data.Fold.Tee (Tee(..))+import Streamly.Data.Fold (Fold, Tee(..))+import Streamly.Data.Stream.Prelude (Stream) import Streamly.Data.Unfold (Unfold)+import Streamly.Internal.Data.Stream (CrossStream, mkCross, unCross) -import qualified Streamly.Data.Array.Foreign as Array+import qualified Streamly.Data.Array as Array import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.Data.Unfold as Unfold-import qualified Streamly.Prelude as Stream import qualified Streamly.FileSystem.Handle as Handle+import qualified Streamly.FileSystem.File as File import qualified Streamly.Unicode.Stream as Unicode +import qualified Streamly.Internal.Data.Stream as Stream (wordsBy) import qualified Streamly.Internal.Data.Unfold as Unfold (enumerateFromToIntegral)-import qualified Streamly.Internal.Data.Fold as Fold (classify)-import qualified Streamly.Internal.FileSystem.File as File (toBytes) ------------------------------------------------------------------------------- -- Simple loops@@ -32,14 +31,14 @@ -- | Sum a list of Int sumInt :: Identity Int sumInt =- Stream.unfold Unfold.fromList [1..10] -- SerialT Identity Int+ Stream.unfold Unfold.fromList [1..10] -- Stream Identity Int & Stream.fold Fold.sum -- Identity Int -- | Sum a list of Int sumInt1 :: Identity Int sumInt1 =- Stream.fromList [1..10] -- SerialT Identity Int- & Stream.sum -- Identity Int+ Stream.fromList [1..10] -- Stream Identity Int+ & Stream.fold Fold.sum -- Identity Int ------------------------------------------------------------------------------- -- Nested loops@@ -60,28 +59,31 @@ (Unfold.lmap fst Unfold.enumerateFromToIntegral) (Unfold.lmap snd Unfold.enumerateFromToIntegral) - in Stream.unfold xmult (range1,range2) -- SerialT Identity Int+ in Stream.unfold xmult (range1,range2) -- Stream Identity Int & Stream.fold Fold.sum -- Identity Int -- | Nested looping similar to 'cross' above but more general and less -- efficient. The second stream may depend on the first stream. The loops -- cannot fuse completely. ---nestedLoops :: SerialT IO ()+nestedLoops :: CrossStream IO () nestedLoops = do- x <- Stream.fromList [3,4 :: Int]- y <- Stream.fromList [1..x]- Stream.fromEffect $ print (x, y)+ x <- mkCross $ Stream.fromList [3,4 :: Int]+ y <- mkCross $ Stream.fromList [1..x]+ mkCross $ Stream.fromEffect $ print (x, y) ------------------------------------------------------------------------------- -- Text processing ------------------------------------------------------------------------------- +splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b+splitOn p f = Stream.foldMany (Fold.takeEndBy_ p f)+ -- | Find average line length for lines in a text file avgLineLength :: IO Double avgLineLength =- File.toBytes "input.txt" -- SerialT IO Word8- & Stream.splitOnSuffix isNewLine Fold.length -- SerialT IO Int+ File.read "input.txt" -- Stream IO Word8+ & splitOn isNewLine Fold.length -- Stream IO Int & Stream.fold avg -- IO Double where@@ -93,17 +95,21 @@ toDouble = fmap (fromIntegral :: Int -> Double) avg :: Fold IO Int Double- avg = toFold $ (/)+ avg = unTee $ (/) <$> Tee (toDouble Fold.sum) <*> Tee (toDouble Fold.length) +{-# INLINE kvMap #-}+kvMap :: (Monad m, Ord k) => Fold m a b -> Fold m (k, a) (Map k b)+kvMap = Fold.toMap fst . Fold.lmap snd+ -- | Read text from a file and generate a histogram of line length lineLengthHistogram :: IO (Map Int Int) lineLengthHistogram =- File.toBytes "input.txt" -- SerialT IO Word8- & Stream.splitOnSuffix isNewLine Fold.length -- SerialT IO Int- & Stream.map bucket -- SerialT IO (Int, Int)- & Stream.fold (Fold.classify Fold.length) -- IO (Map Int Int)+ File.read "input.txt" -- Stream IO Word8+ & splitOn isNewLine Fold.length -- Stream IO Int+ & fmap bucket -- Stream IO (Int, Int)+ & Stream.fold (kvMap Fold.length) -- IO (Map Int Int) where @@ -116,11 +122,11 @@ -- | Read text from a file and generate a histogram of word length wordLengthHistogram :: IO (Map Int Int) wordLengthHistogram =- File.toBytes "input.txt" -- SerialT IO Word8- & Unicode.decodeLatin1 -- SerialT IO Char- & Stream.wordsBy isSpace Fold.length -- SerialT IO Int- & Stream.map bucket -- SerialT IO (Int, Int)- & Stream.fold (Fold.classify Fold.length) -- IO (Map (Int, Int))+ File.read "input.txt" -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & Stream.wordsBy isSpace Fold.length -- Stream IO Int+ & fmap bucket -- Stream IO (Int, Int)+ & Stream.fold (kvMap Fold.length) -- IO (Map (Int, Int)) where @@ -146,12 +152,13 @@ -- getWords :: IO () getWords =- Stream.fromListM meanings -- AheadT IO (String, String)- & Stream.fromAhead -- SerialT IO (String, String)- & Stream.map show -- SerialT IO String- & unlinesBy "\n" -- SerialT IO String- & Stream.map Array.fromList -- SerialT IO (Array Word8)- & Stream.fold (Handle.writeChunks stdout) -- IO ()+ Stream.fromList meanings -- Stream IO (IO (String, String))+ & Stream.parSequence+ (Stream.ordered True) -- Stream IO (String, String)+ & fmap show -- Stream IO String+ & unlinesBy "\n" -- Stream IO String+ & fmap Array.fromList -- Stream IO (Array Word8)+ & Stream.fold (Handle.writeChunks stdout) -- IO () where unlinesBy = Stream.intercalateSuffix (Unfold.function id) @@ -180,7 +187,7 @@ print $ runIdentity $ crossProduct (1,1000) (1000,2000) "nestedLoops" -> do putStrLn "nestedLoops"- Stream.drain nestedLoops+ Stream.fold Fold.drain $ unCross nestedLoops "avgLineLength" -> do putStrLn "avgLineLength" avgLineLength >>= print
examples/ListDir.hs view
@@ -1,27 +1,36 @@+{-# LANGUAGE FlexibleContexts #-}+{-# OPTIONS_GHC -Wno-deprecations #-}+ module Main (main) where -import Data.Bifunctor (bimap)-import Data.Function ((&))-import Streamly.Prelude (SerialT) import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering)) -import qualified Streamly.Prelude as Stream-import qualified Streamly.Internal.Data.Stream.IsStream as Stream- (iterateMapLeftsWith)-import qualified Streamly.Internal.FileSystem.Dir as Dir (toEither)---- Lists a dir as a stream of (Either Dir File)-listDir :: String -> SerialT IO (Either String String)-listDir dir =- Dir.toEither dir -- SerialT IO (Either String String)- & Stream.map (bimap mkAbs mkAbs) -- SerialT IO (Either String String)-- where mkAbs x = dir ++ "/" ++ x+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream+-- import qualified Streamly.Internal.Data.Stream as Stream+-- import qualified Streamly.Internal.Data.Unfold as Unfold (either, nil)+import qualified Streamly.Internal.FileSystem.Dir as Dir -- | List the current directory recursively using concurrent processing main :: IO () main = do hSetBuffering stdout LineBuffering- let start = Stream.fromPure (Left ".")- Stream.iterateMapLeftsWith Stream.ahead listDir start- & Stream.mapM_ print+ Stream.fold (Fold.drainMapM print)+ -- $ Stream.unfoldIterateDfs unfoldOne+ -- $ Stream.unfoldIterateBfs unfoldOne+ -- $ Stream.unfoldIterateBfsRev unfoldOne+ -- $ Stream.concatIterateDfs streamOneMaybe+ -- $ Stream.concatIterateBfs streamOneMaybe+ -- $ Stream.concatIterateBfsRev streamOneMaybe+ -- $ Stream.concatIterateWith Stream.append streamOne+ -- $ Stream.mergeIterateWith Stream.interleave streamOne+ $ Stream.parConcatIterate id streamOne+ -- $ Stream.parConcatIterate (Stream.interleaved True) streamOne+ -- $ Stream.parConcatIterate (Stream.ordered True) streamOne+ $ Stream.fromPure (Left ".")++ where++ -- unfoldOne = Unfold.either Dir.eitherReaderPaths Unfold.nil+ -- streamOneMaybe = either (Just . Dir.readEitherPaths) (const Nothing)+ streamOne = either Dir.readEitherPaths (const Stream.nil)
+ examples/LogParser.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE QuasiQuotes #-}+{-# OPTIONS_GHC -fspec-constr-recursive=4 #-}++import Data.Function ((&))+import Data.Functor.Identity (runIdentity)+import Streamly.Data.Parser (Parser)+import Streamly.Unicode.String (str)++import qualified Data.Char as Char+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Parser as Parser+import qualified Streamly.Data.Stream as Stream++-- Example of a quoted log string to be parsed+quoted :: String+quoted = [str|+"[2023-02-04T09:15:20.549Z] \"GET /cards?customer_id=XXXXP/1.1\" 200+- \"-\" \"-\" 0 75 23 23 \"131.26.22.133,127.16.101.49,127.18.4.69\"+\"edge\" \"7ccc821a-03ff-49c1-a721-977e7cbd78f3\" \"api.example.in\"+\"127.18.55.25:1108\" outbound|443|v21945461|svc.cluster.local+127.18.44.67:75523 127.18.44.67:8443 127.18.4.69:14462+mum.example.net confirmtkt\n"+|]++-- Use double quote as the quoting char+isQuote :: Char -> Maybe Char+isQuote x =+ case x of+ '"' -> Just x+ _ -> Nothing++-- Transliterate \" and \n+tr :: Char -> Char -> Maybe Char+tr q x =+ case x of+ _ | x == q -> Just x+ 'n' -> Just '\n'+ _ -> Nothing++-- quoted string parser, reads everything inside quotes as a single word+-- and processes the quotes and escapes to expose the words inside it.+parser :: Monad m => Parser Char m [Char]+parser = Parser.wordWithQuotes False tr '\\' isQuote Char.isSpace Fold.toList++main :: IO ()+main = do+ -- Strip outer quotes+ let res = runIdentity $ Stream.parse parser $ Stream.fromList quoted++ -- parse words inside quotes+ case res of+ Left err -> error $ "Malformed quoted string: " ++ show err+ Right unquoted ->+ Stream.fromList unquoted+ & Stream.parseMany parser+ & Stream.catRights+ & Stream.toList+ >>= print
examples/MergeServer.hs view
@@ -1,39 +1,44 @@ import Control.Monad.IO.Class (liftIO) import Data.Function ((&)) import Network.Socket (Socket, close)-import Streamly.Data.Array.Foreign (Array)-import Streamly.Prelude (SerialT)+import Streamly.Data.Array (Array)+import Streamly.Data.Stream.Prelude (Stream) import System.IO (Handle, withFile, IOMode(..)) -import qualified Streamly.Data.Array.Foreign as Array+import qualified Streamly.Data.Array as Array+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.FileSystem.Handle as Handle import qualified Streamly.Network.Socket as Socket import qualified Streamly.Network.Inet.TCP as TCP-import qualified Streamly.Prelude as Stream import qualified Streamly.Unicode.Stream as Unicode -- | Read a line stream from a socket. Note, lines are buffered, we could add -- a limit to the buffering for safety.-readLines :: Socket -> SerialT IO (Array Char)+readLines :: Socket -> Stream IO (Array Char) readLines sk =- Stream.unfold Socket.read sk -- SerialT IO Word8- & Unicode.decodeLatin1 -- SerialT IO Char- & Stream.splitWithSuffix (== '\n') Array.write -- SerialT IO String+ Stream.unfold Socket.reader sk -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & split (== '\n') Array.write -- Stream IO String -recv :: Socket -> SerialT IO (Array Char)-recv sk = Stream.finally (liftIO $ close sk) (readLines sk)+ where + split p f = Stream.foldMany (Fold.takeEndBy p f)++recv :: Socket -> Stream IO (Array Char)+recv sk = Stream.finallyIO (liftIO $ close sk) (readLines sk)+ -- | Starts a server at port 8091 listening for lines with space separated -- words. Multiple clients can connect to the server and send streams of lines. -- The server handles all the connections concurrently, merges the incoming -- streams at line boundaries and writes the merged stream to a file. server :: Handle -> IO () server file =- Stream.unfold TCP.acceptOnPort 8090 -- SerialT IO Socket- & Stream.concatMapWith Stream.parallel recv -- SerialT IO (Array Char)- & Stream.unfoldMany Array.read -- SerialT IO Char- & Unicode.encodeLatin1 -- SerialT IO Word8- & Stream.fold (Handle.write file) -- IO ()+ Stream.unfold TCP.acceptorOnPort 8090 -- Stream IO Socket+ & Stream.parConcatMap (Stream.eager True) recv -- Stream IO (Array Char)+ & Stream.unfoldMany Array.reader -- Stream IO Char+ & Unicode.encodeLatin1 -- Stream IO Word8+ & Stream.fold (Handle.write file) -- IO () main :: IO () main = withFile "output.txt" AppendMode server
examples/MergeSort.hs view
@@ -1,26 +1,106 @@-{-# LANGUAGE FlexibleContexts #-}+module Main+ ( main+ , sortMergeCombined+ , sortMergeChunks+ )+where --- This example generates two random streams sorted in ascending order and--- merges them in ascending order, concurrently.------ Compile with '-threaded -with-rtsopts "-N"' GHC options to use the--- parallelism.+import Control.Monad (void)+import Data.Function ((&))+import Streamly.Data.Array (Array)+import Streamly.Data.Stream (Stream) -import Data.Word (Word16)-import Streamly.Prelude (SerialT)-import System.Random (getStdGen, randoms)+import qualified Streamly.Data.Array as Array+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream+import qualified Streamly.Data.StreamK as K -import qualified Data.List as List-import qualified Streamly.Prelude as Stream+import qualified Streamly.Internal.Data.Stream as Stream (reduceIterateBfs) -getRandomSorted :: IO (SerialT IO Word16)-getRandomSorted = do- g <- getStdGen- let ls = take 100000 (randoms g) :: [Word16]- return $ Stream.fromList (List.sort ls)+input :: [Int]+input = [1000000,999999..1] +chunkSize :: Int+chunkSize = 32*1024++streamChunk :: Array Int -> Stream IO Int+streamChunk =+ K.toStream+ . K.sortBy compare+ . K.fromStream+ . Stream.unfold Array.reader++sortChunk :: Array Int -> IO (Array Int)+sortChunk = Stream.fold Array.write . streamChunk++-------------------------------------------------------------------------------+-- Stream the unsorted chunks and sort, merge those streams.+-------------------------------------------------------------------------------+-- In contrast to sortMergeSeparate this uses much more peak memory because all+-- the streams are open in memory at the same time.+sortMergeCombined :: (Array Int -> Stream IO Int) -> IO ()+sortMergeCombined f =+ Stream.fromList input+ & Stream.chunksOf chunkSize+ & K.fromStream+ & K.mergeMapWith (K.mergeBy compare) (K.fromStream . f)+ & K.toStream+ & Stream.fold Fold.drain++-------------------------------------------------------------------------------+-- First create a stream of sorted chunks, then stream sorted chunks and merge+-- the streams+-------------------------------------------------------------------------------++sortMergeSeparate ::+ ( (Array Int -> IO (Array Int))+ -> Stream IO (Array Int)+ -> Stream IO (Array Int)+ )+ -> IO ()+sortMergeSeparate f =+ Stream.fromList input+ & Stream.chunksOf chunkSize+ & f sortChunk+ & K.fromStream+ & K.mergeMapWith+ (K.mergeBy compare) (K.fromStream . Stream.unfold Array.reader)+ & K.toStream+ & Stream.fold Fold.drain++-------------------------------------------------------------------------------+-- First create a stream of sorted chunks, merge sorted chunks into sorted+-- chunks recursively.+-------------------------------------------------------------------------------++reduce :: Array Int -> Array Int -> IO (Array Int)+reduce arr1 arr2 =+ Stream.mergeBy+ compare+ (Stream.unfold Array.reader arr1)+ (Stream.unfold Array.reader arr2)+ & Stream.fold Array.write++sortMergeChunks ::+ ( (Array Int -> IO (Array Int))+ -> Stream IO (Array Int)+ -> Stream IO (Array Int)+ )+ -> IO ()+sortMergeChunks f =+ Stream.fromList input+ & Stream.chunksOf chunkSize+ & f sortChunk+ & Stream.reduceIterateBfs reduce+ & void++-- | Divide a stream in chunks, sort the chunks and merge them. main :: IO () main = do- s1 <- getRandomSorted- s2 <- getRandomSorted- Stream.last (Stream.mergeAsyncBy compare s1 s2) >>= print+ -- Sorted in best performing first order+ sortMergeSeparate (Stream.parMapM id)+ -- sortMergeSeparate Stream.mapM+ -- sortMergeCombined (Stream.parEval id . streamChunk)+ -- sortMergeCombined streamChunk+ -- sortMergeChunks (Stream.parMapM id)+ -- sortMergeChunks Stream.mapM
examples/Rate.hs view
@@ -1,11 +1,11 @@ import Data.Function ((&))-import qualified Streamly.Prelude as Stream-import qualified Streamly.Internal.Data.Stream.IsStream as Stream (timestamped)+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream+import qualified Streamly.Internal.Data.Stream as Stream (timestamped) main :: IO () main =- Stream.repeatM (pure "tick") -- AsyncT IO String- & Stream.timestamped -- AsyncT IO (AbsTime, String)- & Stream.avgRate 1 -- AsyncT IO (AbsTime, String)- & Stream.fromAsync -- SerialT IO (AbsTime, String)- & Stream.mapM_ print -- IO ()+ Stream.sequence (Stream.repeat (pure "tick"))+ & Stream.timestamped+ & Stream.parEval (Stream.avgRate 1)+ & Stream.fold (Fold.drainMapM print)
examples/Split.hs view
@@ -4,13 +4,14 @@ import Data.Word (Word8) import System.Environment (getArgs) import System.IO (Handle, IOMode(..), openFile, hClose)-import Streamly.Prelude (SerialT)+import Streamly.Data.Stream (Stream) -import qualified Streamly.Prelude as Stream-import qualified Streamly.Internal.Data.Stream.IsStream as Stream (refoldMany)+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream as Stream+import qualified Streamly.Internal.Data.Stream as Stream (refoldMany) import qualified Streamly.Internal.Data.Refold.Type as Refold (take) import qualified Streamly.FileSystem.Handle as Handle-import qualified Streamly.Internal.FileSystem.Handle as Handle (consumer)+import qualified Streamly.Internal.FileSystem.Handle as Handle (writer) newHandle :: StateT (Maybe (Handle, Int)) IO Handle newHandle = do@@ -28,13 +29,13 @@ -- of directory names. splitFile :: Handle -> IO () splitFile inHandle =- (Stream.unfold Handle.read inHandle :: SerialT IO Word8) -- SerialT IO Word8- & Stream.liftInner -- SerialT (StateT (Maybe (Handle, Int)) IO) Word8- -- SerialT (StateT (Maybe (Handle, Int)) IO) ()- & Stream.refoldMany (Refold.take (180 * mb) Handle.consumer) newHandle- & Stream.runStateT (return Nothing) -- SerialT IO (Maybe (Handle, Int), ())- & Stream.map snd -- SerialT IO ()- & Stream.drain -- SerialT IO ()+ (Stream.unfold Handle.reader inHandle :: Stream IO Word8) -- Stream IO Word8+ & Stream.liftInner -- Stream (StateT (Maybe (Handle, Int)) IO) Word8+ -- Stream (StateT (Maybe (Handle, Int)) IO) ()+ & Stream.refoldMany (Refold.take (180 * mb) Handle.writer) newHandle+ & Stream.runStateT (return Nothing) -- Stream IO (Maybe (Handle, Int), ())+ & fmap snd -- Stream IO ()+ & Stream.fold Fold.drain -- Stream IO () where
examples/WordCount.hs view
@@ -8,8 +8,9 @@ import Data.Function ((&)) import System.Environment (getArgs) -import qualified Streamly.Internal.FileSystem.File as File-import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream as Stream+import qualified Streamly.FileSystem.File as File import qualified Streamly.Unicode.Stream as Stream -------------------------------------------------------------------------------@@ -40,10 +41,10 @@ wc :: String -> IO Counts wc file =- Stream.unfold File.read file -- SerialT IO Word8- & Stream.decodeLatin1 -- SerialT IO Char- -- & Stream.decodeUtf8 -- SerialT IO Char- & Stream.foldl' count (Counts 0 0 0 True) -- IO Counts+ File.read file -- Stream IO Word8+ & Stream.decodeLatin1 -- Stream IO Char+ -- & Stream.decodeUtf8 -- Stream IO Char+ & Stream.fold (Fold.foldl' count (Counts 0 0 0 True)) -- IO Counts ------------------------------------------------------------------------------- -- Main
examples/WordCountModular.hs view
@@ -7,14 +7,12 @@ import Data.Char (chr, ord) import Data.Function ((&)) import Data.Word (Word8)-import Streamly.Data.Fold (Fold)-import Streamly.Data.Fold.Tee (Tee(..))+import Streamly.Data.Fold (Fold, Tee(..)) import System.Environment (getArgs) import qualified Streamly.Data.Fold as Fold-import qualified Streamly.Data.Fold.Tee as Tee-import qualified Streamly.Internal.FileSystem.File as File (toBytes)-import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Stream as Stream+import qualified Streamly.FileSystem.File as File {-# INLINE isSpace #-} isSpace :: Char -> Bool@@ -28,7 +26,7 @@ -- The fold accepts a stream of `Word8` and returns a value of type "a". foldWith :: Fold IO Word8 a -> String -> IO a foldWith f file =- File.toBytes file -- SerialT IO Word8+ File.read file -- Stream IO Word8 & Stream.fold f -- IO a -------------------------------------------------------------------------------@@ -62,7 +60,7 @@ -- The fold accepts a stream of `Word8` and returns the three counts countAll :: Fold IO Word8 (Int, Int, Int)-countAll = Tee.toFold $ (,,) <$> Tee Fold.length <*> Tee nlines <*> Tee nwords+countAll = unTee $ (,,) <$> Tee Fold.length <*> Tee nlines <*> Tee nwords ------------------------------------------------------------------------------- -- Main
examples/WordCountParallel.hs view
@@ -7,20 +7,21 @@ import Data.Word (Word8) import GHC.Conc (numCapabilities) import System.Environment (getArgs)-import Streamly.Data.Array.Foreign (Array)+import Streamly.Data.Array (Array) import WordCount (count, Counts(..), isSpace) -import qualified Streamly.Data.Array.Foreign as Array-import qualified Streamly.Internal.FileSystem.File as File (readChunks)-import qualified Streamly.Prelude as Stream+import qualified Streamly.Data.Array as Array+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Stream.Prelude as Stream+import qualified Streamly.FileSystem.File as File (readChunks) import qualified Streamly.Unicode.Stream as Stream -- Get the line, word, char counts in one chunk. countArray :: Array Word8 -> IO Counts countArray arr =- Stream.unfold Array.read arr -- SerialT IO Word8- & Stream.decodeLatin1 -- SerialT IO Char- & Stream.foldl' count (Counts 0 0 0 True) -- IO Counts+ Stream.unfold Array.reader arr -- Stream IO Word8+ & Stream.decodeLatin1 -- Stream IO Char+ & Stream.fold (Fold.foldl' count (Counts 0 0 0 True)) -- IO Counts -- When combining the counts in two contiguous chunks, we would also need to -- know whether the first element of the next chunk was a space char or@@ -29,7 +30,7 @@ {-# NOINLINE partialCounts #-} partialCounts :: Array Word8 -> IO (Bool, Counts) partialCounts arr = do- let r = Array.getIndex arr 0+ let r = Array.getIndex 0 arr case r of Just x -> do counts <- countArray arr@@ -49,11 +50,17 @@ -- apply our counting function to each array and then combine all the counts. wc :: String -> IO (Bool, Counts) wc file = do- Stream.unfold File.readChunks file -- AheadT IO (Array Word8)- & Stream.mapM partialCounts -- AheadT IO (Bool, Counts)- & Stream.maxThreads numCapabilities -- AheadT IO (Bool, Counts)- & Stream.fromAhead -- SerialT IO (Bool, Counts)- & Stream.foldl' addCounts (False, Counts 0 0 0 True) -- IO (Bool, Counts)+ File.readChunks file -- Stream IO (Array Word8)+ & Stream.parMapM+ ( Stream.maxThreads numCapabilities+ . Stream.ordered True+ )+ partialCounts -- Stream IO (Bool, Counts)+ & Stream.fold foldCounts -- IO (Bool, Counts)++ where++ foldCounts = Fold.foldl' addCounts (False, Counts 0 0 0 True) ------------------------------------------------------------------------------- -- Main
examples/WordCountParallelUTF8.hs view
@@ -34,19 +34,23 @@ import Data.Char (isSpace) import Data.Word (Word8) import GHC.Conc (numCapabilities)+import Streamly.Data.MutArray (MutArray)+import Streamly.Data.Stream.Prelude (Stream) import System.Environment (getArgs) import System.IO (Handle, openFile, IOMode(..)) -import qualified Streamly.Data.Array.Foreign as Array+import qualified Streamly.Data.Array as Array+import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.MutArray as MArray+import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.FileSystem.Handle as Handle-import qualified Streamly.Prelude as Stream -- Internal modules import qualified Streamly.Internal.Unicode.Stream as Unicode (DecodeState, DecodeError(..), CodePoint, decodeUtf8Either , resumeDecodeUtf8Either)-import qualified Streamly.Internal.Data.Array.Foreign.Mut.Type as MArray- (getIndexUnsafe, putIndexUnsafe, modifyIndexUnsafe, Array, newArray)+import qualified Streamly.Internal.Data.Array.Mut.Type as MArray+ (getIndexUnsafe, putIndexUnsafe, modifyIndexUnsafe) -------------------------------------------------------------------------------@@ -178,20 +182,20 @@ -- Default/initial state of the block ------------------------------------------------------------------------------- -readField :: MArray.Array Int -> Field -> IO Int+readField :: MutArray Int -> Field -> IO Int readField v fld = MArray.getIndexUnsafe (fromEnum fld) v -writeField :: MArray.Array Int -> Field -> Int -> IO ()-writeField v fld i = void $ MArray.putIndexUnsafe (fromEnum fld) i v+writeField :: MutArray Int -> Field -> Int -> IO ()+writeField v fld i = void $ MArray.putIndexUnsafe i v (fromEnum fld) -modifyField :: MArray.Array Int -> Field -> (Int -> Int) -> IO ()+modifyField :: MutArray Int -> Field -> (Int -> Int) -> IO () modifyField v fld f = do let index = fromEnum fld- MArray.modifyIndexUnsafe index (\x -> (f x, ())) v+ MArray.modifyIndexUnsafe index v (\x -> (f x, ())) -newCounts :: IO (MArray.Array Int)+newCounts :: IO (MutArray Int) newCounts = do- counts <- MArray.newArray (fromEnum (maxBound :: Field) + 1)+ counts <- MArray.newPinned (fromEnum (maxBound :: Field) + 1) writeField counts LineCount 0 writeField counts WordCount 0 writeField counts CharCount 0@@ -206,7 +210,7 @@ -- Counting chars ------------------------------------------------------------------------------- -accountChar :: MArray.Array Int -> Bool -> IO ()+accountChar :: MutArray Int -> Bool -> IO () accountChar counts isSp = do c <- readField counts CharCount let space = if isSp then 1 else 0@@ -218,7 +222,7 @@ -- Manipulating the header bytes ------------------------------------------------------------------------------- -addToHeader :: MArray.Array Int -> Int -> IO Bool+addToHeader :: MutArray Int -> Int -> IO Bool addToHeader counts cp = do cnt <- readField counts HeaderWordCount case cnt of@@ -237,7 +241,7 @@ return True _ -> return False -resetHeaderOnNewChar :: MArray.Array Int -> IO ()+resetHeaderOnNewChar :: MutArray Int -> IO () resetHeaderOnNewChar counts = do hdone <- readField counts HeaderDone when (hdone == 0) $ writeField counts HeaderDone 1@@ -246,13 +250,13 @@ -- Manipulating the trailer ------------------------------------------------------------------------------- -setTrailer :: MArray.Array Int -> Unicode.DecodeState -> Unicode.CodePoint -> IO ()+setTrailer :: MutArray Int -> Unicode.DecodeState -> Unicode.CodePoint -> IO () setTrailer counts st cp = do writeField counts TrailerState (fromIntegral st) writeField counts TrailerCodePoint cp writeField counts TrailerPresent 1 -resetTrailerOnNewChar :: MArray.Array Int -> IO ()+resetTrailerOnNewChar :: MutArray Int -> IO () resetTrailerOnNewChar counts = do trailer <- readField counts TrailerPresent when (trailer /= 0) $ do@@ -264,7 +268,7 @@ ------------------------------------------------------------------------------- {-# INLINE countChar #-}-countChar :: MArray.Array Int -> Either Unicode.DecodeError Char -> IO ()+countChar :: MutArray Int -> Either Unicode.DecodeError Char -> IO () countChar counts inp = case inp of Right ch -> do@@ -301,7 +305,7 @@ then accountChar counts True else setTrailer counts st cp -printCounts :: MArray.Array Int -> IO ()+printCounts :: MutArray Int -> IO () printCounts v = do l <- readField v LineCount w <- readField v WordCount@@ -318,9 +322,9 @@ -- combine trailing bytes in preceding block with leading bytes in the next -- block and decode them into a codepoint reconstructChar :: Int- -> MArray.Array Int- -> MArray.Array Int- -> IO (Stream.SerialT IO (Either Unicode.DecodeError Char))+ -> MutArray Int+ -> MutArray Int+ -> IO (Stream IO (Either Unicode.DecodeError Char)) reconstructChar hdrCnt v1 v2 = do when (hdrCnt > 3 || hdrCnt < 0) $ error "reconstructChar: hdrCnt > 3" stream1 <-@@ -346,7 +350,7 @@ cp <- readField v1 TrailerCodePoint return $ Unicode.resumeDecodeUtf8Either (fromIntegral state) cp stream3 -getHdrChar :: MArray.Array Int -> IO (Maybe Int)+getHdrChar :: MutArray Int -> IO (Maybe Int) getHdrChar v = do hdrCnt <- readField v HeaderWordCount case hdrCnt of@@ -372,7 +376,7 @@ -- If the header of the first block is not done then combine the header -- with the header of the next block.-combineHeaders :: MArray.Array Int -> MArray.Array Int -> IO ()+combineHeaders :: MutArray Int -> MutArray Int -> IO () combineHeaders v1 v2 = do hdone1 <- readField v1 HeaderDone when (hdone1 == 0) $ do@@ -387,7 +391,7 @@ -- the first vector and returning it. -- XXX This is a quick hack and can be refactored to reduce the size -- and understandability considerably.-addCounts :: MArray.Array Int -> MArray.Array Int -> IO (MArray.Array Int)+addCounts :: MutArray Int -> MutArray Int -> IO (MutArray Int) addCounts v1 v2 = do hdone1 <- readField v1 HeaderDone hdone2 <- readField v2 HeaderDone@@ -477,7 +481,7 @@ case res of Nothing -> error "addCounts: Bug. empty reconstructed char" Just (h, t) -> do- tlength <- Stream.length t+ tlength <- Stream.fold Fold.length t case h of Right ch -> do -- If we have an error case after this@@ -516,7 +520,7 @@ -- in partially decoded char to be written -- as trailer. Check if the last error is -- an incomplete decode.- r <- Stream.last t+ r <- Stream.fold Fold.latest t let (st', cp') = case r of Nothing -> (st, cp)@@ -557,34 +561,36 @@ -- Individual array processing is an isolated loop, fusing it with the bigger -- loop may be counter productive. {-# NOINLINE countArray #-}-countArray :: Array.Array Word8 -> IO (MArray.Array Int)+countArray :: Array.Array Word8 -> IO (MutArray Int) countArray src = do counts <- newCounts- Stream.mapM_ (countChar counts)+ Stream.fold (Fold.drainMapM (countChar counts)) $ Unicode.decodeUtf8Either- $ Stream.unfold Array.read src+ $ Stream.unfold Array.reader src return counts {-# INLINE wcMwlParallel #-}-wcMwlParallel :: Handle -> Int -> IO (MArray.Array Int)+wcMwlParallel :: Handle -> Int -> IO (MutArray Int) wcMwlParallel src n = do- Stream.foldlM' addCounts newCounts- $ Stream.fromAhead- $ Stream.maxThreads numCapabilities- $ Stream.mapM countArray- $ Stream.unfold Handle.readChunksWithBufferOf (n, src)+ Stream.fold (Fold.foldlM' addCounts newCounts)+ $ Stream.parMapM+ ( Stream.maxThreads numCapabilities+ . Stream.ordered True+ )+ countArray+ $ Stream.unfold Handle.chunkReaderWith (n, src) ------------------------------------------------------------------------------- -- Serial counting using parallel version of countChar ------------------------------------------------------------------------------- -- -- This is only for perf comparison-wcMwlParserial :: Handle -> IO (MArray.Array Int)+wcMwlParserial :: Handle -> IO (MutArray Int) wcMwlParserial src = do counts <- newCounts- Stream.mapM_ (countChar counts)+ Stream.fold (Fold.drainMapM (countChar counts)) $ Unicode.decodeUtf8Either- $ Stream.unfold Handle.read src+ $ Stream.unfold Handle.reader src return counts -------------------------------------------------------------------------------
examples/WordFrequency.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleInstances, CPP #-} {-# OPTIONS_GHC -Wno-orphans #-} @@ -7,28 +7,34 @@ import Data.Function ((&)) import Data.Functor.Identity (Identity(..)) import Data.Hashable (Hashable(..))-import Data.IORef (newIORef, readIORef, modifyIORef')-import Foreign.Storable (Storable(..)) import System.Environment (getArgs)+import Streamly.Data.Array (Unbox)+import Streamly.Internal.Data.IsMap.HashMap () import qualified Data.Char as Char import qualified Data.HashMap.Strict as Map import qualified Data.List as List import qualified Data.Ord as Ord-import qualified Streamly.Data.Array.Foreign as Array+import qualified Streamly.Data.Array as Array import qualified Streamly.Data.Fold as Fold-import qualified Streamly.Internal.FileSystem.File as File (toBytes)-import qualified Streamly.Prelude as Stream+import qualified Streamly.Internal.Data.Fold.Container as Fold (toContainerIO)+import qualified Streamly.Data.Stream as Stream+import qualified Streamly.Internal.Data.Stream as Stream (wordsBy)+import qualified Streamly.FileSystem.File as File import qualified Streamly.Unicode.Stream as Unicode -hashArray :: (Storable a, Integral b, Num c) =>+hashArray :: (Unbox a, Integral b, Num c) => Fold.Fold Identity a b -> Array.Array a -> c hashArray f arr =- Stream.unfold Array.read arr+ Stream.unfold Array.reader arr & Stream.fold f & fromIntegral . runIdentity -instance (Enum a, Storable a) => Hashable (Array.Array a) where+#if MIN_VERSION_hashable(1,4,0)+instance (Eq a, Enum a, Unbox a) => Hashable (Array.Array a) where+#else+instance (Enum a, Unbox a) => Hashable (Array.Array a) where+#endif hash = hashArray Fold.rollingHash hashWithSalt salt =@@ -54,25 +60,17 @@ main = do inFile <- fmap head getArgs + let counter = Fold.foldl' (\n _ -> n + 1) (0 :: Int)+ classifier = Fold.toContainerIO id counter -- Write the stream to a hashmap consisting of word counts mp <-- let- alter Nothing = Just <$> newIORef (1 :: Int)- alter (Just ref) = modifyIORef' ref (+ 1) >> return (Just ref)- in File.toBytes inFile -- SerialT IO Word8- & Unicode.decodeLatin1 -- SerialT IO Char- & Stream.map toLower -- SerialT IO Char- & Stream.wordsBy isSpace Fold.toList -- SerialT IO String- & Stream.filter (all isAlpha) -- SerialT IO String- & Stream.foldlM' (flip (Map.alterF alter)) (return Map.empty) -- IO (Map String (IORef Int))-- -- Print the top hashmap entries- counts <-- let readRef (w, ref) = do- cnt <- readIORef ref- return (w, cnt)- in Map.toList mp- & mapM readRef+ File.read inFile -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & fmap toLower -- Stream IO Char+ & Stream.wordsBy isSpace Fold.toList -- Stream IO String+ & Stream.filter (all isAlpha) -- Stream IO String+ & Stream.fold classifier -- IO (HashMap String Int) - traverse_ print $ List.sortOn (Ord.Down . snd) counts- & List.take 25+ traverse_ print+ $ List.sortOn (Ord.Down . snd) (Map.toList mp)+ & List.take 25
examples/WordServer.hs view
@@ -4,8 +4,9 @@ import Data.Function ((&)) import Network.Socket (Socket, close) -import qualified Streamly.Prelude as Stream import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Parser as Parser+import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.Network.Socket as Socket import qualified Streamly.Network.Inet.TCP as TCP import qualified Streamly.Unicode.Stream as Unicode@@ -20,17 +21,20 @@ -- connection is closed. lookupWords :: Socket -> IO () lookupWords sk =- Stream.unfold Socket.read sk -- SerialT IO Word8- & Unicode.decodeLatin1 -- SerialT IO Char- & Stream.wordsBy isSpace Fold.toList -- SerialT IO String- & Stream.fromSerial -- AheadT IO String- & Stream.mapM fetch -- AheadT IO (String, String)- & Stream.fromAhead -- SerialT IO (String, String)- & Stream.map show -- SerialT IO String- & Stream.intersperse "\n" -- SerialT IO String- & Unicode.encodeStrings Unicode.encodeLatin1 -- SerialT IO (Array Word8)+ Stream.unfold Socket.reader sk -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & Stream.parseMany word+ & Stream.catRights -- Stream IO String+ & Stream.parMapM (Stream.ordered True) fetch+ & fmap show -- Stream IO String+ & Stream.intersperse "\n" -- Stream IO String+ & Unicode.encodeStrings Unicode.encodeLatin1 -- Stream IO (Array Word8) & Stream.fold (Socket.writeChunks sk) -- IO () + where++ word = Parser.wordBy isSpace Fold.toList+ serve :: Socket -> IO () serve sk = finally (lookupWords sk) (close sk) @@ -39,8 +43,6 @@ -- "nc" as a client to try it out. main :: IO () main =- Stream.unfold TCP.acceptOnPort 8091 -- SerialT IO Socket- & Stream.fromSerial -- AsyncT IO ()- & Stream.mapM serve -- AsyncT IO ()- & Stream.fromAsync -- SerialT IO ()- & Stream.drain -- IO ()+ Stream.unfold TCP.acceptorOnPort 8091 -- Stream IO Socket+ & Stream.parMapM id (Socket.forSocketM serve) -- Stream IO ()+ & Stream.fold Fold.drain -- IO ()
streamly-examples.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: streamly-examples-version: 0.1.2+version: 0.1.3 license: Apache-2.0 license-file: LICENSE author: Composewell Technologies@@ -22,7 +22,7 @@ category: Streamly, Streaming, Concurrency, Text, Filesystem, Network, Reactivity stability: provisional-tested-with: GHC==9.0.1, GHC==8.10.5, GHC==8.8.4, GHC==8.6.5+tested-with: GHC==9.4.4, GHC==9.2.7, GHC==9.0.1, GHC==8.10.7 build-type: Simple extra-source-files: Changelog.md@@ -42,7 +42,7 @@ flag fusion-plugin description: Use fusion plugin for for best performance manual: True- default: False+ default: True flag sdl2 description: Include graphics examples using SDL2@@ -60,18 +60,19 @@ common exe-dependencies build-depends:- streamly == 0.8.2.*- , base >= 4.9 && < 4.17+ streamly == 0.9.0+ , streamly-core == 0.1.0+ , base >= 4.9 && < 4.18 , directory >= 1.2 && < 1.4- , transformers >= 0.4 && < 0.6+ , transformers >= 0.4 && < 0.7 , containers >= 0.5 && < 0.7 , random >= 1.0.0 && < 2 , exceptions >= 0.8 && < 0.11 , transformers-base >= 0.4 && < 0.5 , network >= 2.6 && < 4- , hashable >= 1.2 && < 1.4+ , hashable >= 1.2 && < 1.5 , unordered-containers >= 0.2 && < 0.3- , vector >= 0.12 && < 0.13+ , vector >= 0.12 && < 0.14 , mtl >= 2.2 && < 3 common exe-options@@ -96,7 +97,7 @@ if flag(fusion-plugin) && !impl(ghcjs) && !impl(ghc < 8.6) ghc-options: -fplugin Fusion.Plugin build-depends:- fusion-plugin >= 0.2 && < 0.3+ fusion-plugin >= 0.2.6 && < 0.3 common exe-options-threaded import: exe-options@@ -322,5 +323,23 @@ main-is: FileSystemEvent.hs if !impl(ghcjs) buildable: True+ else+ buildable: False++executable DateTimeParser+ import: exe-options+ main-is: DateTimeParser.hs+ if !impl(ghcjs)+ buildable: True+ build-depends: tasty-bench >= 0.3+ else+ buildable: False++executable LogParser+ import: exe-options+ main-is: LogParser.hs+ if !impl(ghcjs)+ buildable: True+ build-depends: tasty-bench >= 0.3 else buildable: False