streamly-examples 0.2.0 → 0.3.0
raw patch · 32 files changed
+795/−170 lines, 32 filesdep +filepathdep +streamly-fseventsdep ~basedep ~hashabledep ~streamlynew-component:exe:EchoClientnew-component:exe:ListDirBasicnew-component:exe:ListDirNaivenew-component:exe:ListDirOsPath
Dependencies added: filepath, streamly-fsevents
Dependency ranges changed: base, hashable, streamly, streamly-core, tasty-bench
Files
- Cargo.toml +15/−0
- Changelog.md +4/−0
- Makefile +6/−4
- README.md +37/−1
- examples/BasicDirStream.hsc +183/−0
- examples/CSVParser.hs +3/−3
- examples/CamelCase.hs +3/−3
- examples/CmdClient.hs +4/−3
- examples/CmdServer.hs +2/−2
- examples/ControlFlow.hs +26/−26
- examples/CoreUtils.hs +26/−21
- examples/CoreUtilsHandle.hs +4/−5
- examples/DateTimeParser.hs +15/−16
- examples/EchoClient.hs +36/−0
- examples/FileSystemEvent.hs +5/−16
- examples/Intro.hs +14/−11
- examples/ListDir.hs +201/−25
- examples/ListDirBasic.c +36/−0
- examples/ListDirBasic.hs +16/−0
- examples/ListDirBasic.rs +25/−0
- examples/ListDirNaive.hs +21/−0
- examples/ListDirOsPath.hs +25/−0
- examples/MergeServer.hs +4/−4
- examples/MergeSort.hs +7/−7
- examples/Rate.hs +1/−1
- examples/WalkDirBasic.rs +10/−0
- examples/WordCount.hs +6/−3
- examples/WordCountModular.hs +6/−3
- examples/WordCountParallel.hs +6/−3
- examples/WordCountParallelUTF8.hs +4/−4
- examples/WordFrequency.hs +4/−2
- streamly-examples.cabal +40/−7
+ Cargo.toml view
@@ -0,0 +1,15 @@+[package]+name = "listdir"+version = "0.1.0"+edition = "2021"++[dependencies]+walkdir = "2.4"++[[example]]+name = "WalkDirBasic"+path = "examples/WalkDirBasic.rs"++[[example]]+name = "ListDirBasic"+path = "examples/ListDirBasic.rs"
Changelog.md view
@@ -1,5 +1,9 @@ # Changelog +## 0.3.0 (Sep 2025)++* Support streamly-0.11.0 and streamly-core-0.3.0+ ## 0.2.0 (Dec 2023) * Support streamly-0.10.0 and streamly-core-0.2.0
Makefile view
@@ -1,10 +1,12 @@-CFLAGS += -std=gnu11 -Wall -Wextra -pedantic -march=native -Ofast+CFLAGS += -Wall -Wextra -pedantic -march=native -Ofast -all: examples/WordCount+EXAMPLES = examples/WordCount examples/ListDirBasic -examples/WordCount: examples/WordCount.c+all: $(EXAMPLES)++examples/%: examples/%.c $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) .PHONY: clean clean:- rm -f examples/WordCount+ rm -f $(EXAMPLES)
README.md view
@@ -59,7 +59,7 @@ * [FileSystemEvent](https://github.com/composewell/streamly-examples/blob/master/examples/FileSystemEvent.hs): File watching/fsnotify API example. * [ListDir](https://github.com/composewell/streamly-examples/blob/master/examples/ListDir.hs): List a directory tree recursively and- concurrently.+ concurrently, faster than rust fd. ### Text Processing @@ -110,6 +110,42 @@ stream type to and from `conduit` stream type. * [Interop.Vector](https://github.com/composewell/streamly-examples/blob/master/examples/Interop/Vector.hs): Converting streamly stream type to and from `vector` stream type.++## Comparing Haskell Performance with C and Rust++### Word Count++`examples/WordCount.c` is the C equivalent of `examples/WordCount.hs`:++```+$ make+$ examples/WordCount input.txt+```++### Directory Traversal++`examples/ListDirBasic.c` is the C equivalent of `examples/ListDirBasic.hs`:++```+$ make+$ examples/ListDirBasic+```++`examples/ListDirBasic.rs` is the Rust equivalent of+`examples/ListDirBasic.hs` using `std::fs` :++```+$ cargo build --example ListDirBasic --release+$ target/release/examples/ListDirBasic+```++`example/WalkDirBasic.rs` is the Rust equivalent of+``example/ListDirBasic.hs` using the `walkdir` crate:++```+$ cargo build --example WalkDirBasic --release+$ target/release/examples/WalkDirBasic+``` ## Licensing
+ examples/BasicDirStream.hsc view
@@ -0,0 +1,183 @@+{-# Language ScopedTypeVariables #-}+{-# Language CApiFFI #-}+-- {-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all #-}++-- | Removing the print statements in both C program and Haskell program. C+-- takes 58ms CPU time, Haskell +RTS -s reports 60ms CPU time (time reports+-- 62ms which includes 2ms of RTS startup/teardown overhead). Actual Haskell+-- processing is approximately 3% slower.+--+-- Learnings and notes:+--+-- * Be frugal with allocations, our perf tool can help in precisely+-- pin-pointing the places where more allocations are happening.+--+-- * static argument transformation is very important in reducing unnecessary+-- overhead in recursive loops, it becomes especially important when other+-- inefficiencies are removed. It will be nice if compiler can automatically+-- detect and point out such opportunities.+--+-- * Using mutable memory in a local loop is beneficial due to cache+-- benefits and reduction in allocations/collections. Haskell lacks good+-- mutable memory abstractions which should be fixed.+--+-- * In C we use local storage on stack and the stack unwinds as the function+-- returns, allocations and deallocations are cheaper. Even if we allocate a+-- variable earlier than it is used, we do not pay much cost. In Haskell, there+-- is no such thing as stack, all allocations happen on heap, once we allocate+-- we pay the cost of collection as well. Therefore, if we simulate stack+-- like mutable allocations in Haskell, when allocating strictly we have to be+-- careful to allocate only when needed.+--+-- * In multithreaded programs however Haskell might work better because in+-- that case each C thread requires allocation of a stack, in Haskell it is+-- natural as it always uses the heap. Thus the memory requirement of Haskell+-- is lower and there is no limit for stack.+--+-- * XXX there should be a way to automatically suggest NOINLINE when using+-- unsafeperformIO.+--+-- * XXX A CString module to manipulate CStrings conveniently?+--+-- * XXX An efficient unsafe variable length mutable cell/array module?++module BasicDirStream (loopDir0)++where++import Control.Monad (when)+import Data.Char (ord)+import Foreign (Ptr, Word8, nullPtr, peek, peekByteOff, castPtr, plusPtr)+import Foreign.C+ (resetErrno, Errno(..), getErrno, eINTR, throwErrno, CChar, CSize(..))+import Streamly.Internal.FileSystem.Posix.ReadDir (DirStream)+import Streamly.Internal.Data.MutByteArray (MutByteArray(..))+import System.IO (hPutBuf, stdout)++import qualified Streamly.Internal.Data.CString as CString+import qualified Streamly.Internal.Data.MutByteArray as MutByteArray+import qualified Streamly.Internal.FileSystem.Posix.ReadDir as ReadDir++#include <dirent.h>++{-# INLINE isMetaDir #-}+isMetaDir :: Ptr CChar -> IO Bool+isMetaDir dname = do+ -- XXX Assuming an encoding that maps "." to ".", this is true for+ -- UTF8.+ -- Load as soon as possible to optimize memory accesses+ c1 <- peek dname+ c2 :: Word8 <- peekByteOff dname 1+ if (c1 /= fromIntegral (ord '.'))+ then return False+ else do+ if (c2 == 0)+ then return True+ else do+ if (c2 /= fromIntegral (ord '.'))+ then return False+ else do+ c3 :: Word8 <- peekByteOff dname 2+ if (c3 == 0)+ then return True+ else return False++foreign import capi unsafe "dirent.h readdir"+ c_readdir :: DirStream -> IO (Ptr a)++foreign import ccall unsafe "string.h strlen" c_strlen_pinned+ :: Ptr a -> IO CSize++{-+-- Temporary mutable buffer+-- XXX In a multithreaded program, we will need a per thread mut var. So we+-- have to pass the mutvar in a function scope instead. It would be nice to+-- have first class mutable variable support in Haskell. We can use the current+-- thread-id as a token to create a variable on heap in the ST monad.+buffer :: MutByteArray+buffer = MutByteArray.emptyMutVar' 1024+-}++-- dirname should not be mutated, we can use an immutable ByteArray type for+-- that.+loopDir0 :: MutByteArray -> MutByteArray -> DirStream -> IO ()+loopDir0 buffer dirname0 dirp0 = loopDir dirname0 dirp0++ where++ -- XXX We can use a poke monad to do this more+ -- ergonomically. We can create a monad for composing+ -- CStrings, and equivalent of snprintf.+ allocBuf dirLen dname = do+ dentLen <- fmap fromIntegral $ c_strlen_pinned dname+ buf <- MutByteArray.new' (dirLen + dentLen + 2)+ return buf++ printName arr off = do+ -- If we use an Array then we can use a slice of the same+ -- buffer for printing and for passing to loopDir.+ MutByteArray.pokeAt off arr (10 :: Word8)+ -- hPutBuf is quite expensive compared to C printf.+ -- There should be an API that supplies the buffer directly to kernel.+ -- And we can build a buffer in the loop here and write it when it is+ -- ready.+ MutByteArray.unsafeAsPtr arr $ \p ->+ hPutBuf stdout p (off+1)++ -- XXX static argument transformations can create too many scoping levels,+ -- can there be a more convenient way to achieve this?+ loopDir dirname dirp = goDir++ where++ appendName dirLen buf dname = do+ -- XXX can use putCString variant instead+ MutByteArray.pokeAt 0 buf (0 :: Word8)+ _ <- CString.splice buf dirname+ MutByteArray.pokeAt dirLen buf (47 :: Word8)+ off <- CString.putCString buf (dirLen + 1) dname+ return off++ goDir = do+ resetErrno+ ptr <- c_readdir dirp -- streaming read, no buffering+ if (ptr /= nullPtr)+ then do+ let dname = #{ptr struct dirent, d_name} ptr+ dtype :: #{type unsigned char} <- #{peek struct dirent, d_type} ptr++ if dtype == (#const DT_DIR) -- dir/no-symlink check+ then do+ isMeta <- isMetaDir dname+ when (not isMeta) $ do+ dirLen <- CString.length dirname+ buf <- allocBuf dirLen dname+ off <- appendName dirLen buf dname++ -- Print the dir name+ arr <- MutByteArray.unsafePinnedCloneSlice 0 off buf+ printName arr off++ MutByteArray.unsafeAsPtr buf $ \s ->+ ReadDir.openDirStreamCString (castPtr s) >>= loopDir buf+ else do+ dirLen <- CString.length dirname+ -- Using a new buffer every time is more expensive+ -- buf <- allocBuf dirLen dname+ -- Use a mutable scratch buffer+ -- XXX If the size exceeds the scratch buffer then allocate+ -- a new buffer here.+ off <- appendName dirLen buffer dname+ printName buffer off+ return ()+ goDir+ else do+ errno <- getErrno+ if (errno == eINTR)+ then goDir+ else do+ let (Errno n) = errno+ if (n == 0)+ then do+ ReadDir.closeDirStream dirp+ else throwErrno "loopDir"
examples/CSVParser.hs view
@@ -13,7 +13,7 @@ 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 as ArrayStream (splitOn)+import qualified Streamly.Internal.Data.Array as Array (compactSepByByte_) main :: IO () main = do@@ -21,7 +21,7 @@ src <- IO.openFile inFile ReadMode Handle.readChunks src -- Stream IO (Array Word8)- & ArrayStream.splitOn 10 -- Stream IO (Array Word8)+ & Array.compactSepByByte_ 10 -- Stream IO (Array Word8) & Stream.fold (Fold.drainMapM parseLine) -- IO () where@@ -30,7 +30,7 @@ parseLine arr = (Array.read arr :: Stream IO Word8)- & Stream.splitOn (== 44) Fold.toList -- Stream IO [Word8]+ & Stream.splitSepBy_ (== 44) Fold.toList -- Stream IO [Word8] & Stream.intersperse [32] -- Stream IO [Word8] & Stream.fold (Fold.drainMapM printList) -- IO () >> putStrLn ""
examples/CamelCase.hs view
@@ -6,7 +6,7 @@ import System.Environment (getArgs) import System.IO (Handle, IOMode(..), openFile, stdout) -import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Scanl as Scanl import qualified Streamly.Data.Stream as Stream import qualified Streamly.FileSystem.Handle as Handle @@ -14,7 +14,7 @@ camelCase :: Handle -> Handle -> IO () camelCase src dst = Handle.read src -- Stream IO Word8- & Stream.postscan fold -- Stream IO (Bool, Maybe Word8)+ & Stream.postscanl fold -- Stream IO (Bool, Maybe Word8) & Stream.mapMaybe snd -- Stream IO Word8 & Stream.fold (Handle.write dst) -- IO () @@ -35,7 +35,7 @@ | isLower x = (False, Just $ if wasSpace then x - 32 else x) | otherwise = (True, Nothing) - fold = Fold.foldl' step (True, Nothing)+ fold = Scanl.mkScanl step (True, Nothing) main :: IO () main = do
examples/CmdClient.hs view
@@ -7,6 +7,7 @@ import Streamly.Data.Stream.Prelude (Stream) import qualified Streamly.Data.Fold as Fold+import qualified Streamly.Data.Scanl as Scanl import qualified Streamly.Data.Unfold as Unfold import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.Internal.Network.Inet.TCP as TCP (pipeBytes)@@ -34,7 +35,7 @@ sender :: Stream IO () sender = Stream.repeat "time\nrandom\n" -- Stream IO String- & Stream.unfoldMany Unfold.fromList -- Stream IO Char+ & Stream.unfoldEach Unfold.fromList -- Stream IO Char & Unicode.encodeLatin1 -- Stream IO Word8 & TCP.pipeBytes remoteAddr remotePort -- Stream IO Word8 & Unicode.decodeLatin1 -- Stream IO Char@@ -50,9 +51,9 @@ main = do Stream.replicate 4 sender -- Stream IO (Stream IO ()) & Stream.parConcat id -- Stream IO ()- & Stream.postscan counts -- Stream IO Int+ & Stream.postscanl counts -- Stream IO Int & Stream.fold Fold.drain -- IO () where - counts = Fold.foldlM' (counter "rcvd") (return 0 :: IO Int)+ counts = Scanl.mkScanlM (counter "rcvd") (return 0 :: IO Int)
examples/CmdServer.hs view
@@ -38,7 +38,7 @@ sendValue sk x = Stream.fromList (show x ++ "\n") & Unicode.encodeLatin1- & Stream.fold (Array.writeN 60)+ & Stream.fold (Array.createOf 60) >>= Socket.putChunk sk ------------------------------------------------------------------------------@@ -64,7 +64,7 @@ {-# 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))+demuxKvToMap f = Fold.demuxerToMap fst (fmap (Just . Fold.lmap snd) . f) demux :: Fold IO (String, Socket) () demux = void (demuxKvToMap commands)
examples/ControlFlow.hs view
@@ -27,7 +27,7 @@ import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE, catchE) import Control.Monad.Trans.Cont (ContT(..), callCC) import Streamly.Data.Stream (Stream)-import Streamly.Internal.Data.Stream (CrossStream, mkCross, unCross)+import Streamly.Internal.Data.Stream (Nested(..)) import qualified Streamly.Data.Fold as Fold import qualified Streamly.Data.Stream as Stream@@ -40,10 +40,10 @@ -- non-determinism. -- getSequenceMaybeBelow :: MonadIO m => Stream (MaybeT m) ()-getSequenceMaybeBelow = unCross $ do+getSequenceMaybeBelow = unNested $ do liftIO $ putStrLn "MaybeT below streamly: Enter one char per line: " - i <- mkCross $ Stream.fromList [1..2 :: Int]+ i <- Nested $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -69,11 +69,11 @@ -- Note that this is redundant configuration as the same behavior can be -- achieved with just streamly, using mzero. ---getSequenceMaybeAbove :: MonadIO m => MaybeT (CrossStream m) ()+getSequenceMaybeAbove :: MonadIO m => MaybeT (Nested m) () getSequenceMaybeAbove = do liftIO $ putStrLn "MaybeT above streamly: Enter one char per line: " - i <- lift $ mkCross $ Stream.fromList [1..2 :: Int]+ i <- lift $ Nested $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -82,7 +82,7 @@ r2 <- liftIO getLine when (r2 /= "y") mzero -mainMaybeAbove :: MonadIO m => MaybeT (CrossStream m) ()+mainMaybeAbove :: MonadIO m => MaybeT (Nested m) () mainMaybeAbove = do getSequenceMaybeAbove liftIO $ putStrLn "Bingo"@@ -96,10 +96,10 @@ -- Note that throwE would terminate all iterations of non-determinism -- altogether. getSequenceEitherBelow :: MonadIO m => Stream (ExceptT String m) ()-getSequenceEitherBelow = unCross $ do+getSequenceEitherBelow = unNested $ do liftIO $ putStrLn "ExceptT below streamly: Enter one char per line: " - i <- mkCross $ Stream.fromList [1..2 :: Int]+ i <- Nested $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -124,10 +124,10 @@ -- getSequenceEitherAsyncBelow :: MonadIO m => Stream (ExceptT String m) ()-getSequenceEitherAsyncBelow = unCross $ do+getSequenceEitherAsyncBelow = unNested $ do liftIO $ putStrLn "ExceptT below concurrent streamly: " - i <- mkCross+ i <- Nested $ Stream.consM (liftIO (threadDelay 1000) >> throwE "First task"@@ -158,11 +158,11 @@ -- -- Here we can use catchE directly but will have to use monad-control to lift -- stream operations with stream arguments.-getSequenceEitherAbove :: MonadIO m => ExceptT String (CrossStream m) ()+getSequenceEitherAbove :: MonadIO m => ExceptT String (Nested m) () getSequenceEitherAbove = do liftIO $ putStrLn "ExceptT above streamly: Enter one char per line: " - i <- lift $ mkCross $ Stream.fromList [1..2 :: Int]+ i <- lift $ Nested $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine@@ -171,7 +171,7 @@ r2 <- liftIO getLine when (r2 /= "y") $ throwE $ "Expecting y got: " <> r2 -mainEitherAbove :: MonadIO m => ExceptT String (CrossStream m) ()+mainEitherAbove :: MonadIO m => ExceptT String (Nested m) () mainEitherAbove = catchE (getSequenceEitherAbove >> liftIO (putStrLn "Bingo")) (liftIO . putStrLn)@@ -188,20 +188,20 @@ -- iterations of non-determinism rather then just the current iteration. -- getSequenceMonadThrow :: (MonadIO m, MonadThrow m) => Stream m ()-getSequenceMonadThrow = unCross $ do+getSequenceMonadThrow = unNested $ do liftIO $ putStrLn "MonadThrow in streamly: Enter one char per line: " - i <- mkCross $ Stream.fromList [1..2 :: Int]+ i <- Nested $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r1 <- liftIO getLine when (r1 /= "x")- $ mkCross+ $ Nested $ Stream.fromEffect $ throwM $ Unexpected $ "Expecting x got: " <> r1 r2 <- liftIO getLine when (r2 /= "y")- $ mkCross+ $ Nested $ Stream.fromEffect $ throwM $ Unexpected $ "Expecting y got: " <> r2 mainMonadThrow :: IO ()@@ -218,11 +218,11 @@ -- -- XXX need to have a specialized liftCallCC to actually lift callCC ---getSequenceContBelow :: MonadIO m => CrossStream (ContT r m) (Either String ())+getSequenceContBelow :: MonadIO m => Nested (ContT r m) (Either String ()) getSequenceContBelow = do liftIO $ putStrLn "ContT below streamly: Enter one char per line: " - i <- mkCross $ Stream.fromList [1..2 :: Int]+ i <- Nested $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i r <- lift $ callCC $ \exit -> do@@ -239,7 +239,7 @@ return r mainContBelow :: MonadIO m => Stream (ContT r m) ()-mainContBelow = unCross $ do+mainContBelow = unNested $ do r <- getSequenceContBelow case r of Right _ -> liftIO $ putStrLn "Bingo"@@ -249,11 +249,11 @@ -- Using ContT above streamly ------------------------------------------------------------------------------- ---getSequenceContAbove :: MonadIO m => ContT r (CrossStream m) (Either String ())+getSequenceContAbove :: MonadIO m => ContT r (Nested m) (Either String ()) getSequenceContAbove = do liftIO $ putStrLn "ContT above streamly: Enter one char per line: " - i <- lift $ mkCross $ Stream.fromList [1..2 :: Int]+ i <- lift $ Nested $ Stream.fromList [1..2 :: Int] liftIO $ putStrLn $ "iteration = " <> show i callCC $ \exit -> do@@ -267,7 +267,7 @@ then exit $ Left $ "Expecting y got: " <> r2 else return $ Right () -mainContAbove :: MonadIO m => ContT r (CrossStream m) ()+mainContAbove :: MonadIO m => ContT r (Nested m) () mainContAbove = do r <- getSequenceContAbove case r of@@ -282,10 +282,10 @@ main :: IO () main = do mainMaybeBelow- Stream.fold Fold.drain $ unCross $ runMaybeT mainMaybeAbove+ Stream.fold Fold.drain $ unNested $ runMaybeT mainMaybeAbove runContT (Stream.fold Fold.drain mainContBelow) return- Stream.fold Fold.drain $ unCross $ runContT mainContAbove return+ Stream.fold Fold.drain $ unNested $ runContT mainContAbove return mainEitherBelow- Stream.fold Fold.drain $ unCross $ runExceptT mainEitherAbove+ Stream.fold Fold.drain $ unNested $ runExceptT mainEitherAbove mainMonadThrow mainEitherAsyncBelow
examples/CoreUtils.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE QuasiQuotes #-}+ -- Implement some simple coreutils commands import Control.Monad (void)@@ -6,46 +8,49 @@ import System.Environment (getArgs) import Streamly.Data.Array (Array) import Streamly.Data.Fold (Fold)+import Streamly.FileSystem.Path (path) import qualified Streamly.Console.Stdio as Stdio import qualified Streamly.Data.Fold as Fold 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.Internal.FileSystem.DirIO as Dir (readFiles)+import qualified Streamly.Internal.FileSystem.FileIO as File -- | > cat input.txt cat :: IO () cat =- File.readChunks "input.txt" -- Stream IO (Array Word8)- & Stream.fold Stdio.writeChunks -- IO ()+ File.readChunks [path|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.readFiles "dir" -- Stream IO String- & Stream.unfoldMany File.chunkReader -- Stream IO (Array Word8)- & File.fromChunks "output.txt" -- IO ()+ Dir.readFiles id [path|dir|] -- Stream IO Path+ -- NOTE: This is a redundant step that we are forced to do as we've still+ -- not introduced FileIO module that uses Path yet.+ & Stream.unfoldEach File.chunkReader -- Stream IO (Array Word8)+ & File.fromChunks [path|output.txt|] -- IO () -- | > cp input.txt output.txt cp :: IO () cp =- File.readChunks "input.txt" -- Stream IO (Array Word8)- & File.fromChunks "output.txt" -- IO ()+ File.readChunks [path|input.txt|] -- Stream IO (Array Word8)+ & File.fromChunks [path|output.txt|] -- IO () -- | > cat input.txt >> output.txt append :: IO () append =- File.readChunks "input.txt" -- Stream IO (Array Word8)- & File.writeAppendChunks "output.txt" -- IO ()+ File.readChunks [path|input.txt|] -- Stream IO (Array Word8)+ & File.writeAppendChunks [path|output.txt|] -- IO () -- | > cat input.txt | tee output1.txt > output.txt tap :: IO () tap =- File.readChunks "input.txt" -- Stream IO (Array Word8)- & Stream.tap (File.writeChunks "output1.txt") -- Stream IO (Array Word8)- & File.fromChunks "output.txt" -- IO ()+ File.readChunks [path|input.txt|] -- Stream IO (Array Word8)+ & Stream.tap (File.writeChunks [path|output1.txt|]) -- Stream IO (Array Word8)+ & File.fromChunks [path|output.txt|] -- IO () {- -- | > cat input.txt | tee output1.txt > output.txt -- output1.txt is processed/written to in a separate thread.@@ -54,28 +59,28 @@ Stdio.readChunks () -- Stream IO (Array Word8) & Stream.tapAsyncK (File.fromChunks "output1.txt") -- Stream IO (Array Word8)- & File.fromChunks "output.txt" -- IO ()+ & File.fromChunks [path|output.txt|] -- IO () -} -- | > cat input.txt | tee output1.txt > output.txt tee :: IO () tee =- File.readChunks "input.txt" -- Stream IO (Array Word8)- & Stream.fold t -- IO ((),())- & void -- IO ()+ File.readChunks [path|input.txt|] -- Stream IO (Array Word8)+ & Stream.fold t -- IO ((),())+ & void -- IO () where -- Combine two folds into a single fold t :: Fold IO (Array Word8) ((),()) t = Fold.tee- (File.writeChunks "output1.txt") -- Fold IO (Array Word8) ()- (File.writeChunks "output.txt") -- Fold IO (Array Word8) ()+ (File.writeChunks [path|output1.txt|]) -- Fold IO (Array Word8) ()+ (File.writeChunks [path|output.txt|]) -- Fold IO (Array Word8) () {- -- | > grep -c "the" input.txt grepc :: IO () grepc = do- File.toBytes "input.txt" -- Stream IO Word8+ File.toBytes [path|input.txt|] -- Stream IO Word8 & Stream.splitOnSeq (Array.fromList pat) Fold.drain -- Stream IO () & Stream.length -- IO Int >>= print . subtract 1 -- IO ()
examples/CoreUtilsHandle.hs view
@@ -9,8 +9,7 @@ import qualified Streamly.Data.Stream as Stream import qualified Streamly.FileSystem.Handle as Handle import qualified Streamly.Unicode.Stream as Unicode--import qualified Streamly.Internal.Data.Array.Stream as ArrayStream (splitOn)+import qualified Streamly.Internal.Data.Array as Array (compactSepByByte_) -- | Read the contents of a file to stdout. --@@ -73,9 +72,9 @@ -- first. wcl :: Handle -> IO Int wcl src =- Handle.readChunks src -- Stream IO (Array Word8)- & ArrayStream.splitOn 10 -- Stream IO (Array Word8)- & Stream.fold Fold.length -- IO ()+ Handle.readChunks src -- Stream IO (Array Word8)+ & Array.compactSepByByte_ 10 -- Stream IO (Array Word8)+ & Stream.fold Fold.length -- IO () main :: IO () main = do
examples/DateTimeParser.hs view
@@ -19,7 +19,6 @@ 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)@@ -153,19 +152,19 @@ _parseBreakDateTime :: Array Char -> IO Int _parseBreakDateTime arr = do let s = StreamK.fromStream $ Stream.fromPure arr- p = ParserK.adaptC . 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+ p = Array.toParserK . Parser.fromFold+ (Right year, s1) <- Array.parseBreak (p $ decimal 4) s+ (_, s2) <- Array.parseBreak (p $ char '-') s1+ (Right month, s3) <- Array.parseBreak (p $ decimal 2) s2+ (_, s4) <- Array.parseBreak (p $ char '-') s3+ (Right day, s5) <- Array.parseBreak (p $ decimal 2) s4+ (_, s6) <- Array.parseBreak (p $ char 'T') s5+ (Right hr, s7) <- Array.parseBreak (p $ decimal 2) s6+ (_, s8) <- Array.parseBreak (p $ char ':') s7+ (Right mn, s9) <- Array.parseBreak (p $ decimal 2) s8+ (_, s10) <- Array.parseBreak (p $ char ':') s9+ (Right sec, s11) <- Array.parseBreak (p $ decimal 2) s10+ (_, _) <- Array.parseBreak (p $ char 'Z') s11 return (year + month + day + hr + mn + sec) -------------------------------------------------------------------------------@@ -175,12 +174,12 @@ {-# NOINLINE _parseKDateTime #-} _parseKDateTime :: Array Char -> IO Int _parseKDateTime arr = do- r <- StreamK.parseChunks dateParser $ StreamK.fromPure arr+ r <- Array.parse dateParser $ StreamK.fromPure arr return $ fromRight (error "failed") r where - p = ParserK.adaptC+ p = Array.toParserK dateParser = do year <- p $ Parser.decimal <* Parser.char '-'
+ examples/EchoClient.hs view
@@ -0,0 +1,36 @@+module Main (main) where++import Data.Function ((&))+import Data.Word (Word8)+import Network.Socket (PortNumber)+import Streamly.Data.Stream.Prelude (Stream)++import qualified Streamly.Console.Stdio as Stdio+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.Internal.Network.Inet.TCP as TCP+import qualified Streamly.Unicode.Stream as Unicode+++remoteAddr :: (Word8,Word8,Word8,Word8)+remoteAddr = (127, 0, 0, 1)++remotePort :: PortNumber+remotePort = 8091++echo :: Stream IO ()+echo =+ Stream.unfold Stdio.reader () -- Stream IO Word8+ & split (== 10) Array.create -- Stream IO (Array Word8)+ & TCP.pipeChunks remoteAddr remotePort -- Stream IO (Array Word8)+ & Stream.unfoldEach Array.reader -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & Stream.mapM putChar -- Stream IO ()++ where++ split p f = Stream.foldMany (Fold.takeEndBy p f)++main :: IO ()+main = Stream.fold Fold.drain echo
examples/FileSystemEvent.hs view
@@ -2,42 +2,31 @@ -- Report all events recursively under the paths provided as arguments module Main (main) where -import Control.Monad.IO.Class (MonadIO) import Data.Function ((&))-import Data.Word (Word8) import System.Environment (getArgs)-import Streamly.Data.Array (Array) import qualified Data.List.NonEmpty as NonEmpty-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+import qualified Streamly.FileSystem.Path as Path #if darwin_HOST_OS-import qualified Streamly.Internal.FileSystem.Event.Darwin as Event+import qualified Streamly.Internal.FS.Event.Darwin as Event #elif linux_HOST_OS-import qualified Streamly.Internal.FileSystem.Event.Linux as Event+import qualified Streamly.Internal.FS.Event.Linux as Event #elif mingw32_HOST_OS-import qualified Streamly.Internal.FileSystem.Event.Windows as Event+import qualified Streamly.Internal.FS.Event.Windows as Event #else #error "FS Events not supported on this platform" #endif ---------------------------------------------------------------------------------- Utilities----------------------------------------------------------------------------------toUtf8 :: MonadIO m => String -> m (Array Word8)-toUtf8 s = Stream.fold Array.write (Unicode.encodeUtf8' $ Stream.fromList s)--------------------------------------------------------------------------------- -- Main ------------------------------------------------------------------------------- main :: IO () main = do args <- getArgs- paths <- mapM toUtf8 args+ paths <- mapM Path.fromString args Event.watch (NonEmpty.fromList paths) & Stream.fold (Fold.drainMapM (putStrLn . Event.showEvent))
examples/Intro.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE QuasiQuotes #-}+ -- Introductory example programs import Control.Concurrent (threadDelay)@@ -11,14 +13,15 @@ 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 Streamly.FileSystem.Path (path)+import Streamly.Internal.Data.Stream (Nested(..)) 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.FileSystem.Handle as Handle-import qualified Streamly.FileSystem.File as File+import qualified Streamly.FileSystem.FileIO as File import qualified Streamly.Unicode.Stream as Unicode -------------------------------------------------------------------------------@@ -63,11 +66,11 @@ -- efficient. The second stream may depend on the first stream. The loops -- cannot fuse completely. ---nestedLoops :: CrossStream IO ()+nestedLoops :: Nested IO () nestedLoops = do- x <- mkCross $ Stream.fromList [3,4 :: Int]- y <- mkCross $ Stream.fromList [1..x]- mkCross $ Stream.fromEffect $ print (x, y)+ x <- Nested $ Stream.fromList [3,4 :: Int]+ y <- Nested $ Stream.fromList [1..x]+ Nested $ Stream.fromEffect $ print (x, y) ------------------------------------------------------------------------------- -- Text processing@@ -79,7 +82,7 @@ -- | Find average line length for lines in a text file avgLineLength :: IO Double avgLineLength =- File.read "input.txt" -- Stream IO Word8+ File.read [path|input.txt|] -- Stream IO Word8 & splitOn isNewLine Fold.length -- Stream IO Int & Stream.fold avg -- IO Double @@ -103,7 +106,7 @@ -- | Read text from a file and generate a histogram of line length lineLengthHistogram :: IO (Map Int Int) lineLengthHistogram =- File.read "input.txt" -- Stream IO Word8+ File.read [path|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)@@ -119,7 +122,7 @@ -- | Read text from a file and generate a histogram of word length wordLengthHistogram :: IO (Map Int Int) wordLengthHistogram =- File.read "input.txt" -- Stream IO Word8+ File.read [path|input.txt|] -- Stream IO Word8 & Unicode.decodeLatin1 -- Stream IO Char & Stream.wordsBy isSpace Fold.length -- Stream IO Int & fmap bucket -- Stream IO (Int, Int)@@ -157,7 +160,7 @@ & fmap Array.fromList -- Stream IO (Array Word8) & Stream.fold (Handle.writeChunks stdout) -- IO () - where unlinesBy = Stream.intercalateSuffix (Unfold.function id)+ where unlinesBy k = Stream.unfoldEachEndBySeq k (Unfold.function id) ------------------------------------------------------------------------------- -- Main@@ -184,7 +187,7 @@ print $ runIdentity $ crossProduct (1,1000) (1000,2000) "nestedLoops" -> do putStrLn "nestedLoops"- Stream.fold Fold.drain $ unCross nestedLoops+ Stream.fold Fold.drain $ unNested nestedLoops "avgLineLength" -> do putStrLn "avgLineLength" avgLineLength >>= print
examples/ListDir.hs view
@@ -1,36 +1,212 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-}-{-# OPTIONS_GHC -Wno-deprecations #-}+{-# OPTIONS_GHC -Wno-unused-binds #-}+{-# OPTIONS_GHC -Wno-unused-imports #-} +-- This is faster than the rust "fd". To compare listing the entire tree+-- recursively, use the following commands:+--+-- $ time fd -u > /dev/null+-- $ time ListDir > /dev/null+--+-- Running on a sample directory tree the concurrent rust "fd" tool took 150 ms+-- (real time). On the same tree the fastest variant using Haskell streamly+-- below took 94 ms. The time taken by other variants on the same tree is noted+-- in the comments. The fastest serial implementation using Haskell streamly+-- takes similar time as the concurrent rust "fd".+--+-- The code for directory traversal is just a few lines. This file is bigger+-- because we have implemented it in around 27 possible ways. To try other+-- variants just uncomment the relevant line and comment the currently enabled+-- line.+ module Main (main) where +import Data.Maybe (fromJust)+import Data.Word (Word8)+import Streamly.Data.Array (Array)+import Streamly.Data.Stream (Stream)+import Streamly.Data.Unfold (Unfold)+import Streamly.FileSystem.DirIO (ReadOptions)+import Streamly.FileSystem.Path (Path) import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering)) -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+import qualified Streamly.Data.Array as Array+import qualified Streamly.FileSystem.DirIO as DirIO+import qualified Streamly.Internal.Data.Array as Array (compactMax')+import qualified Streamly.Internal.Data.Stream as Stream+ (unfoldEachEndBy, concatIterateDfs, concatIterateBfs, concatIterateBfsRev)+import qualified Streamly.Data.StreamK as StreamK+import qualified Streamly.Internal.Data.StreamK as StreamK+ (concatIterateWith, mergeIterateWith)+import qualified Streamly.Data.Unfold as Unfold+import qualified Streamly.Internal.Data.Unfold as Unfold+ (either, nil)+import qualified Streamly.Internal.FileSystem.DirIO as Dir+ (readEitherChunks, readEitherPaths, eitherReaderPaths)+import qualified Streamly.FileSystem.Handle as Handle+import qualified Streamly.FileSystem.Path as Path+import qualified Streamly.Internal.FileSystem.Path as Path (toArray)+#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+import qualified Streamly.Internal.FileSystem.Posix.ReadDir as Dir+ (readEitherByteChunks)+#endif --- | List the current directory recursively using concurrent processing-main :: IO ()-main = do- hSetBuffering stdout LineBuffering- 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 ".")+{-# INLINE recReadOpts #-}+recReadOpts :: ReadOptions -> ReadOptions+recReadOpts =+ DirIO.followSymlinks True+ . DirIO.ignoreSymlinkLoops False+ . DirIO.ignoreMissing True+ . DirIO.ignoreInaccessible True +#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+-- Fastest implementation, only works for posix as of now.+listDirByteChunked :: IO ()+listDirByteChunked = do+ Stream.fold (Handle.writeChunks stdout)+ -- $ Array.compactMax' 32000+ $ Stream.catRights++ -- Serial+ -- $ Stream.concatIterateDfs streamDirMaybe -- 154 ms+ -- $ Stream.concatIterateBfs streamDirMaybe -- 154 ms+ -- $ Stream.concatIterateBfsRev streamDirMaybe -- 154 ms++ -- Serial using stream append and interleave+ -- $ concatIterateWith StreamK.append -- 154 ms+ -- $ mergeIterateWith StreamK.interleave -- 154 ms++ -- Concurrent+ -- XXX To reduce concurrency overhead, perform buffering in each worker+ -- and post the buffer or return [Path] and then unfold it.+ $ Stream.parConcatIterate id streamDir -- 94 ms+ -- $ Stream.parConcatIterate (Stream.interleaved True) streamDir -- 94 ms+ -- $ Stream.parConcatIterate (Stream.ordered True) streamDir -- 154 ms++ $ Stream.fromPure (Left [fromJust $ Path.fromString "."])+ where - -- unfoldOne = Unfold.either Dir.eitherReaderPaths Unfold.nil- -- streamOneMaybe = either (Just . Dir.readEitherPaths) (const Nothing)- streamOne = either Dir.readEitherPaths (const Stream.nil)+ concatIterateWith f =+ StreamK.toStream+ . StreamK.concatIterateWith f (StreamK.fromStream . streamDir)+ . StreamK.fromStream++ mergeIterateWith f =+ StreamK.toStream+ . StreamK.mergeIterateWith f (StreamK.fromStream . streamDir)+ . StreamK.fromStream++ -- cfg = Stream.eager False . Stream.maxBuffer 2000 . Stream.maxThreads 2+ streamDir :: Either [Path] b -> Stream IO (Either [Path] (Array Word8))+ streamDir = either (Dir.readEitherByteChunks recReadOpts) (const Stream.nil)++ streamDirMaybe :: Either [Path] b -> Maybe (Stream IO (Either [Path] (Array Word8)))+ streamDirMaybe = either (Just . Dir.readEitherByteChunks recReadOpts) (const Nothing)+#endif++-- Faster than the listDir implementation below+listDirChunked :: IO ()+listDirChunked = do+ Stream.fold (Handle.writeWith 32000 stdout)+ $ Stream.unfoldEachEndBy 10 Array.reader+ $ fmap Path.toArray+ $ Stream.unfoldEach Unfold.fromList+ $ fmap (either id id)++ -- Serial using streams+ -- $ Stream.concatIterateDfs streamDirMaybe -- 264 ms+ -- $ Stream.concatIterateBfs streamDirMaybe -- 264 ms+ -- $ Stream.concatIterateBfsRev streamDirMaybe -- 264 ms++ -- Serial using stream append and interleave+ -- $ concatIterateWith StreamK.append -- 164 ms+ -- $ mergeIterateWith StreamK.interleave -- 194 ms++ -- Concurrent+ $ Stream.parConcatIterate id streamDir -- 124 ms+ -- $ Stream.parConcatIterate (Stream.interleaved True) streamDir -- 134 ms+ -- $ Stream.parConcatIterate (Stream.ordered True) streamDir -- 174 ms++ $ Stream.fromPure (Left [fromJust $ Path.fromString "."])++ where++ concatIterateWith f =+ StreamK.toStream+ . StreamK.concatIterateWith f (StreamK.fromStream . streamDir)+ . StreamK.fromStream++ mergeIterateWith f =+ StreamK.toStream+ . StreamK.mergeIterateWith f (StreamK.fromStream . streamDir)+ . StreamK.fromStream++ streamDir :: Either [Path] b -> Stream IO (Either [Path] [Path])+ streamDir = either (Dir.readEitherChunks recReadOpts) (const Stream.nil)++ streamDirMaybe :: Either [Path] b -> Maybe (Stream IO (Either [Path] [Path]))+ streamDirMaybe = either (Just . Dir.readEitherChunks recReadOpts) (const Nothing)++listDir :: IO ()+listDir = do+ Stream.fold (Handle.writeWith 32000 stdout)+ $ Stream.unfoldEachEndBy 10 Array.reader+ $ fmap (Path.toArray . either id id)++ -- Serial using unfolds+ -- $ Stream.unfoldIterateDfs unfoldDir -- 284 ms+ -- May fail with too many open files+ -- $ Stream.unfoldIterateBfs unfoldDir+ -- $ Stream.unfoldIterateBfsRev unfoldDir -- 344 ms++ -- Serial using streams+ -- $ Stream.concatIterateDfs streamDirMaybe -- 274 ms+ -- $ Stream.concatIterateBfs streamDirMaybe -- 274 ms+ -- $ Stream.concatIterateBfsRev streamDirMaybe -- 264 ms++ -- Serial using stream append and interleave+ -- $ concatIterateWith StreamK.append -- 204 ms+ -- $ mergeIterateWith StreamK.interleave -- 304 ms++ -- Concurrent+ $ Stream.parConcatIterate id streamDir -- 174 ms+ -- $ Stream.parConcatIterate (Stream.interleaved True) streamDir -- 224 ms+ -- $ Stream.parConcatIterate (Stream.ordered True) streamDir -- 234 ms++ $ Stream.fromPure (Left (fromJust $ Path.fromString "."))++ where++ concatIterateWith f =+ StreamK.toStream+ . StreamK.concatIterateWith f (StreamK.fromStream . streamDir)+ . StreamK.fromStream++ mergeIterateWith f =+ StreamK.toStream+ . StreamK.mergeIterateWith f (StreamK.fromStream . streamDir)+ . StreamK.fromStream++ streamDir :: Either Path b -> Stream IO (Either Path Path)+ streamDir = either (Dir.readEitherPaths recReadOpts) (const Stream.nil)++ unfoldDir :: Unfold IO (Either Path b) (Either Path Path)+ unfoldDir = Unfold.either (Dir.eitherReaderPaths recReadOpts) Unfold.nil++ streamDirMaybe :: Either Path b -> Maybe (Stream IO (Either Path Path))+ streamDirMaybe = either (Just . Dir.readEitherPaths recReadOpts) (const Nothing)++-- | List the current directory recursively+main :: IO ()+main = do+ hSetBuffering stdout LineBuffering+#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+ listDirByteChunked+#else+ listDirChunked+#endif+ -- listDirChunked+ -- listDir
+ examples/ListDirBasic.c view
@@ -0,0 +1,36 @@+#include <stdio.h>+#include <stdlib.h>+#include <dirent.h>+#include <string.h>++void list_directory(const char *path) {+ DIR *dir = opendir(path);+ if (dir == NULL) {+ perror("opendir");+ return;+ }++ struct dirent *entry;+ char full_path[1024];++ while ((entry = readdir(dir)) != NULL) {+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {+ continue;+ }++ snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);+ printf("%s\n", full_path);++ if (entry->d_type == DT_DIR) {+ list_directory(full_path);+ }+ }++ closedir(dir);+}++int main() {+ const char *path = ".";+ list_directory(path);+ return 0;+}
+ examples/ListDirBasic.hs view
@@ -0,0 +1,16 @@+module Main (main) where++import BasicDirStream (loopDir0)+import Streamly.Internal.Data.Array (arrContents)++import qualified Streamly.Internal.Data.MutByteArray as MutByteArray+import qualified Streamly.Internal.FileSystem.Path as Path+import qualified Streamly.Internal.FileSystem.Posix.ReadDir as ReadDir++main :: IO ()+main = do+ name <- Path.fromString "."+ let arr = Path.toArray name+ buffer <- MutByteArray.new' 1024+ ReadDir.openDirStream name+ >>= loopDir0 buffer (arrContents arr)
+ examples/ListDirBasic.rs view
@@ -0,0 +1,25 @@+use std::fs;+use std::path::Path;++fn list_dir(path: &Path) {+ if let Ok(entries) = fs::read_dir(path) {+ for entry in entries.filter_map(|e| e.ok()) {+ let entry_path = entry.path();+ println!("{}", entry_path.display());++ if let Ok(metadata) = entry.metadata() {+ if metadata.is_dir() && !metadata.file_type().is_symlink() {+ list_dir(&entry_path);+ }+ }+ }+ } else {+ eprintln!("Could not access: {}", path.display());+ }+}++fn main() {+ let dir = ".";+ let path = Path::new(dir);+ list_dir(path);+}
+ examples/ListDirNaive.hs view
@@ -0,0 +1,21 @@+import Control.Monad+import System.Directory+import System.FilePath++listDir :: FilePath -> IO ()+listDir dir = do+ contents <- listDirectory dir+ let fullPaths = map (dir </>) contents+ forM_ fullPaths $ \path -> do+ isDir <- doesDirectoryExist path+ if isDir+ then do+ symlink <- pathIsSymbolicLink path+ if symlink+ then putStrLn path+ else listDir path -- depth first+ else putStrLn path+ putStrLn dir++main :: IO ()+main = listDir "."
+ examples/ListDirOsPath.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE QuasiQuotes #-}+module Main (main) where++import Control.Monad+import System.Directory.OsPath+import System.OsPath+import Data.Maybe++listDir :: OsPath -> IO ()+listDir dir = do+ contents <- listDirectory dir+ let fullPaths = map (dir </>) contents+ forM_ fullPaths $ \path -> do+ isDir <- doesDirectoryExist path+ if isDir+ then do+ symlink <- pathIsSymbolicLink path+ if symlink+ then putStrLn $ fromJust $ decodeUtf path+ else listDir path -- depth first+ else putStrLn $ fromJust $ decodeUtf path+ putStrLn $ fromJust $ decodeUtf dir++main :: IO ()+main = listDir [osp|.|]
examples/MergeServer.hs view
@@ -17,9 +17,9 @@ -- a limit to the buffering for safety. readLines :: Socket -> Stream IO (Array Char) readLines sk =- Socket.read sk -- Stream IO Word8- & Unicode.decodeLatin1 -- Stream IO Char- & split (== '\n') Array.write -- Stream IO String+ Socket.read sk -- Stream IO Word8+ & Unicode.decodeLatin1 -- Stream IO Char+ & split (== '\n') Array.create -- Stream IO String where @@ -36,7 +36,7 @@ server file = TCP.accept 8090 -- Stream IO Socket & Stream.parConcatMap (Stream.eager True) recv -- Stream IO (Array Char)- & Stream.unfoldMany Array.reader -- Stream IO Char+ & Stream.unfoldEach Array.reader -- Stream IO Char & Unicode.encodeLatin1 -- Stream IO Word8 & Stream.fold (Handle.write file) -- IO ()
examples/MergeSort.hs view
@@ -15,7 +15,7 @@ import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.Data.StreamK as K -import qualified Streamly.Internal.Data.Stream as Stream (reduceIterateBfs)+import qualified Streamly.Internal.Data.Stream as Stream (bfsReduceIterate) input :: [Int] input = [1000000,999999..1]@@ -31,7 +31,7 @@ . Array.read sortChunk :: Array Int -> IO (Array Int)-sortChunk = Stream.fold Array.write . streamChunk+sortChunk = Stream.fold Array.create . streamChunk ------------------------------------------------------------------------------- -- Stream the unsorted chunks and sort, merge those streams.@@ -41,7 +41,7 @@ sortMergeCombined :: (Array Int -> Stream IO Int) -> IO () sortMergeCombined f = Stream.fromList input- & Stream.chunksOf chunkSize+ & Array.chunksOf chunkSize & K.fromStream & K.mergeMapWith (K.mergeBy compare) (K.fromStream . f) & K.toStream@@ -60,7 +60,7 @@ -> IO () sortMergeSeparate f = Stream.fromList input- & Stream.chunksOf chunkSize+ & Array.chunksOf chunkSize & f sortChunk & K.fromStream & K.mergeMapWith (K.mergeBy compare) (K.fromStream . Array.read)@@ -78,7 +78,7 @@ compare (Array.read arr1) (Array.read arr2)- & Stream.fold Array.write+ & Stream.fold Array.create sortMergeChunks :: ( (Array Int -> IO (Array Int))@@ -88,9 +88,9 @@ -> IO () sortMergeChunks f = Stream.fromList input- & Stream.chunksOf chunkSize+ & Array.chunksOf chunkSize & f sortChunk- & Stream.reduceIterateBfs reduce+ & Stream.bfsReduceIterate reduce & void -- | Divide a stream in chunks, sort the chunks and merge them.
examples/Rate.hs view
@@ -7,5 +7,5 @@ main = Stream.sequence (Stream.repeat (pure "tick")) & Stream.timestamped- & Stream.parEval (Stream.avgRate 1)+ & Stream.parBuffered (Stream.avgRate 1) & Stream.fold (Fold.drainMapM print)
+ examples/WalkDirBasic.rs view
@@ -0,0 +1,10 @@+use walkdir::WalkDir;+use std::env;++fn main() {+ dir = ".";++ for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {+ println!("{}", entry.path().display());+ }+}
examples/WordCount.hs view
@@ -7,10 +7,12 @@ import Data.Char (ord) import Data.Function ((&)) import System.Environment (getArgs)+import Streamly.FileSystem.Path (Path) import qualified Streamly.Data.Fold as Fold import qualified Streamly.Data.Stream as Stream-import qualified Streamly.FileSystem.File as File+import qualified Streamly.FileSystem.FileIO as File+import qualified Streamly.FileSystem.Path as Path import qualified Streamly.Unicode.Stream as Stream -------------------------------------------------------------------------------@@ -39,7 +41,7 @@ else (if wasSpace then w + 1 else w, False) in Counts l1 w1 (c + 1) wasSpace1 -wc :: String -> IO Counts+wc :: Path -> IO Counts wc file = File.read file -- Stream IO Word8 & Stream.decodeLatin1 -- Stream IO Char@@ -53,5 +55,6 @@ main :: IO () main = do name <- fmap head getArgs- Counts l w c _ <- wc name+ nameP <- Path.fromString name+ Counts l w c _ <- wc nameP putStrLn $ show l ++ " " ++ show w ++ " " ++ show c ++ " " ++ name
examples/WordCountModular.hs view
@@ -9,10 +9,12 @@ import Data.Word (Word8) import Streamly.Data.Fold (Fold, Tee(..)) import System.Environment (getArgs)+import Streamly.FileSystem.Path (Path) import qualified Streamly.Data.Fold as Fold import qualified Streamly.Data.Stream as Stream-import qualified Streamly.FileSystem.File as File+import qualified Streamly.FileSystem.FileIO as File+import qualified Streamly.FileSystem.Path as Path {-# INLINE isSpace #-} isSpace :: Char -> Bool@@ -24,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 :: Fold IO Word8 a -> Path -> IO a foldWith f file = File.read file -- Stream IO Word8 & Stream.fold f -- IO a@@ -69,8 +71,9 @@ main :: IO () main = do name <- fmap head getArgs+ nameP <- Path.fromString name -- foldWith Fold.length name >>= print -- count bytes only -- foldWith nlines name >>= print -- count lines only -- foldWith nwords name >>= print -- count words only- (c, l, w) <- foldWith countAll name -- count all at once+ (c, l, w) <- foldWith countAll nameP -- count all at once putStrLn $ show l ++ " " ++ show w ++ " " ++ show c ++ " " ++ name
examples/WordCountParallel.hs view
@@ -9,11 +9,13 @@ import System.Environment (getArgs) import Streamly.Data.Array (Array) import WordCount (count, Counts(..), isSpace)+import Streamly.FileSystem.Path (Path) 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.FileSystem.Path as Path+import qualified Streamly.FileSystem.FileIO as File (readChunks) import qualified Streamly.Unicode.Stream as Stream -- Get the line, word, char counts in one chunk.@@ -48,7 +50,7 @@ -- Now put it all together, we only need to divide the stream into arrays, -- apply our counting function to each array and then combine all the counts.-wc :: String -> IO (Bool, Counts)+wc :: Path -> IO (Bool, Counts) wc file = do File.readChunks file -- Stream IO (Array Word8) & Stream.parMapM@@ -69,5 +71,6 @@ main :: IO () main = do name <- fmap head getArgs- (_, Counts l w c _) <- wc name+ nameP <- Path.fromString name+ (_, Counts l w c _) <- wc nameP putStrLn $ show l ++ " " ++ show w ++ " " ++ show c ++ " " ++ name
examples/WordCountParallelUTF8.hs view
@@ -180,19 +180,19 @@ ------------------------------------------------------------------------------- readField :: MutArray Int -> Field -> IO Int-readField v fld = MArray.getIndexUnsafe (fromEnum fld) v+readField v fld = MArray.unsafeGetIndex (fromEnum fld) v writeField :: MutArray Int -> Field -> Int -> IO ()-writeField v fld i = void $ MArray.putIndexUnsafe i v (fromEnum fld)+writeField v fld i = void $ MArray.unsafePutIndex i v (fromEnum fld) modifyField :: MutArray Int -> Field -> (Int -> Int) -> IO () modifyField v fld f = do let index = fromEnum fld- MArray.modifyIndexUnsafe index v (\x -> (f x, ()))+ MArray.unsafeModifyIndex index v (\x -> (f x, ())) newCounts :: IO (MutArray Int) newCounts = do- counts <- MArray.pinnedNew (fromEnum (maxBound :: Field) + 1)+ counts <- MArray.emptyOf' (fromEnum (maxBound :: Field) + 1) writeField counts LineCount 0 writeField counts WordCount 0 writeField counts CharCount 0
examples/WordFrequency.hs view
@@ -17,7 +17,8 @@ import qualified Streamly.Data.Array as Array import qualified Streamly.Data.Fold.Prelude as Fold import qualified Streamly.Data.Stream as Stream-import qualified Streamly.FileSystem.File as File+import qualified Streamly.FileSystem.Path as Path+import qualified Streamly.FileSystem.FileIO as File import qualified Streamly.Unicode.Stream as Unicode hashArray :: (Unbox a, Integral b, Num c) =>@@ -56,12 +57,13 @@ main :: IO () main = do inFile <- fmap head getArgs+ inFileP <- Path.fromString inFile let counter = Fold.foldl' (\n _ -> n + 1) (0 :: Int) classifier = Fold.toHashMapIO id counter -- Write the stream to a hashmap consisting of word counts mp <-- File.read inFile -- Stream IO Word8+ File.read inFileP -- Stream IO Word8 & Unicode.decodeLatin1 -- Stream IO Char & fmap toLower -- Stream IO Char & Stream.wordsBy isSpace Fold.toList -- Stream IO String
streamly-examples.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: streamly-examples-version: 0.2.0+version: 0.3.0 license: Apache-2.0 license-file: LICENSE author: Composewell Technologies@@ -25,10 +25,14 @@ tested-with: GHC==9.4.4, GHC==9.2.7, GHC==9.0.1, GHC==8.10.7 build-type: Simple extra-source-files:+ Cargo.toml Changelog.md Makefile NOTICE README.md+ examples/ListDirBasic.c+ examples/ListDirBasic.rs+ examples/WalkDirBasic.rs examples/WordCount.c source-repository head@@ -60,9 +64,11 @@ common exe-dependencies build-depends:- streamly == 0.10.0- , streamly-core == 0.2.0- , base >= 4.9 && < 4.20+ streamly == 0.11.0+ -- , streamly-fsevents == 0.1.0+ , streamly-core == 0.3.0+ , streamly-fsevents == 0.1.0+ , base >= 4.9 && < 4.22 , directory >= 1.2 && < 1.4 , transformers >= 0.4 && < 0.7 , containers >= 0.5 && < 0.8@@ -70,10 +76,12 @@ , exceptions >= 0.8 && < 0.11 , transformers-base >= 0.4 && < 0.5 , network >= 2.6 && < 4- , hashable >= 1.2 && < 1.5+ , hashable >= 1.2 && < 1.6 , unordered-containers >= 0.2 && < 0.3 , vector >= 0.12 && < 0.14 , mtl >= 2.2 && < 3+ -- For the ListDirOsPath example+ , filepath >= 1.4.2 && < 1.6 common exe-options import: exe-dependencies@@ -197,6 +205,23 @@ import: exe-options-threaded main-is: ListDir.hs +executable ListDirNaive+ import: exe-options+ main-is: ListDirNaive.hs++executable ListDirOsPath+ import: exe-options+ main-is: ListDirOsPath.hs+ if impl(ghc < 9.6)+ buildable: False++executable ListDirBasic+ import: exe-options+ main-is: ListDirBasic.hs+ other-modules: BasicDirStream+ if os(windows)+ buildable: False+ executable MergeSort import: exe-options-threaded main-is: MergeSort.hs@@ -238,6 +263,14 @@ else buildable: False +executable EchoClient+ import: exe-options-threaded+ main-is: EchoClient.hs+ if !impl(ghcjs)+ buildable: True+ else+ buildable: False+ executable FileSender import: exe-options-threaded main-is: FileSender.hs@@ -333,7 +366,7 @@ main-is: DateTimeParser.hs if !impl(ghcjs) buildable: True- build-depends: tasty-bench >= 0.3 && < 0.4+ build-depends: tasty-bench >= 0.3 && < 0.5 else buildable: False @@ -342,6 +375,6 @@ main-is: LogParser.hs if !impl(ghcjs) buildable: True- build-depends: tasty-bench >= 0.3 && < 0.4+ build-depends: tasty-bench >= 0.3 && < 0.5 else buildable: False