Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1b405e1
Add ifThenElse combinator for combining streams
harendra-kumar Jul 2, 2026
517620f
Add overflow tests for enumeration unfolds/streams
harendra-kumar Jul 1, 2026
2b3a810
Add a test for a corner case of enumerateFromThenTo
harendra-kumar Jul 2, 2026
596e842
Fix enumerateFromThenToIntegral
harendra-kumar Jul 1, 2026
fce2dcd
Fix overflow in enumerateFromToIntegral
harendra-kumar Jul 2, 2026
88487e0
Move enumerateFromStepNum to Producer module
harendra-kumar Jul 2, 2026
4389524
Move enumerateFromStepIntegral to Producer module
harendra-kumar Jul 2, 2026
9625aea
Move enumerateFromThenToIntegral to the Producer module
harendra-kumar Jul 2, 2026
2614443
Move takeEndByM to the Producer module
harendra-kumar Jul 2, 2026
cc34867
Fix overflow in enumerateFromToIntegral Unfold
harendra-kumar Jul 2, 2026
89cf72a
Fix overflow in enumerateFromThenIntegralBounded
harendra-kumar Jul 2, 2026
1787d0e
Split out Stream enumeration ops in a separate module
harendra-kumar Jul 2, 2026
3a2ae66
Synchronize Stream and Unfold Enumeration modules
harendra-kumar Jul 2, 2026
e1e242e
Fix enumerateFromTo Unfold
harendra-kumar Jul 2, 2026
ca70b33
Make bounded as default in Stream Enumeration
harendra-kumar Jul 2, 2026
a25d16d
Make bounded versions default in Unfold enumeration
harendra-kumar Jul 3, 2026
91eb842
Add a simplified upward enumeration operation
harendra-kumar Jul 4, 2026
662d6c0
Update single yield point guidelines
harendra-kumar Jul 4, 2026
f55495d
Add Fuse annotation on EnumState
harendra-kumar Jul 7, 2026
4fb1a89
Make enumerateFromThenTo fusible
harendra-kumar Jul 4, 2026
788144f
Use Num APIs in general and specialize RealFloat, Bounded
harendra-kumar Jul 6, 2026
ff54220
Do not use Producer for Stream generation
harendra-kumar Jul 8, 2026
6928d2e
Fix deprecation warning in Unfold benchmarks
harendra-kumar Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion benchmark/Streamly/Benchmark/Data/Stream/Generate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module Stream.Generate (benchmarks) where
import GHC.Types (SPEC(..))
import Test.Inspection
import qualified Streamly.Internal.Data.Fold as Fold
import qualified Streamly.Internal.Data.Producer as Producer
#endif

import Control.Monad.IO.Class (MonadIO)
Expand Down Expand Up @@ -85,7 +86,7 @@ sourceIntFromThenTo value = withDrain $ \n ->
#ifdef INSPECTION
inspect $ hasNoTypeClasses 'sourceIntFromThenTo
inspect $ 'sourceIntFromThenTo `hasNoType` ''Stream.Step
inspect $ 'sourceIntFromThenTo `hasNoType` ''Stream.EnumState
inspect $ 'sourceIntFromThenTo `hasNoType` ''Producer.EnumState
inspect $ 'sourceIntFromThenTo `hasNoType` ''Fold.Step
inspect $ 'sourceIntFromThenTo `hasNoType` ''SPEC
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ infiniteIntsUnfold :: Monad m => Int -> Int -> Unfold m ((), ()) Int
infiniteIntsUnfold _ _ =
Unfold.interleave
(Unfold.supply (0 :: Int) Unfold.enumerateFrom)
(Unfold.supply (-1, -2) Unfold.enumerateFromThen)
(Unfold.supply (-1) Unfold.enumerateDownFromNum)

