diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/Cargo.toml
@@ -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"
diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -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
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -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)
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -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
 
diff --git a/examples/BasicDirStream.hsc b/examples/BasicDirStream.hsc
new file mode 100644
--- /dev/null
+++ b/examples/BasicDirStream.hsc
@@ -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"
diff --git a/examples/CSVParser.hs b/examples/CSVParser.hs
--- a/examples/CSVParser.hs
+++ b/examples/CSVParser.hs
@@ -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 ""
diff --git a/examples/CamelCase.hs b/examples/CamelCase.hs
--- a/examples/CamelCase.hs
+++ b/examples/CamelCase.hs
@@ -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
diff --git a/examples/CmdClient.hs b/examples/CmdClient.hs
--- a/examples/CmdClient.hs
+++ b/examples/CmdClient.hs
@@ -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)
diff --git a/examples/CmdServer.hs b/examples/CmdServer.hs
--- a/examples/CmdServer.hs
+++ b/examples/CmdServer.hs
@@ -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)
diff --git a/examples/ControlFlow.hs b/examples/ControlFlow.hs
--- a/examples/ControlFlow.hs
+++ b/examples/ControlFlow.hs
@@ -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
diff --git a/examples/CoreUtils.hs b/examples/CoreUtils.hs
--- a/examples/CoreUtils.hs
+++ b/examples/CoreUtils.hs
@@ -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 ()
diff --git a/examples/CoreUtilsHandle.hs b/examples/CoreUtilsHandle.hs
--- a/examples/CoreUtilsHandle.hs
+++ b/examples/CoreUtilsHandle.hs
@@ -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
diff --git a/examples/DateTimeParser.hs b/examples/DateTimeParser.hs
--- a/examples/DateTimeParser.hs
+++ b/examples/DateTimeParser.hs
@@ -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 '-'
diff --git a/examples/EchoClient.hs b/examples/EchoClient.hs
new file mode 100644
--- /dev/null
+++ b/examples/EchoClient.hs
@@ -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
diff --git a/examples/FileSystemEvent.hs b/examples/FileSystemEvent.hs
--- a/examples/FileSystemEvent.hs
+++ b/examples/FileSystemEvent.hs
@@ -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))
diff --git a/examples/Intro.hs b/examples/Intro.hs
--- a/examples/Intro.hs
+++ b/examples/Intro.hs
@@ -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
diff --git a/examples/ListDir.hs b/examples/ListDir.hs
--- a/examples/ListDir.hs
+++ b/examples/ListDir.hs
@@ -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
diff --git a/examples/ListDirBasic.c b/examples/ListDirBasic.c
new file mode 100644
--- /dev/null
+++ b/examples/ListDirBasic.c
@@ -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;
+}
diff --git a/examples/ListDirBasic.hs b/examples/ListDirBasic.hs
new file mode 100644
--- /dev/null
+++ b/examples/ListDirBasic.hs
@@ -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)
diff --git a/examples/ListDirBasic.rs b/examples/ListDirBasic.rs
new file mode 100644
--- /dev/null
+++ b/examples/ListDirBasic.rs
@@ -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);
+}
diff --git a/examples/ListDirNaive.hs b/examples/ListDirNaive.hs
new file mode 100644
--- /dev/null
+++ b/examples/ListDirNaive.hs
@@ -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 "."
diff --git a/examples/ListDirOsPath.hs b/examples/ListDirOsPath.hs
new file mode 100644
--- /dev/null
+++ b/examples/ListDirOsPath.hs
@@ -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|.|]
diff --git a/examples/MergeServer.hs b/examples/MergeServer.hs
--- a/examples/MergeServer.hs
+++ b/examples/MergeServer.hs
@@ -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 ()
 
diff --git a/examples/MergeSort.hs b/examples/MergeSort.hs
--- a/examples/MergeSort.hs
+++ b/examples/MergeSort.hs
@@ -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.
diff --git a/examples/Rate.hs b/examples/Rate.hs
--- a/examples/Rate.hs
+++ b/examples/Rate.hs
@@ -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)
diff --git a/examples/WalkDirBasic.rs b/examples/WalkDirBasic.rs
new file mode 100644
--- /dev/null
+++ b/examples/WalkDirBasic.rs
@@ -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());
+    }
+}
diff --git a/examples/WordCount.hs b/examples/WordCount.hs
--- a/examples/WordCount.hs
+++ b/examples/WordCount.hs
@@ -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
diff --git a/examples/WordCountModular.hs b/examples/WordCountModular.hs
--- a/examples/WordCountModular.hs
+++ b/examples/WordCountModular.hs
@@ -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
diff --git a/examples/WordCountParallel.hs b/examples/WordCountParallel.hs
--- a/examples/WordCountParallel.hs
+++ b/examples/WordCountParallel.hs
@@ -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
diff --git a/examples/WordCountParallelUTF8.hs b/examples/WordCountParallelUTF8.hs
--- a/examples/WordCountParallelUTF8.hs
+++ b/examples/WordCountParallelUTF8.hs
@@ -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
diff --git a/examples/WordFrequency.hs b/examples/WordFrequency.hs
--- a/examples/WordFrequency.hs
+++ b/examples/WordFrequency.hs
@@ -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
diff --git a/streamly-examples.cabal b/streamly-examples.cabal
--- a/streamly-examples.cabal
+++ b/streamly-examples.cabal
@@ -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
