@@ -39,6 +39,9 @@ module Streamly.Internal.Data.Stream.StreamD.Transform
3939 , scan
4040 , scanMany
4141
42+ -- * Splitting
43+ , splitOn
44+
4245 -- * Scanning
4346 -- | Left scans. Stateful, mostly one-to-one maps.
4447 , scanlM'
@@ -1842,3 +1845,61 @@ catRights = fmap (fromRight undefined) . filter isRight
18421845{-# INLINE catEithers #-}
18431846catEithers :: Monad m => Stream m (Either a a) -> Stream m a
18441847catEithers = fmap (either id id)
1848+
1849+------------------------------------------------------------------------------
1850+-- Splitting
1851+------------------------------------------------------------------------------
1852+
1853+-- | Split on an infixed separator element, dropping the separator. The
1854+-- supplied 'Fold' is applied on the split segments. Splits the stream on
1855+-- separator elements determined by the supplied predicate, separator is
1856+-- considered as infixed between two segments:
1857+--
1858+-- >>> splitOn' p xs = Stream.fold Fold.toList $ Stream.splitOn p Fold.toList (Stream.fromList xs)
1859+-- >>> splitOn' (== '.') "a.b"
1860+-- ["a","b"]
1861+--
1862+-- An empty stream is folded to the default value of the fold:
1863+--
1864+-- >>> splitOn' (== '.') ""
1865+-- [""]
1866+--
1867+-- If one or both sides of the separator are missing then the empty segment on
1868+-- that side is folded to the default output of the fold:
1869+--
1870+-- >>> splitOn' (== '.') "."
1871+-- ["",""]
1872+--
1873+-- >>> splitOn' (== '.') ".a"
1874+-- ["","a"]
1875+--
1876+-- >>> splitOn' (== '.') "a."
1877+-- ["a",""]
1878+--
1879+-- >>> splitOn' (== '.') "a..b"
1880+-- ["a","","b"]
1881+--
1882+-- splitOn is an inverse of intercalating single element:
1883+--
1884+-- > Stream.intercalate (Stream.fromPure '.') Unfold.fromList . Stream.splitOn (== '.') Fold.toList === id
1885+--
1886+-- Assuming the input stream does not contain the separator:
1887+--
1888+-- > Stream.splitOn (== '.') Fold.toList . Stream.intercalate (Stream.fromPure '.') Unfold.fromList === id
1889+--
1890+{-# INLINE splitOn #-}
1891+splitOn :: Monad m => (a -> Bool) -> Fold m a b -> Stream m a -> Stream m b
1892+splitOn predicate f =
1893+ -- We can express the infix splitting in terms of optional suffix split
1894+ -- fold. After applying a suffix split fold repeatedly if the last segment
1895+ -- ends with a suffix then we need to return the default output of the fold
1896+ -- after that to make it an infix split.
1897+ --
1898+ -- Alternately, we can also express it using an optional prefix split fold.
1899+ -- If the first segment starts with a prefix then we need to emit the
1900+ -- default output of the fold before that to make it an infix split, and
1901+ -- then apply prefix split fold repeatedly.
1902+ --
1903+ -- Since a suffix split fold can be easily expressed using a
1904+ -- non-backtracking fold, we use that.
1905+ foldManyPost (FL.takeEndBy_ predicate f)