{-# INLINE unfoldCrossEqn #-}
unfoldCrossEqn :: Monad m => Int -> Unfold m ((), ()) Int -> m ()
Expand Down
6 changes: 3 additions & 3 deletions benchmark/Streamly/Benchmark/Data/Stream/Type/Logic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,21 @@ boundedInts :: Monad m => Int -> Int -> Stream m Int
boundedInts n _ =
Stream.interleave
(Stream.enumerateFromTo (0 :: Int) n)
(Stream.enumerateFromThenTo (-1) (-2) (-n))
(Stream.enumerateDownFromToNum (-1) (-n))

{-# INLINE infiniteInts #-}
infiniteInts :: Monad m => Int -> Int -> Stream m Int
infiniteInts _ _ =
Stream.interleave
(Stream.enumerateFrom (0 :: Int))
(Stream.enumerateFromThen (-1) (-2))
(Stream.enumerateDownFromNum (-1))

{-# INLINE boundedIntsUnfold #-}
boundedIntsUnfold :: Monad m => Int -> Int -> Unfold m ((), ()) Int
boundedIntsUnfold n _ =
Unfold.interleave
(Unfold.supply (0 :: Int, n) Unfold.enumerateFromTo)
(Unfold.supply (-1, -2, -n) Unfold.enumerateFromThenTo)
(Unfold.supply (-1, -n) Unfold.enumerateDownFromToNum)

{-# INLINE checkStream #-}
checkStream :: Applicative m =>
Expand Down
33 changes: 33 additions & 0 deletions benchmark/Streamly/Benchmark/Data/Stream/Type/MultiStream.hs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,26 @@ inspect $ 'serial4 `hasNoType` ''S.Step
inspect $ 'serial4 `hasNoType` ''Fold.Step
#endif

-------------------------------------------------------------------------------
-- Branching
-------------------------------------------------------------------------------

ifThenElse2 :: Int -> IO ()
ifThenElse2 count = withRandomIntIO $ \n ->
drain $
S.ifThenElse
(return True)
(sourceUnfoldrM count n)
(sourceUnfoldrM count (n + 1))

#ifdef INSPECTION
inspect $ hasNoTypeClasses 'ifThenElse2
inspect $ 'ifThenElse2 `hasNoType` ''SPEC
inspect $ 'ifThenElse2 `hasNoType` ''S.IfThenElseState
inspect $ 'ifThenElse2 `hasNoType` ''S.Step
inspect $ 'ifThenElse2 `hasNoType` ''Fold.Step
#endif

-------------------------------------------------------------------------------
-- Zipping
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -175,6 +195,17 @@ concatMapM outer inner = withRandomIntIO $ \n ->
(return . sourceUnfoldrM inner)
(sourceUnfoldrM outer n)

concatEffect :: Int -> IO ()
concatEffect count = withRandomIntIO $ \n ->
drain $ S.concatEffect $ return $ sourceUnfoldrM count n

#ifdef INSPECTION
inspect $ hasNoTypeClasses 'concatEffect
inspect $ 'concatEffect `hasNoType` ''SPEC
-- inspect $ 'concatEffect `hasNoType` ''S.Step
inspect $ 'concatEffect `hasNoType` ''Fold.Step
#endif

-- concatMap Streams

concatMapSingletonStreams :: Int -> IO ()
Expand Down Expand Up @@ -356,8 +387,10 @@ benchmarks size =
-- Multi-stream (concatMap/foldMany)
[ (SpaceO_1, benchIO "serial" $ serial2 (size `div` 2))
, (SpaceO_1, benchIO "serial (2,2,x/4)" $ serial4 (size `div` 4))
, (SpaceO_1, benchIO "ifThenElse" $ ifThenElse2 (size `div` 2))
, (SpaceO_1, benchIO "zipWith" $ zipWith size)
, (SpaceO_1, benchIO "zipWithM" $ zipWithM size)
, (SpaceO_1, benchIO "concatEffect" $ concatEffect size)
, (SpaceO_1, benchIO "concatMap" $ concatMap 2 (size `div` 2))
, (SpaceO_1, benchIO "concatMap unfoldr outer=Max inner=1" $
concatMapPure size 1)
Expand Down
12 changes: 6 additions & 6 deletions benchmark/Streamly/Benchmark/Data/Unfold.hs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ benchIO name f = bench name $ nfIO $ randomRIO (1,1) >>= f
-- generate numbers up to the argument value
{-# INLINE source #-}
source :: Monad m => Int -> Unfold m Int Int
source n = UF.supplySecond n UF.enumerateFromToIntegral
source n = UF.supplySecond n UF.enumerateFromToNum

-------------------------------------------------------------------------------
-- Benchmark helpers
Expand All @@ -87,7 +87,7 @@ drainTransformation unf f seed = drainGeneration (f unf) seed
drainTransformationDefault ::
Monad m => Int -> (Unfold m Int Int -> Unfold m c d) -> c -> m ()
drainTransformationDefault to =
drainTransformation (UF.supplySecond to UF.enumerateFromToIntegral)
drainTransformation (UF.supplySecond to UF.enumerateFromToNum)

-------------------------------------------------------------------------------
-- Operations on input
Expand Down Expand Up @@ -147,7 +147,7 @@ inspect $ 'fromStreamK `hasNoType` ''SPEC
{-# INLINE fromStreamD #-}
fromStreamD :: Int -> Int -> IO ()
fromStreamD size start =
drainGeneration UF.fromStreamD (S.replicate size start)
drainGeneration UF.fromStream (S.replicate size start)

#ifdef INSPECTION
-- inspect $ 'fromStreamD `hasNoType` ''S.Step
Expand Down Expand Up @@ -550,7 +550,7 @@ afterIO :: Int -> Int -> IO ()
afterIO size start =
UF.fold FL.drain
(UF.afterIO (\_ -> return ())
(UF.supplySecond (size + start) UF.enumerateFromToIntegral))
(UF.supplySecond (size + start) UF.enumerateFromToNum))
start

#ifdef INSPECTION
Expand All @@ -564,7 +564,7 @@ finallyIO :: Int -> Int -> IO ()
finallyIO size start =
UF.fold FL.drain
(UF.finallyIO (\_ -> return ())
(UF.supplySecond (size + start) UF.enumerateFromToIntegral))
(UF.supplySecond (size + start) UF.enumerateFromToNum))
start

-- 'finallyIO' and 'bracketIO' wrap the step function in exception handlers,
Expand All @@ -580,7 +580,7 @@ bracketIO :: Int -> Int -> IO ()
bracketIO size start =
UF.fold FL.drain
(UF.bracketIO return (\_ -> return ())
(UF.supplySecond (size + start) UF.enumerateFromToIntegral))
(UF.supplySecond (size + start) UF.enumerateFromToNum))
start

#ifdef INSPECTION
Expand Down
8 changes: 4 additions & 4 deletions benchmark/Streamly/Benchmark/Data/Unfold/Enumeration.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ drainGeneration = UF.fold FL.drain
{-# INLINE enumerateFromThenIntegral #-}
enumerateFromThenIntegral :: Int -> Int -> IO ()
enumerateFromThenIntegral size start =
drainGeneration (UF.take size UF.enumerateFromThenIntegral) (start, 1)
drainGeneration (UF.take size UF.enumerateFromThenNum) (start, 1)

#ifdef INSPECTION
inspect $ 'enumerateFromThenIntegral `hasNoType` ''S.Step
Expand All @@ -71,7 +71,7 @@ enumerateFromToIntegral size start =
drainGeneration
( UF.supplySecond
(size + start)
UF.enumerateFromToIntegral
UF.enumerateFromToNum
) start

#ifdef INSPECTION
Expand All @@ -83,7 +83,7 @@ inspect $ 'enumerateFromToIntegral `hasNoType` ''SPEC
{-# INLINE enumerateFromIntegral #-}
enumerateFromIntegral :: Int -> Int -> IO ()
enumerateFromIntegral size =
drainGeneration (UF.take size UF.enumerateFromIntegral)
drainGeneration (UF.take size UF.enumerateFromNum)

#ifdef INSPECTION
inspect $ 'enumerateFromIntegral `hasNoType` ''S.Step
Expand Down Expand Up @@ -119,7 +119,7 @@ enumerateFromToFractional size start =
in drainGeneration
( UF.supplySecond
(intToDouble $ start + size)
UF.enumerateFromToFractional
UF.enumerateFromToRealFloat
)
(intToDouble start)

Expand Down
20 changes: 10 additions & 10 deletions benchmark/Streamly/Benchmark/Data/Unfold/Type.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ benchIO name f = bench name $ nfIO $ randomRIO (1,1) >>= f
-- generate numbers up to the argument value
{-# INLINE source #-}
source :: Monad m => Int -> Unfold m Int Int
source n = UF.supplySecond n UF.enumerateFromToIntegral
source n = UF.supplySecond n UF.enumerateFromToNum

-------------------------------------------------------------------------------
-- Benchmark helpers
Expand All @@ -75,7 +75,7 @@ drainTransformation unf f = drainGeneration (f unf)
drainTransformationDefault ::
Monad m => Int -> (Unfold m Int Int -> Unfold m c d) -> c -> m ()
drainTransformationDefault to =
drainTransformation (UF.supplySecond to UF.enumerateFromToIntegral)
drainTransformation (UF.supplySecond to UF.enumerateFromToNum)

{-# INLINE drainProduct #-}
drainProduct ::
Expand All @@ -98,7 +98,7 @@ drainProductDefault to = drainProduct src src

where

src = UF.supplySecond to UF.enumerateFromToIntegral
src = UF.supplySecond to UF.enumerateFromToNum

-------------------------------------------------------------------------------
-- Operations on input
Expand Down Expand Up @@ -141,7 +141,7 @@ inspect $ 'both `hasNoType` ''SPEC
first :: Int -> Int -> IO ()
first size start =
drainTransformation
(UF.take size UF.enumerateFromThenIntegral)
(UF.take size UF.enumerateFromThenNum)
(UF.supplyFirst start)
1

Expand All @@ -155,7 +155,7 @@ inspect $ 'first `hasNoType` ''SPEC
second :: Int -> Int -> IO ()
second size =
drainTransformation
(UF.take size UF.enumerateFromThenIntegral)
(UF.take size UF.enumerateFromThenNum)
(UF.supplySecond 1)

#ifdef INSPECTION
Expand Down Expand Up @@ -192,7 +192,7 @@ inspect $ 'consInputWith `hasNoType` ''UF.ConsInputState
swap :: Int -> Int -> IO ()
swap size start =
drainTransformation
(UF.take size UF.enumerateFromThenIntegral)
(UF.take size UF.enumerateFromThenNum)
(UF.lmap Tuple.swap)
(1, start)

Expand Down Expand Up @@ -657,8 +657,8 @@ concatMapM inner outer start =

where

unfoldInGen i = return (UF.supplySecond (i + inner) UF.enumerateFromToIntegral)
unfoldOut = UF.supplySecond (start + outer) UF.enumerateFromToIntegral
unfoldInGen i = return (UF.supplySecond (i + inner) UF.enumerateFromToNum)
unfoldOut = UF.supplySecond (start + outer) UF.enumerateFromToNum

-- The 'bind'-based benchmarks use the Unfold monad ('UF.bind'), which is a
-- concatMap and does not fuse, so the 'Step' constructors remain.
Expand Down Expand Up @@ -920,8 +920,8 @@ concatMapPure inner outer start =

where

unfoldInGen i = UF.supplySecond (i + inner) UF.enumerateFromToIntegral
unfoldOut = UF.supplySecond (start + outer) UF.enumerateFromToIntegral
unfoldInGen i = UF.supplySecond (i + inner) UF.enumerateFromToNum
unfoldOut = UF.supplySecond (start + outer) UF.enumerateFromToNum

#ifdef INSPECTION
-- inspect $ 'concatMapPure `hasNoType` ''S.Step
Expand Down
6 changes: 6 additions & 0 deletions core/docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

* Fixed `Stream.postscanl` to omit the output of a scan that terminates without
consuming any input (e.g. `Scanl.take 0`).
* Fix overflow in enumerateFrom and enumerateFromTo variants in Stream and
Unfold modules.
* Fix `Unfold.enumerateFromTo` for the `Identity` type, it was incorrectly
calling `enumerateFromThen` instead of `enumerateFromTo`.

### Breaking changes

Expand Down Expand Up @@ -38,6 +42,8 @@
filtering in scans.
* Removed deprecated module `Streamly.Internal.Data.Stream.StreamD`.
Use `Streamly.Internal.Data.Stream` instead.
* enumerateFrom*Num are not numerically stable any more, use
enumerateFrom*RealFloat for that.

## 0.3.1 (May 2026)

Expand Down
5 changes: 3 additions & 2 deletions core/src/Streamly/Internal/Data/Array/Generic/Type.hs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import qualified Streamly.Internal.Data.Parser.Type as ParserD
import qualified Streamly.Internal.Data.ParserK.Type as ParserK
import qualified Streamly.Internal.Data.RingArray.Generic as RB
import qualified Streamly.Internal.Data.Stream.Type as D
import qualified Streamly.Internal.Data.Stream.Enumeration as D
import qualified Streamly.Internal.Data.Stream.Generate as D
import qualified Streamly.Internal.Data.Unfold.Type as Unfold
import qualified Text.ParserCombinators.ReadPrec as ReadPrec
Expand Down Expand Up @@ -226,13 +227,13 @@ toList arr = loop 0
{-# INLINE_NORMAL read #-}
read :: Monad m => Array a -> Stream m a
read arr =
D.map (`unsafeGetIndex` arr) $ D.enumerateFromToIntegral 0 (length arr - 1)
D.map (`unsafeGetIndex` arr) $ D.enumerateFromToNum 0 (length arr - 1)

{-# INLINE_NORMAL readRev #-}
readRev :: Monad m => Array a -> Stream m a
readRev arr =
D.map (`unsafeGetIndex` arr)
$ D.enumerateFromThenToIntegral (arrLen - 1) (arrLen - 2) 0
$ D.enumerateFromThenToNum (arrLen - 1) (arrLen - 2) 0
where
arrLen = length arr

Expand Down
6 changes: 3 additions & 3 deletions core/src/Streamly/Internal/Data/MutArray/Generic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ import Streamly.Internal.Data.SVar.Type (adaptState)

import qualified Streamly.Internal.Data.Fold.Type as FL
import qualified Streamly.Internal.Data.Stream.Type as D
import qualified Streamly.Internal.Data.Stream.Generate as D
import qualified Streamly.Internal.Data.Stream.Enumeration as D
import qualified Streamly.Internal.Data.Stream.Lift as D
import qualified Streamly.Internal.Data.StreamK.Type as K

Expand Down Expand Up @@ -586,7 +586,7 @@ toList arr = mapM (`unsafeGetIndex` arr) [0 .. (length arr - 1)]
{-# INLINE_NORMAL read #-}
read :: MonadIO m => MutArray a -> D.Stream m a
read arr =
D.mapM (`unsafeGetIndex` arr) $ D.enumerateFromToIntegral 0 (length arr - 1)
D.mapM (`unsafeGetIndex` arr) $ D.enumerateFromToNum 0 (length arr - 1)

-- Check equivalence with StreamK.fromStream . toStreamD and remove
{-# INLINE toStreamK #-}
Expand All @@ -606,7 +606,7 @@ toStreamK arr = K.unfoldrM step 0
readRev :: MonadIO m => MutArray a -> D.Stream m a
readRev arr =
D.mapM (`unsafeGetIndex` arr)
$ D.enumerateFromThenToIntegral (arrLen - 1) (arrLen - 2) 0
$ D.enumerateFromThenToNum (arrLen - 1) (arrLen - 2) 0
where
arrLen = length arr

Expand Down
4 changes: 2 additions & 2 deletions core/src/Streamly/Internal/Data/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ import Streamly.Internal.Data.Stream.Type (Stream)
import qualified Data.Foldable as Foldable
import qualified Streamly.Internal.Data.Fold.Type as FL
import qualified Streamly.Internal.Data.Stream.Type as D
import qualified Streamly.Internal.Data.Stream.Generate as D
import qualified Streamly.Internal.Data.Stream.Enumeration as D

import Streamly.Internal.Data.Parser.Type
--import Streamly.Internal.Data.Parser.Tee -- It's empty
Expand Down Expand Up @@ -2351,7 +2351,7 @@ zip = zipWithM (curry return)
-- /Pre-release/
{-# INLINE indexed #-}
indexed :: forall m a b. Monad m => Fold m (Int, a) b -> Parser a m b
indexed = zip (D.enumerateFromIntegral 0 :: D.Stream m Int)
indexed = zip (D.enumerateFromBoundedNum 0 :: D.Stream m Int)

-- | @makeIndexFilter indexer filter predicate@ generates a fold filtering
-- function using a fold indexing function that attaches an index to each input
Expand Down
Loading
Loading