From 1b405e1f20b43e8fe1af914dbe639d98f3ee3532 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 07:12:09 +0530 Subject: [PATCH 01/23] Add ifThenElse combinator for combining streams --- .../Benchmark/Data/Stream/Type/MultiStream.hs | 33 +++++++++++ .../src/Streamly/Internal/Data/Stream/Type.hs | 57 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/benchmark/Streamly/Benchmark/Data/Stream/Type/MultiStream.hs b/benchmark/Streamly/Benchmark/Data/Stream/Type/MultiStream.hs index 3cee23c294..991d2927f9 100644 --- a/benchmark/Streamly/Benchmark/Data/Stream/Type/MultiStream.hs +++ b/benchmark/Streamly/Benchmark/Data/Stream/Type/MultiStream.hs @@ -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 ------------------------------------------------------------------------------- @@ -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 () @@ -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) diff --git a/core/src/Streamly/Internal/Data/Stream/Type.hs b/core/src/Streamly/Internal/Data/Stream/Type.hs index 9788136e9d..ede7743401 100644 --- a/core/src/Streamly/Internal/Data/Stream/Type.hs +++ b/core/src/Streamly/Internal/Data/Stream/Type.hs @@ -99,6 +99,8 @@ module Streamly.Internal.Data.Stream.Type -- is equivalent to @concatMap id@. Append is equivalent to @mergeBy fst@. , AppendState(..) , append + , IfThenElseState (..) + , ifThenElse -- ** Zipping -- | Zip corresponding elements of two streams. @@ -1252,6 +1254,53 @@ append (Stream step1 state1) (Stream step2 state2) = Skip s -> Skip (AppendSecond s) Stop -> Stop +------------------------------------------------------------------------------ +-- Branching +------------------------------------------------------------------------------ + +{-# ANN type IfThenElseState Fuse #-} +data IfThenElseState s1 s2 = + IfThenElseInit + | IfThenElseThen s1 + | IfThenElseElse s2 + +-- | Run the predicate action, then run the "then" stream if it returns +-- 'True', otherwise run the "else" stream. +-- +-- >>> Stream.toList $ Stream.ifThenElse (pure True) (Stream.fromList [1,2 :: Int]) (Stream.fromList [3,4]) +-- [1,2] +-- >>> Stream.toList $ Stream.ifThenElse (pure False) (Stream.fromList [1,2 :: Int]) (Stream.fromList [3,4]) +-- [3,4] +-- +{-# INLINE_NORMAL ifThenElse #-} +ifThenElse :: Monad m => m Bool -> Stream m a -> Stream m a -> Stream m a +ifThenElse predicate (Stream step1 state1) (Stream step2 state2) = + Stream step IfThenElseInit + + where + + {-# INLINE_LATE step #-} + step _ IfThenElseInit = do + r <- predicate + return $ + if r + then Skip (IfThenElseThen state1) + else Skip (IfThenElseElse state2) + + step gst (IfThenElseThen st) = + (\case + Yield x s -> Yield x (IfThenElseThen s) + Skip s -> Skip (IfThenElseThen s) + Stop -> Stop + ) <$> step1 gst st + + step gst (IfThenElseElse st) = + (\case + Yield x s -> Yield x (IfThenElseElse s) + Skip s -> Skip (IfThenElseElse s) + Stop -> Stop + ) <$> step2 gst st + ------------------------------------------------------------------------------ -- Zipping ------------------------------------------------------------------------------ @@ -1828,6 +1877,14 @@ concat = concatMap id -- -- See also: 'concat', 'sequence' -- +-- Note: When the stream being generated is composed of statically known +-- streams, prefer composing them using a fused operation instead of +-- 'concatEffect', as fusion avoids the overhead of going through the +-- stream-of-streams machinery. For example, prefer +-- 'Streamly.Internal.Data.Stream.Nesting.ifThenElse' over using +-- 'concatEffect' to compose two statically known streams based on a monadic +-- predicate. +-- {-# INLINE concatEffect #-} concatEffect :: Monad m => m (Stream m a) -> Stream m a concatEffect generator = concatMapM (\() -> generator) (fromPure ()) From 517620fb537f2743da870b920d3f63e3d742a5be Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 1 Jul 2026 09:25:56 +0530 Subject: [PATCH 02/23] Add overflow tests for enumeration unfolds/streams --- test/Streamly/Test/Data/Stream/Generate.hs | 52 ++++++++++++++ test/Streamly/Test/Data/Unfold.hs | 79 ++++++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/test/Streamly/Test/Data/Stream/Generate.hs b/test/Streamly/Test/Data/Stream/Generate.hs index 427c8c5e19..a9044e04a9 100644 --- a/test/Streamly/Test/Data/Stream/Generate.hs +++ b/test/Streamly/Test/Data/Stream/Generate.hs @@ -10,6 +10,7 @@ module Streamly.Test.Data.Stream.Generate (main) where import Data.IORef (newIORef, readIORef, writeIORef) +import Data.Int (Int8) import Data.Word (Word8, Word16) import Foreign.Marshal.Alloc (alloca) import Foreign.Marshal.Array (withArray) @@ -234,6 +235,45 @@ testEnumerateFromToFractional = toList (Stream.enumerateFromToFractional (1.1 :: Double) 4.0) `shouldReturn` [1.1, 2.1, 3.1, 4.1] +------------------------------------------------------------------------------- +-- Overflow at the bound of a fixed-size Integral type +-- +-- All of these are guarded with 'Stream.take' so a regression +-- that reintroduces unbounded wraparound fails instead of hanging. +------------------------------------------------------------------------------- + +testEnumerateFromToIntegralOverflow :: Expectation +testEnumerateFromToIntegralOverflow = + toList (Stream.take 10 (Stream.enumerateFromToIntegral (253 :: Word8) 255)) + `shouldReturn` [253, 254, 255] + +testEnumerateFromIntegralOverflow :: Expectation +testEnumerateFromIntegralOverflow = + toList (Stream.take 10 (Stream.enumerateFromIntegral (253 :: Word8))) + `shouldReturn` [253, 254, 255] + +testEnumerateFromThenIntegralOverflowUp :: Expectation +testEnumerateFromThenIntegralOverflowUp = + toList (Stream.take 10 (Stream.enumerateFromThenIntegral (250 :: Word8) 252)) + `shouldReturn` [250, 252, 254] + +testEnumerateFromThenIntegralOverflowDn :: Expectation +testEnumerateFromThenIntegralOverflowDn = + toList (Stream.take 10 (Stream.enumerateFromThenIntegral (-124 :: Int8) (-126))) + `shouldReturn` [-124, -126, -128] + +testEnumerateFromThenToIntegralOverflowUp :: Expectation +testEnumerateFromThenToIntegralOverflowUp = + toList (Stream.take 10 (Stream.enumerateFromThenToIntegral (250 :: Word8) 252 255)) + `shouldReturn` [250, 252, 254] + +testEnumerateFromThenToIntegralOverflowDn :: Expectation +testEnumerateFromThenToIntegralOverflowDn = + toList + (Stream.take 10 + (Stream.enumerateFromThenToIntegral (-124 :: Int8) (-126) (-128))) + `shouldReturn` [-124, -126, -128] + ------------------------------------------------------------------------------- -- Time Enumeration (smoke tests - verify elements are produced) ------------------------------------------------------------------------------- @@ -322,6 +362,18 @@ main = hspec $ describe moduleName $ do it "enumerateFromFractional" testEnumerateFromFractional it "enumerateFromToFractional" testEnumerateFromToFractional + describe "Enumeration overflow at type bound" $ do + it "enumerateFromToIntegral overflow" testEnumerateFromToIntegralOverflow + it "enumerateFromIntegral overflow" testEnumerateFromIntegralOverflow + it "enumerateFromThenIntegral overflow up" + testEnumerateFromThenIntegralOverflowUp + it "enumerateFromThenIntegral overflow dn" + testEnumerateFromThenIntegralOverflowDn + it "enumerateFromThenToIntegral overflow up" + testEnumerateFromThenToIntegralOverflowUp + it "enumerateFromThenToIntegral overflow dn" + testEnumerateFromThenToIntegralOverflowDn + describe "Time Enumeration" $ do it "timesWith produces elements" testTimesWith it "times produces elements" testTimes diff --git a/test/Streamly/Test/Data/Unfold.hs b/test/Streamly/Test/Data/Unfold.hs index 0c4d171f60..0df1ed1ffc 100644 --- a/test/Streamly/Test/Data/Unfold.hs +++ b/test/Streamly/Test/Data/Unfold.hs @@ -27,7 +27,9 @@ import Control.Exception (Exception, SomeException, try) import Control.Monad.Catch (throwM) import Control.Monad.Trans.State.Strict import Data.Functor.Identity +import Data.Int (Int8) import Data.IORef (newIORef, readIORef, writeIORef) +import Data.Word (Word8) import Foreign.Marshal.Array (withArray) import Prelude hiding (const, take, drop, concat, mapM, either, filter, dropWhile, repeat, scanl) import Test.Hspec as H @@ -481,6 +483,69 @@ enumerateFromToFractional = let unf = UF.enumerateFromToFractional in testUnfold unf (f :: Double, t) [f..(t :: Double)] +------------------------------------------------------------------------------- +-- Overflow at the bound of a fixed-size Integral type +-- +-- All of these are guarded with 'UF.take' so a regression that +-- reintroduces unbounded wraparound fails instead of hanging. +------------------------------------------------------------------------------- + +enumerateFromToIntegralOverflow :: Expectation +enumerateFromToIntegralOverflow = + testUnfold + (UF.take 10 UF.enumerateFromToIntegral) + (253 :: Word8, 255) + [253, 254, 255] + `shouldBe` True + +enumerateFromThenToIntegralOverflowUp :: Expectation +enumerateFromThenToIntegralOverflowUp = + testUnfold + (UF.take 10 UF.enumerateFromThenToIntegral) + (250 :: Word8, 252, 255) + [250, 252, 254] + `shouldBe` True + +enumerateFromThenToIntegralOverflowDn :: Expectation +enumerateFromThenToIntegralOverflowDn = + testUnfold + (UF.take 10 UF.enumerateFromThenToIntegral) + (-124 :: Int8, -126, -128) + [-124, -126, -128] + `shouldBe` True + +enumerateFromIntegralBoundedOverflow :: Expectation +enumerateFromIntegralBoundedOverflow = + testUnfold + (UF.take 10 UF.enumerateFromIntegralBounded) + (253 :: Word8) + [253, 254, 255] + `shouldBe` True + +enumerateFromThenIntegralBoundedOverflow :: Expectation +enumerateFromThenIntegralBoundedOverflow = + testUnfold + (UF.take 10 UF.enumerateFromThenIntegralBounded) + (250 :: Word8, 252) + [250, 252, 254] + `shouldBe` True + +enumerateFromToIntegralBoundedOverflow :: Expectation +enumerateFromToIntegralBoundedOverflow = + testUnfold + (UF.take 10 UF.enumerateFromToIntegralBounded) + (253 :: Word8, 255) + [253, 254, 255] + `shouldBe` True + +enumerateFromThenToIntegralBoundedOverflow :: Expectation +enumerateFromThenToIntegralBoundedOverflow = + testUnfold + (UF.take 10 UF.enumerateFromThenToIntegralBounded) + (250 :: Word8, 252, 255) + [250, 252, 254] + `shouldBe` True + ------------------------------------------------------------------------------- -- Stream transformation ------------------------------------------------------------------------------- @@ -1049,6 +1114,20 @@ testGeneration = prop "enumerateFromThenIntegralBounded" enumerateFromThenIntegralBounded prop "enumerateFromToIntegralBounded" enumerateFromToIntegralBounded prop "enumerateFromThenToIntegralBounded" enumerateFromThenToIntegralBounded + ----------- Overflow at the bound of a fixed-size Integral type ---- + it "enumerateFromToIntegral overflow" enumerateFromToIntegralOverflow + it "enumerateFromThenToIntegral overflow up" + enumerateFromThenToIntegralOverflowUp + it "enumerateFromThenToIntegral overflow dn" + enumerateFromThenToIntegralOverflowDn + it "enumerateFromIntegralBounded overflow" + enumerateFromIntegralBoundedOverflow + it "enumerateFromThenIntegralBounded overflow" + enumerateFromThenIntegralBoundedOverflow + it "enumerateFromToIntegralBounded overflow" + enumerateFromToIntegralBoundedOverflow + it "enumerateFromThenToIntegralBounded overflow" + enumerateFromThenToIntegralBoundedOverflow ----------- Enumerate from Small Integral ------------------------- prop "enumerateFromSmallBounded" enumerateFromSmallBounded prop "enumerateFromThenSmallBounded" enumerateFromThenSmallBounded From 2b3a810f289aec2d552f1a66d505ffe689be669f Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 07:58:03 +0530 Subject: [PATCH 03/23] Add a test for a corner case of enumerateFromThenTo --- test/Streamly/Test/Data/Stream/Generate.hs | 8 ++++++++ test/Streamly/Test/Data/Unfold.hs | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/test/Streamly/Test/Data/Stream/Generate.hs b/test/Streamly/Test/Data/Stream/Generate.hs index a9044e04a9..5b08d8a2d9 100644 --- a/test/Streamly/Test/Data/Stream/Generate.hs +++ b/test/Streamly/Test/Data/Stream/Generate.hs @@ -204,6 +204,14 @@ testEnumerateFromThenToIntegral = do `shouldReturn` [0, 2, 4, 6] toList (Stream.enumerateFromThenToIntegral (0 :: Int) (-2) (-6)) `shouldReturn` [0, -2, -4, -6] + -- Regression test, matches [from, then .. to] from the Prelude. + -- Large stride with "then" included. + toList + (Stream.enumerateFromThenToIntegral + (-7537527385297985025) + 5092559113693760989 + (6977257977275108264 :: Int)) + `shouldReturn` [-7537527385297985025, 5092559113693760989] testEnumerateFromThenToFractional :: Expectation testEnumerateFromThenToFractional = diff --git a/test/Streamly/Test/Data/Unfold.hs b/test/Streamly/Test/Data/Unfold.hs index 0df1ed1ffc..4222eafb6e 100644 --- a/test/Streamly/Test/Data/Unfold.hs +++ b/test/Streamly/Test/Data/Unfold.hs @@ -291,6 +291,19 @@ enumerateFromToIntegral = in testUnfold unf (f :: Integer, to) $ Prelude.take 50 $ Prelude.enumFromTo f to +-- Regression test, matches [from, then .. to] from the Prelude. +-- Large stride with "then" included. +enumerateFromThenToIntegralLargeStride :: Expectation +enumerateFromThenToIntegralLargeStride = + testUnfold + UF.enumerateFromThenToIntegral + ( -7537527385297985025 :: Int + , 5092559113693760989 + , 6977257977275108264 + ) + [-7537527385297985025, 5092559113693760989] + `shouldBe` True + enumerateFromIntegralBounded :: Property enumerateFromIntegralBounded = property @@ -1109,6 +1122,8 @@ testGeneration = prop "enumerateFromThenIntegral" enumerateFromThenIntegral prop "enumerateFromToIntegral" enumerateFromToIntegral prop "enumerateFromThenToIntegral" enumerateFromThenToIntegral + it "enumerateFromThenToIntegral large stride" + enumerateFromThenToIntegralLargeStride prop "enumerateFromIntegralBounded" enumerateFromIntegralBounded prop "enumerateFromThenIntegralBounded" enumerateFromThenIntegralBounded From 596e842d23b5f153d7f89ad5f98c372dcecdebdb Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 1 Jul 2026 11:35:49 +0530 Subject: [PATCH 04/23] Fix enumerateFromThenToIntegral --- .../Streamly/Internal/Data/Stream/Generate.hs | 105 ++++++++---------- 1 file changed, 45 insertions(+), 60 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Stream/Generate.hs b/core/src/Streamly/Internal/Data/Stream/Generate.hs index 96bd133b9c..ffbc7adce3 100644 --- a/core/src/Streamly/Internal/Data/Stream/Generate.hs +++ b/core/src/Streamly/Internal/Data/Stream/Generate.hs @@ -28,7 +28,10 @@ module Streamly.Internal.Data.Stream.Generate , replicate , replicateM - -- * Enumeration + -- NOTE: We enumerate up variants only, we can add enumerateDown* variants + -- as well, to enumerate downwards to the final value. Currently that is + -- achieved by enumerateFromThen variants. + -- ** Enumerating 'Num' Types , enumerateFromStepNum , enumerateFromNum @@ -359,61 +362,13 @@ enumerateFromThenNum from next = enumerateFromStepNum from (next - from) ------------------------------------------------------------------------------ #ifndef USE_UNFOLDS_EVERYWHERE -data EnumState a = EnumInit | EnumYield a a a | EnumStop - -{-# INLINE_NORMAL enumerateFromThenToIntegralUp #-} -enumerateFromThenToIntegralUp - :: (Monad m, Integral a) - => a -> a -> a -> Stream m a -enumerateFromThenToIntegralUp from next to = Stream step EnumInit - where - {-# INLINE_LATE step #-} - step _ EnumInit = - return $ - if to < next - then if to < from - then Stop - else Yield from EnumStop - else -- from <= next <= to - let stride = next - from - in Skip $ EnumYield from stride (to - stride) - - step _ (EnumYield x stride toMinus) = - return $ - if x > toMinus - then Yield x EnumStop - else Yield x $ EnumYield (x + stride) stride toMinus - - step _ EnumStop = return Stop - -{-# INLINE_NORMAL enumerateFromThenToIntegralDn #-} -enumerateFromThenToIntegralDn - :: (Monad m, Integral a) - => a -> a -> a -> Stream m a -enumerateFromThenToIntegralDn from next to = Stream step EnumInit - where - {-# INLINE_LATE step #-} - step _ EnumInit = - return $ if to > next - then if to > from - then Stop - else Yield from EnumStop - else -- from >= next >= to - let stride = next - from - in Skip $ EnumYield from stride (to - stride) - - step _ (EnumYield x stride toMinus) = - return $ - if x < toMinus - then Yield x EnumStop - else Yield x $ EnumYield (x + stride) stride toMinus - - step _ EnumStop = return Stop +data EnumState a = + EnumInit + | EnumYieldUpward a a a + | EnumYieldDownward a a a + | EnumStop #endif --- XXX This can perhaps be simplified and written in terms of --- enumeratFromStepIntegral as we have done in unfolds. - -- | Enumerate an 'Integral' type in steps up to a given limit. -- @enumerateFromThenToIntegral from then to@ generates a finite stream whose -- first element is @from@, the second element is @then@ and the successive @@ -433,9 +388,40 @@ enumerateFromThenToIntegral enumerateFromThenToIntegral from next to = unfold Unfold.enumerateFromThenToIntegral (from, next, to) #else -enumerateFromThenToIntegral from next to - | next >= from = enumerateFromThenToIntegralUp from next to - | otherwise = enumerateFromThenToIntegralDn from next to +enumerateFromThenToIntegral from next to = Stream step EnumInit + + where + + {-# INLINE_LATE step #-} + step _ EnumInit = + return $ + if next >= from + then + if to < next + then if to < from then Stop else Yield from EnumStop + else -- from <= next <= to + let stride = next - from + in Skip $ EnumYieldUpward from stride (to - stride) + else + if to > next + then if to > from then Stop else Yield from EnumStop + else -- from >= next >= to + let stride = next - from + in Skip $ EnumYieldDownward from stride (to - stride) + + step _ (EnumYieldUpward x stride toMinus) = + return $ + if x > toMinus + then Yield x EnumStop + else Yield x $ EnumYieldUpward (x + stride) stride toMinus + + step _ (EnumYieldDownward x stride toMinus) = + return $ + if x < toMinus + then Yield x EnumStop + else Yield x $ EnumYieldDownward (x + stride) stride toMinus + + step _ EnumStop = return Stop #endif -- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from @@ -458,9 +444,8 @@ enumerateFromThenIntegral from next = unfold Unfold.enumerateFromThenIntegralBounded (from, next) #else enumerateFromThenIntegral from next = - if next > from - then enumerateFromThenToIntegralUp from next maxBound - else enumerateFromThenToIntegralDn from next minBound + enumerateFromThenToIntegral + from next (if next >= from then maxBound else minBound) #endif -- | @enumerateFromStepIntegral from step@ generates an infinite stream whose From fce2dcdb9ef5553bdf780ef7d0c7cf10ef5e6d9f Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 11:10:30 +0530 Subject: [PATCH 05/23] Fix overflow in enumerateFromToIntegral --- core/docs/Changelog.md | 2 ++ core/src/Streamly/Internal/Data/Stream/Generate.hs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/docs/Changelog.md b/core/docs/Changelog.md index 0ee0f6cd97..19f7ce5e31 100644 --- a/core/docs/Changelog.md +++ b/core/docs/Changelog.md @@ -11,6 +11,8 @@ * 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. ### Breaking changes diff --git a/core/src/Streamly/Internal/Data/Stream/Generate.hs b/core/src/Streamly/Internal/Data/Stream/Generate.hs index ffbc7adce3..03b89d0bdf 100644 --- a/core/src/Streamly/Internal/Data/Stream/Generate.hs +++ b/core/src/Streamly/Internal/Data/Stream/Generate.hs @@ -485,7 +485,7 @@ enumerateFromStepIntegral from stride = {-# INLINE enumerateFromToIntegral #-} enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a enumerateFromToIntegral from to = - takeWhile (<= to) $ enumerateFromStepIntegral from 1 + takeWhile (<= to) $ takeEndBy (== to) $ enumerateFromStepIntegral from 1 -- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a -- stream whose first element is @from@ and the successive elements are in From 88487e09ff1d8ff7b4be821b44bdfdd702aabf09 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 11:55:09 +0530 Subject: [PATCH 06/23] Move enumerateFromStepNum to Producer module --- core/src/Streamly/Internal/Data/Producer.hs | 14 ++++++++++++++ core/src/Streamly/Internal/Data/Stream/Generate.hs | 6 ++---- .../Streamly/Internal/Data/Unfold/Enumeration.hs | 12 ++---------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index ddb5623346..de661435ce 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -43,6 +43,7 @@ module Streamly.Internal.Data.Producer , mapMaybeM , takeWhileM , unfoldrM + , enumerateFromStepNum ) where @@ -549,3 +550,16 @@ unfoldrM next a = next a <&> \case Just (b, a1) -> Yield b a1 Nothing -> Stop + +-- | 'Producer' for enumerating starting from @from@, incrementing by +-- @stride@ every time. The state @(from, stride, i)@ carries the counter +-- @i@ used to compute @from + i * stride@ on each step; @from@ and @stride@ +-- are threaded through unchanged. +{-# INLINE_LATE enumerateFromStepNum #-} +enumerateFromStepNum :: (Applicative m, Num a) => Producer m (a, a, a) a +enumerateFromStepNum (from, stride, i) = + -- Note that the counter "i" is the same type as the type being enumerated. + -- It may overflow, for example, if we are enumerating Word8, after 255 the + -- counter will become 0, but the overflow does not affect the enumeration + -- behavior. + pure $ (Yield $! (from + i * stride)) $! (from, stride, i + 1) diff --git a/core/src/Streamly/Internal/Data/Stream/Generate.hs b/core/src/Streamly/Internal/Data/Stream/Generate.hs index 03b89d0bdf..dd34f4e514 100644 --- a/core/src/Streamly/Internal/Data/Stream/Generate.hs +++ b/core/src/Streamly/Internal/Data/Stream/Generate.hs @@ -343,10 +343,8 @@ enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a enumerateFromStepNum from stride = unfold Unfold.enumerateFromStepNum (from, stride) #else -enumerateFromStepNum from stride = Stream step 0 - where - {-# INLINE_LATE step #-} - step _ !i = return $ (Yield $! (from + i * stride)) $! (i + 1) +enumerateFromStepNum !from !stride = + Stream (const Producer.enumerateFromStepNum) (from, stride, 0) #endif {-# INLINE_NORMAL enumerateFromNum #-} diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 8e0e9f9ceb..5e31596ffb 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -66,6 +66,7 @@ import Data.Word import Numeric.Natural import Data.Functor.Identity (Identity(..)) import Streamly.Internal.Data.Unfold.Type hiding (takeWhileMWithInput) +import qualified Streamly.Internal.Data.Producer as Producer import Prelude hiding (map, mapM, takeWhile, take, filter, const, zipWith , drop, dropWhile) @@ -100,21 +101,12 @@ import Prelude -- {-# INLINE enumerateFromStepNum #-} enumerateFromStepNum :: (Monad m, Num a) => Unfold m (a, a) a -enumerateFromStepNum = Unfold step inject +enumerateFromStepNum = Unfold Producer.enumerateFromStepNum inject where inject (!from, !stride) = return (from, stride, 0) - -- Note that the counter "i" is the same type as the type being enumerated. - -- It may overflow, for example, if we are enumerating Word8, after 255 the - -- counter will become 0, but the overflow does not affect the enumeration - -- behavior. - {-# INLINE_LATE step #-} - step (from, stride, i) = - return $ - (Yield $! (from + i * stride)) $! (from, stride, i + 1) - -- | Same as 'enumerateFromStepNum (from, next)' using a stride of @next - from@: -- -- @ From 438952444bc532b52ccf36944327b7d1483f5880 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 12:01:21 +0530 Subject: [PATCH 07/23] Move enumerateFromStepIntegral to Producer module --- core/src/Streamly/Internal/Data/Producer.hs | 8 ++++++++ core/src/Streamly/Internal/Data/Stream/Generate.hs | 6 ++---- core/src/Streamly/Internal/Data/Unfold/Enumeration.hs | 5 +---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index de661435ce..c21a6f9458 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -44,6 +44,7 @@ module Streamly.Internal.Data.Producer , takeWhileM , unfoldrM , enumerateFromStepNum + , enumerateFromStepIntegral ) where @@ -563,3 +564,10 @@ enumerateFromStepNum (from, stride, i) = -- counter will become 0, but the overflow does not affect the enumeration -- behavior. pure $ (Yield $! (from + i * stride)) $! (from, stride, i + 1) + +-- | 'Producer' for enumerating integrals starting from @x@, incrementing by +-- a constant @stride@ every time. The state @(x, stride)@ carries the +-- current value and the stride, which is threaded through unchanged. +{-# INLINE_LATE enumerateFromStepIntegral #-} +enumerateFromStepIntegral :: (Applicative m, Integral a) => Producer m (a, a) a +enumerateFromStepIntegral (x, stride) = pure $ Yield x $! (x + stride, stride) diff --git a/core/src/Streamly/Internal/Data/Stream/Generate.hs b/core/src/Streamly/Internal/Data/Stream/Generate.hs index dd34f4e514..b7a4501694 100644 --- a/core/src/Streamly/Internal/Data/Stream/Generate.hs +++ b/core/src/Streamly/Internal/Data/Stream/Generate.hs @@ -466,10 +466,8 @@ enumerateFromStepIntegral from stride = unfold Unfold.enumerateFromStepIntegral (from, stride) #else enumerateFromStepIntegral from stride = - from `seq` stride `seq` Stream step from - where - {-# INLINE_LATE step #-} - step _ !x = return $ Yield x $! (x + stride) + from `seq` stride `seq` + Stream (const Producer.enumerateFromStepIntegral) (from, stride) #endif -- | Enumerate an 'Integral' type up to a given limit. diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 5e31596ffb..15243d66d2 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -179,15 +179,12 @@ takeWhileMWithInput f = map snd . takeWhileM (uncurry f) . carryInput -- /Internal/ {-# INLINE_NORMAL enumerateFromStepIntegral #-} enumerateFromStepIntegral :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromStepIntegral = Unfold step inject +enumerateFromStepIntegral = Unfold Producer.enumerateFromStepIntegral inject where inject (from, stride) = from `seq` stride `seq` return (from, stride) - {-# INLINE_LATE step #-} - step (x, stride) = return $ Yield x $! (x + stride, stride) - -- Enumerate Unbounded Integrals ---------------------------------------------- {-# INLINE enumerateFromIntegral #-} enumerateFromIntegral :: (Monad m, Integral a) => Unfold m a a From 9625aea6d779fc3edff4ed4f30167b05a31f50b8 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 12:30:31 +0530 Subject: [PATCH 08/23] Move enumerateFromThenToIntegral to the Producer module --- .../Benchmark/Data/Stream/Generate.hs | 3 +- core/src/Streamly/Internal/Data/Producer.hs | 47 +++++++++++++++++++ .../Streamly/Internal/Data/Stream/Generate.hs | 47 ++----------------- .../Internal/Data/Unfold/Enumeration.hs | 10 +--- 4 files changed, 55 insertions(+), 52 deletions(-) diff --git a/benchmark/Streamly/Benchmark/Data/Stream/Generate.hs b/benchmark/Streamly/Benchmark/Data/Stream/Generate.hs index 03e75916a9..f7c7a0e3d7 100644 --- a/benchmark/Streamly/Benchmark/Data/Stream/Generate.hs +++ b/benchmark/Streamly/Benchmark/Data/Stream/Generate.hs @@ -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) @@ -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 diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index c21a6f9458..467c3f495f 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -17,6 +17,7 @@ module Streamly.Internal.Data.Producer , CrossState(..) , FairCrossState(..) , TupleState(..) + , EnumState(..) , ConcatState(..) , InterleaveState(..) , InterleaveEachState(..) @@ -45,6 +46,7 @@ module Streamly.Internal.Data.Producer , unfoldrM , enumerateFromStepNum , enumerateFromStepIntegral + , enumerateFromThenToIntegral ) where @@ -571,3 +573,48 @@ enumerateFromStepNum (from, stride, i) = {-# INLINE_LATE enumerateFromStepIntegral #-} enumerateFromStepIntegral :: (Applicative m, Integral a) => Producer m (a, a) a enumerateFromStepIntegral (x, stride) = pure $ Yield x $! (x + stride, stride) + +-- | State for 'enumerateFromThenToIntegral'. @EnumInit from next to@ is the +-- starting state carrying the arguments; it transitions to 'EnumYieldUpward' +-- or 'EnumYieldDownward', which carry the current value, the stride and +-- @to - stride@ (checked against before incrementing, so that the increment +-- itself cannot overflow past the bound). +data EnumState a = + EnumInit a a a + | EnumYieldUpward a a a + | EnumYieldDownward a a a + | EnumStop + +-- | 'Producer' for enumerating an 'Integral' type in steps up to a given +-- limit. @EnumInit from next to@ generates a finite stream whose first +-- element is @from@, the second element is @next@ and the successive +-- elements are in increments of @next - from@ up to @to@. +{-# INLINE_LATE enumerateFromThenToIntegral #-} +enumerateFromThenToIntegral :: + (Applicative m, Integral a) => Producer m (EnumState a) a +enumerateFromThenToIntegral (EnumInit from next to) = + pure $ + if next >= from + then + if to < next + then if to < from then Stop else Yield from EnumStop + else -- from <= next <= to + let stride = next - from + in Skip $ EnumYieldUpward from stride (to - stride) + else + if to > next + then if to > from then Stop else Yield from EnumStop + else -- from >= next >= to + let stride = next - from + in Skip $ EnumYieldDownward from stride (to - stride) +enumerateFromThenToIntegral (EnumYieldUpward x stride toMinus) = + pure $ + if x > toMinus + then Yield x EnumStop + else Yield x $ EnumYieldUpward (x + stride) stride toMinus +enumerateFromThenToIntegral (EnumYieldDownward x stride toMinus) = + pure $ + if x < toMinus + then Yield x EnumStop + else Yield x $ EnumYieldDownward (x + stride) stride toMinus +enumerateFromThenToIntegral EnumStop = pure Stop diff --git a/core/src/Streamly/Internal/Data/Stream/Generate.hs b/core/src/Streamly/Internal/Data/Stream/Generate.hs index b7a4501694..91b98ca93d 100644 --- a/core/src/Streamly/Internal/Data/Stream/Generate.hs +++ b/core/src/Streamly/Internal/Data/Stream/Generate.hs @@ -52,7 +52,6 @@ module Streamly.Internal.Data.Stream.Generate , enumerateFromThenIntegral -- ** Enumerating 'Integral' Types - , EnumState (..) , enumerateFromToIntegral , enumerateFromThenToIntegral @@ -359,14 +358,6 @@ enumerateFromThenNum from next = enumerateFromStepNum from (next - from) -- Enumeration of Integrals ------------------------------------------------------------------------------ -#ifndef USE_UNFOLDS_EVERYWHERE -data EnumState a = - EnumInit - | EnumYieldUpward a a a - | EnumYieldDownward a a a - | EnumStop -#endif - -- | Enumerate an 'Integral' type in steps up to a given limit. -- @enumerateFromThenToIntegral from then to@ generates a finite stream whose -- first element is @from@, the second element is @then@ and the successive @@ -386,40 +377,10 @@ enumerateFromThenToIntegral enumerateFromThenToIntegral from next to = unfold Unfold.enumerateFromThenToIntegral (from, next, to) #else -enumerateFromThenToIntegral from next to = Stream step EnumInit - - where - - {-# INLINE_LATE step #-} - step _ EnumInit = - return $ - if next >= from - then - if to < next - then if to < from then Stop else Yield from EnumStop - else -- from <= next <= to - let stride = next - from - in Skip $ EnumYieldUpward from stride (to - stride) - else - if to > next - then if to > from then Stop else Yield from EnumStop - else -- from >= next >= to - let stride = next - from - in Skip $ EnumYieldDownward from stride (to - stride) - - step _ (EnumYieldUpward x stride toMinus) = - return $ - if x > toMinus - then Yield x EnumStop - else Yield x $ EnumYieldUpward (x + stride) stride toMinus - - step _ (EnumYieldDownward x stride toMinus) = - return $ - if x < toMinus - then Yield x EnumStop - else Yield x $ EnumYieldDownward (x + stride) stride toMinus - - step _ EnumStop = return Stop +enumerateFromThenToIntegral from next to = + Stream + (const Producer.enumerateFromThenToIntegral) + (Producer.EnumInit from next to) #endif -- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 15243d66d2..8bf4f8f38c 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -204,17 +204,11 @@ enumerateFromToIntegral = {-# INLINE enumerateFromThenToIntegral #-} enumerateFromThenToIntegral :: (Monad m, Integral a) => Unfold m (a, a, a) a enumerateFromThenToIntegral = - takeWhileMWithInput cond $ lmap toFromStep enumerateFromStepIntegral + Unfold Producer.enumerateFromThenToIntegral inject where - toFromStep (from, next, _) = (from, next - from) - - cond (from, next, to) b = - return - $ if next >= from - then b <= to - else b >= to + inject (from, next, to) = return (Producer.EnumInit from next to) -- Enumerate Bounded Integrals ------------------------------------------------ {-# INLINE enumerateFromIntegralBounded #-} From 2614443cbeabc8f4ab5924d355e4fe9054e8b976 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 12:34:31 +0530 Subject: [PATCH 09/23] Move takeEndByM to the Producer module Ad takeEndByM and takeEndBy to the Unfold module. --- core/src/Streamly/Internal/Data/Producer.hs | 21 ++++++++++++++++++ .../src/Streamly/Internal/Data/Stream/Type.hs | 22 +++++-------------- .../src/Streamly/Internal/Data/Unfold/Type.hs | 20 +++++++++++++++++ 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index 467c3f495f..70dbd0417a 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -43,6 +43,7 @@ module Streamly.Internal.Data.Producer , mapM , mapMaybeM , takeWhileM + , takeEndByM , unfoldrM , enumerateFromStepNum , enumerateFromStepIntegral @@ -544,6 +545,26 @@ takeWhileM f step1 st = do Skip s -> return (Skip s) Stop -> return Stop +-- | Like 'takeWhileM' but takes and includes the element on which the +-- predicate succeeds, then stops on the following step. The @Maybe s@ state +-- is 'Nothing' once the matching element has been yielded, so that the next +-- step can stop the producer. +{-# INLINE_LATE takeEndByM #-} +takeEndByM :: Monad m + => (b -> m Bool) -> Producer m s b -> Producer m (Maybe s) b +takeEndByM f step1 (Just st) = do + r <- step1 st + case r of + Yield x s -> do + b <- f x + return $ + if not b + then Yield x (Just s) + else Yield x Nothing + Skip s -> return $ Skip (Just s) + Stop -> return Stop +takeEndByM _ _ Nothing = return Stop + -- | Build a 'Producer' from a /monadic/ step function that generates the next -- element and the next seed value from the current seed value. It is invoked -- until it returns 'Nothing'. diff --git a/core/src/Streamly/Internal/Data/Stream/Type.hs b/core/src/Streamly/Internal/Data/Stream/Type.hs index ede7743401..27e318e212 100644 --- a/core/src/Streamly/Internal/Data/Stream/Type.hs +++ b/core/src/Streamly/Internal/Data/Stream/Type.hs @@ -1177,22 +1177,12 @@ takeWhile f = takeWhileM (return . f) {-# INLINE_NORMAL takeEndByM #-} takeEndByM :: Monad m => (a -> m Bool) -> Stream m a -> Stream m a -takeEndByM f (Stream step state) = Stream step' (Just state) - where - {-# INLINE_LATE step' #-} - step' gst (Just st) = do - r <- step gst st - case r of - Yield x s -> do - b <- f x - return $ - if not b - then Yield x (Just s) - else Yield x Nothing - Skip s -> return $ Skip (Just s) - Stop -> return Stop - - step' _ Nothing = return Stop +takeEndByM f (Stream step1 state1) = Stream step (Just state1) + + where + + {-# INLINE_LATE step #-} + step gst st = Producer.takeEndByM f (step1 gst) st {-# INLINE takeEndBy #-} takeEndBy :: Monad m => (a -> Bool) -> Stream m a -> Stream m a diff --git a/core/src/Streamly/Internal/Data/Unfold/Type.hs b/core/src/Streamly/Internal/Data/Unfold/Type.hs index fbd37b73e0..f1e2af56ce 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Type.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Type.hs @@ -86,6 +86,8 @@ module Streamly.Internal.Data.Unfold.Type -- * Trimming , takeWhileM , takeWhile + , takeEndByM + , takeEndBy -- * Nesting , interleave @@ -423,6 +425,24 @@ takeWhileM f (Unfold step1 inject1) = takeWhile :: Monad m => (b -> Bool) -> Unfold m a b -> Unfold m a b takeWhile f = takeWhileM (return . f) +-- | Same as 'takeEndBy' but with a monadic predicate. +-- +{-# INLINE_NORMAL takeEndByM #-} +takeEndByM :: Monad m => (b -> m Bool) -> Unfold m a b -> Unfold m a b +takeEndByM f (Unfold step1 inject1) = + Unfold (Producer.takeEndByM f step1) inject + + where + + inject a = Just <$> inject1 a + +-- | End the stream generated by the 'Unfold' as soon as the predicate +-- succeeds, including the matching element in the output. +-- +{-# INLINE takeEndBy #-} +takeEndBy :: Monad m => (b -> Bool) -> Unfold m a b -> Unfold m a b +takeEndBy f = takeEndByM (return . f) + ------------------------------------------------------------------------------ -- Functor ------------------------------------------------------------------------------ From cc34867025bd81ebdba0fefdbbe3508c24bc934e Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 12:50:47 +0530 Subject: [PATCH 10/23] Fix overflow in enumerateFromToIntegral Unfold --- core/src/Streamly/Internal/Data/Unfold/Enumeration.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 8bf4f8f38c..3796a51d7e 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -198,7 +198,10 @@ enumerateFromThenIntegral = {-# INLINE enumerateFromToIntegral #-} enumerateFromToIntegral :: (Monad m, Integral a) => Unfold m (a, a) a enumerateFromToIntegral = - takeWhileMWithInput (\(_, to) b -> return $ b <= to) + map snd + $ takeWhile (\((_, to), b) -> b <= to) + $ takeEndBy (\((_, to), b) -> b == to) + $ carryInput $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegral {-# INLINE enumerateFromThenToIntegral #-} From 89cf72af5ba4cf55349417eb8483e8dcc20bdbeb Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 13:19:16 +0530 Subject: [PATCH 11/23] Fix overflow in enumerateFromThenIntegralBounded And deprecate redundant functions. --- .../Internal/Data/Unfold/Enumeration.hs | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 3796a51d7e..eb307076d8 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -223,40 +223,24 @@ enumerateFromIntegralBounded = supplySecond maxBound enumerateFromToIntegral enumerateFromThenIntegralBounded :: (Monad m, Integral a, Bounded a ) => Unfold m (a, a) a enumerateFromThenIntegralBounded = - takeWhileMWithInput cond $ lmap toFromStep enumerateFromStepIntegral + lmap toFromThenTo enumerateFromThenToIntegral where - toFromStep (from, next) = (from, next - from) - - cond (from, next) b = - return - $ if next >= from - then b <= maxBound - else b >= minBound + toFromThenTo (from, next) = + (from, next, if next >= from then maxBound else minBound) +{-# DEPRECATED enumerateFromToIntegralBounded "Use enumerateFromToIntegral instead." #-} {-# INLINE enumerateFromToIntegralBounded #-} -enumerateFromToIntegralBounded :: (Monad m, Integral a, Bounded a) => +enumerateFromToIntegralBounded :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromToIntegralBounded = - takeWhileMWithInput (\(_, to) b -> return $ b <= to) - $ lmap fst enumerateFromIntegralBounded +enumerateFromToIntegralBounded = enumerateFromToIntegral +{-# DEPRECATED enumerateFromThenToIntegralBounded "Use enumerateFromThenToIntegral instead." #-} {-# INLINE enumerateFromThenToIntegralBounded #-} -enumerateFromThenToIntegralBounded :: (Monad m, Integral a, Bounded a) => +enumerateFromThenToIntegralBounded :: (Monad m, Integral a) => Unfold m (a, a, a) a -enumerateFromThenToIntegralBounded = - takeWhileMWithInput cond $ lmap toFromThen enumerateFromThenIntegralBounded - - where - - toFromThen (from, next, _) = (from, next) - - cond (from, next, to) b = - return - $ if next >= from - then b <= to - else b >= to +enumerateFromThenToIntegralBounded = enumerateFromThenToIntegral ------------------------------------------------------------------------------ -- Enumeration of Fractionals @@ -469,9 +453,9 @@ instance Enumerable INTEGRAL_TYPE where { \ {-# INLINE enumerateFromThen #-}; \ enumerateFromThen = enumerateFromThenIntegralBounded; \ {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegralBounded; \ + enumerateFromTo = enumerateFromToIntegral; \ {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegralBounded } + enumerateFromThenTo = enumerateFromThenToIntegral } ENUMERABLE_BOUNDED_INTEGRAL(Int) ENUMERABLE_BOUNDED_INTEGRAL(Int8) From 1787d0e83323ee62ed89238fba77ec3165ebd1d4 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 13:39:12 +0530 Subject: [PATCH 12/23] Split out Stream enumeration ops in a separate module --- .../Internal/Data/Array/Generic/Type.hs | 1 + .../Internal/Data/MutArray/Generic.hs | 2 +- core/src/Streamly/Internal/Data/Parser.hs | 2 +- core/src/Streamly/Internal/Data/Stream.hs | 2 + .../Internal/Data/Stream/Enumeration.hs | 565 ++++++++++++++++++ .../Streamly/Internal/Data/Stream/Generate.hs | 538 ----------------- core/streamly-core.cabal | 1 + 7 files changed, 571 insertions(+), 540 deletions(-) create mode 100644 core/src/Streamly/Internal/Data/Stream/Enumeration.hs diff --git a/core/src/Streamly/Internal/Data/Array/Generic/Type.hs b/core/src/Streamly/Internal/Data/Array/Generic/Type.hs index dd2f776f15..035ed80f1a 100644 --- a/core/src/Streamly/Internal/Data/Array/Generic/Type.hs +++ b/core/src/Streamly/Internal/Data/Array/Generic/Type.hs @@ -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 diff --git a/core/src/Streamly/Internal/Data/MutArray/Generic.hs b/core/src/Streamly/Internal/Data/MutArray/Generic.hs index 3d382b8d08..bfd1653b38 100644 --- a/core/src/Streamly/Internal/Data/MutArray/Generic.hs +++ b/core/src/Streamly/Internal/Data/MutArray/Generic.hs @@ -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 diff --git a/core/src/Streamly/Internal/Data/Parser.hs b/core/src/Streamly/Internal/Data/Parser.hs index cd2b99baf7..ef01b87ff3 100644 --- a/core/src/Streamly/Internal/Data/Parser.hs +++ b/core/src/Streamly/Internal/Data/Parser.hs @@ -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 diff --git a/core/src/Streamly/Internal/Data/Stream.hs b/core/src/Streamly/Internal/Data/Stream.hs index 4569111e3e..50376957c8 100644 --- a/core/src/Streamly/Internal/Data/Stream.hs +++ b/core/src/Streamly/Internal/Data/Stream.hs @@ -18,6 +18,7 @@ module Streamly.Internal.Data.Stream ( module Streamly.Internal.Data.Stream.Type , module Streamly.Internal.Data.Stream.Generate + , module Streamly.Internal.Data.Stream.Enumeration , module Streamly.Internal.Data.Stream.Eliminate , module Streamly.Internal.Data.Stream.Exception , module Streamly.Internal.Data.Stream.Lift @@ -32,6 +33,7 @@ where import Streamly.Internal.Data.Stream.Type import Streamly.Internal.Data.Stream.Generate +import Streamly.Internal.Data.Stream.Enumeration import Streamly.Internal.Data.Stream.Eliminate import Streamly.Internal.Data.Stream.Exception import Streamly.Internal.Data.Stream.Lift diff --git a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs new file mode 100644 index 0000000000..57e51dc7a9 --- /dev/null +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -0,0 +1,565 @@ +{-# LANGUAGE CPP #-} +-- | +-- Module : Streamly.Internal.Data.Stream.Enumeration +-- Copyright : (c) 2020 Composewell Technologies and Contributors +-- License : BSD-3-Clause +-- Maintainer : streamly@composewell.com +-- Stability : experimental +-- Portability : GHC +-- +module Streamly.Internal.Data.Stream.Enumeration + ( + -- NOTE: We enumerate up variants only, we can add enumerateDown* variants + -- as well, to enumerate downwards to the final value. Currently that is + -- achieved by enumerateFromThen variants. + + -- ** Enumerating 'Num' Types + enumerateFromStepNum + , enumerateFromNum + , enumerateFromThenNum + + -- ** Enumerating 'Bounded' 'Enum' Types + , enumerate + , enumerateTo + , enumerateFromBounded + + -- ** Enumerating 'Enum' Types not larger than 'Int' + , enumerateFromToSmall + , enumerateFromThenToSmall + , enumerateFromThenSmallBounded + + -- ** Enumerating 'Bounded' 'Integral' Types + , enumerateFromIntegral + , enumerateFromThenIntegral + + -- ** Enumerating 'Integral' Types + , enumerateFromToIntegral + , enumerateFromThenToIntegral + + -- ** Enumerating unbounded 'Integral' Types + , enumerateFromStepIntegral + + -- ** Enumerating 'Fractional' Types + , enumerateFromFractional + , enumerateFromToFractional + , enumerateFromThenFractional + , enumerateFromThenToFractional + + -- ** Enumerable Type Class + , Enumerable(..) + ) +where + +#include "inline.hs" + +import Data.Fixed +import Data.Functor.Identity (Identity(..)) +import Data.Int +import Data.Ratio +import Data.Word +import Numeric.Natural +import Prelude hiding (takeWhile) +import Streamly.Internal.Data.Stream.Type + +#ifdef USE_UNFOLDS_EVERYWHERE +import qualified Streamly.Internal.Data.Unfold as Unfold +#else +import qualified Streamly.Internal.Data.Producer as Producer +#endif + +#include "DocTestDataStream.hs" + +------------------------------------------------------------------------------ +-- Enumeration of Num +------------------------------------------------------------------------------ + +-- | For floating point numbers if the increment is less than the precision then +-- it just gets lost. Therefore we cannot always increment it correctly by just +-- repeated addition. +-- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15 +-- 9007199254740992 + 2 :: Double => 9.007199254740994e15 +-- +-- Instead we accumulate the increment counter and compute the increment +-- every time before adding it to the starting number. +-- +-- This works for Integrals as well as floating point numbers, but +-- enumerateFromStepIntegral is faster for integrals. +{-# INLINE_NORMAL enumerateFromStepNum #-} +enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a +#ifdef USE_UNFOLDS_EVERYWHERE +enumerateFromStepNum from stride = + unfold Unfold.enumerateFromStepNum (from, stride) +#else +enumerateFromStepNum !from !stride = + Stream (const Producer.enumerateFromStepNum) (from, stride, 0) +#endif + +{-# INLINE_NORMAL enumerateFromNum #-} +enumerateFromNum :: (Monad m, Num a) => a -> Stream m a +enumerateFromNum from = enumerateFromStepNum from 1 + +{-# INLINE_NORMAL enumerateFromThenNum #-} +enumerateFromThenNum :: (Monad m, Num a) => a -> a -> Stream m a +enumerateFromThenNum from next = enumerateFromStepNum from (next - from) + +------------------------------------------------------------------------------ +-- Enumeration of Integrals +------------------------------------------------------------------------------ + +-- | Enumerate an 'Integral' type in steps up to a given limit. +-- @enumerateFromThenToIntegral from then to@ generates a finite stream whose +-- first element is @from@, the second element is @then@ and the successive +-- elements are in increments of @then - from@ up to @to@. +-- +-- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 2 6 +-- [0,2,4,6] +-- +-- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 (-2) (-6) +-- [0,-2,-4,-6] +-- +{-# INLINE_NORMAL enumerateFromThenToIntegral #-} +enumerateFromThenToIntegral + :: (Monad m, Integral a) + => a -> a -> a -> Stream m a +#ifdef USE_UNFOLDS_EVERYWHERE +enumerateFromThenToIntegral from next to = + unfold Unfold.enumerateFromThenToIntegral (from, next, to) +#else +enumerateFromThenToIntegral from next to = + Stream + (const Producer.enumerateFromThenToIntegral) + (Producer.EnumInit from next to) +#endif + +-- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from +-- then@ generates a stream whose first element is @from@, the second element +-- is @then@ and the successive elements are in increments of @then - from@. +-- The stream is bounded by the size of the 'Integral' type. +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) 2 +-- [0,2,4,6] +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2) +-- [0,-2,-4,-6] +-- +{-# INLINE_NORMAL enumerateFromThenIntegral #-} +enumerateFromThenIntegral + :: (Monad m, Integral a, Bounded a) + => a -> a -> Stream m a +#ifdef USE_UNFOLDS_EVERYWHERE +enumerateFromThenIntegral from next = + unfold Unfold.enumerateFromThenIntegralBounded (from, next) +#else +enumerateFromThenIntegral from next = + enumerateFromThenToIntegral + from next (if next >= from then maxBound else minBound) +#endif + +-- | @enumerateFromStepIntegral from step@ generates an infinite stream whose +-- first element is @from@ and the successive elements are in increments of +-- @step@. +-- +-- CAUTION: This function is not safe for finite integral types. It does not +-- check for overflow, underflow or bounds. +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2 +-- [0,2,4,6] +-- +-- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2) +-- [0,-2,-4] +-- +{-# INLINE_NORMAL enumerateFromStepIntegral #-} +enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a +#ifdef USE_UNFOLDS_EVERYWHERE +enumerateFromStepIntegral from stride = + unfold Unfold.enumerateFromStepIntegral (from, stride) +#else +enumerateFromStepIntegral from stride = + from `seq` stride `seq` + Stream (const Producer.enumerateFromStepIntegral) (from, stride) +#endif + +-- | Enumerate an 'Integral' type up to a given limit. +-- @enumerateFromToIntegral from to@ generates a finite stream whose first +-- element is @from@ and successive elements are in increments of @1@ up to +-- @to@. +-- +-- >>> Stream.toList $ Stream.enumerateFromToIntegral 0 4 +-- [0,1,2,3,4] +-- +{-# INLINE enumerateFromToIntegral #-} +enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a +enumerateFromToIntegral from to = + takeWhile (<= to) $ takeEndBy (== to) $ enumerateFromStepIntegral from 1 + +-- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a +-- stream whose first element is @from@ and the successive elements are in +-- increments of @1@. The stream is bounded by the size of the 'Integral' type. +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int) +-- [0,1,2,3] +-- +{-# INLINE enumerateFromIntegral #-} +enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a +enumerateFromIntegral from = enumerateFromToIntegral from maxBound + +------------------------------------------------------------------------------ +-- Enumeration of Fractionals +------------------------------------------------------------------------------ + +-- We cannot write a general function for Num. The only way to write code +-- portable between the two is to use a 'Real' constraint and convert between +-- Fractional and Integral using fromRational which is horribly slow. + +-- Even though the underlying implementation of enumerateFromFractional and +-- enumerateFromThenFractional works for any 'Num' we have restricted these to +-- 'Fractional' because these do not perform any bounds check, in contrast to +-- integral versions and are therefore not equivalent substitutes for those. + +-- | Numerically stable enumeration from a 'Fractional' number in steps of size +-- @1@. @enumerateFromFractional from@ generates a stream whose first element +-- is @from@ and the successive elements are in increments of @1@. No overflow +-- or underflow checks are performed. +-- +-- This is the equivalent to 'enumFrom' for 'Fractional' types. For example: +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromFractional 1.1 +-- [1.1,2.1,3.1,4.1] +-- +{-# INLINE enumerateFromFractional #-} +enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a +enumerateFromFractional = enumerateFromNum + +-- | Numerically stable enumeration from a 'Fractional' number in steps. +-- @enumerateFromThenFractional from then@ generates a stream whose first +-- element is @from@, the second element is @then@ and the successive elements +-- are in increments of @then - from@. No overflow or underflow checks are +-- performed. +-- +-- This is the equivalent of 'enumFromThen' for 'Fractional' types. For +-- example: +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 2.1 +-- [1.1,2.1,3.1,4.1] +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 (-2.1) +-- [1.1,-2.1,-5.300000000000001,-8.500000000000002] +-- +{-# INLINE enumerateFromThenFractional #-} +enumerateFromThenFractional + :: (Monad m, Fractional a) + => a -> a -> Stream m a +enumerateFromThenFractional = enumerateFromThenNum + +-- | Numerically stable enumeration from a 'Fractional' number to a given +-- limit. @enumerateFromToFractional from to@ generates a finite stream whose +-- first element is @from@ and successive elements are in increments of @1@ up +-- to @to@. +-- +-- This is the equivalent of 'enumFromTo' for 'Fractional' types. For +-- example: +-- +-- >>> Stream.toList $ Stream.enumerateFromToFractional 1.1 4 +-- [1.1,2.1,3.1,4.1] +-- +-- >>> Stream.toList $ Stream.enumerateFromToFractional 1.1 4.6 +-- [1.1,2.1,3.1,4.1,5.1] +-- +-- Notice that the last element is equal to the specified @to@ value after +-- rounding to the nearest integer. +-- +{-# INLINE_NORMAL enumerateFromToFractional #-} +enumerateFromToFractional + :: (Monad m, Fractional a, Ord a) + => a -> a -> Stream m a +enumerateFromToFractional from to = + takeWhile (<= to + 1 / 2) $ enumerateFromStepNum from 1 + +-- | Numerically stable enumeration from a 'Fractional' number in steps up to a +-- given limit. @enumerateFromThenToFractional from then to@ generates a +-- finite stream whose first element is @from@, the second element is @then@ +-- and the successive elements are in increments of @then - from@ up to @to@. +-- +-- This is the equivalent of 'enumFromThenTo' for 'Fractional' types. For +-- example: +-- +-- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 2 6 +-- [0.1,2.0,3.9,5.799999999999999] +-- +-- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 (-2) (-6) +-- [0.1,-2.0,-4.1000000000000005,-6.200000000000001] +-- +{-# INLINE_NORMAL enumerateFromThenToFractional #-} +enumerateFromThenToFractional + :: (Monad m, Fractional a, Ord a) + => a -> a -> a -> Stream m a +enumerateFromThenToFractional from next to = + takeWhile predicate $ enumerateFromThenFractional from next + where + mid = (next - from) / 2 + predicate | next >= from = (<= to + mid) + | otherwise = (>= to + mid) + +------------------------------------------------------------------------------- +-- Enumeration of Enum types not larger than Int +------------------------------------------------------------------------------- +-- +-- | 'enumerateFromTo' for 'Enum' types not larger than 'Int'. +-- +{-# INLINE enumerateFromToSmall #-} +enumerateFromToSmall :: (Monad m, Enum a) => a -> a -> Stream m a +enumerateFromToSmall from to = + fmap toEnum + $ enumerateFromToIntegral (fromEnum from) (fromEnum to) + +-- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'. +-- +{-# INLINE enumerateFromThenToSmall #-} +enumerateFromThenToSmall :: (Monad m, Enum a) + => a -> a -> a -> Stream m a +enumerateFromThenToSmall from next to = + fmap toEnum + $ enumerateFromThenToIntegral + (fromEnum from) (fromEnum next) (fromEnum to) + +-- | 'enumerateFromThen' for 'Enum' types not larger than 'Int'. +-- +-- Note: We convert the 'Enum' to 'Int' and enumerate the 'Int'. If a +-- type is bounded but does not have a 'Bounded' instance then we can go on +-- enumerating it beyond the legal values of the type, resulting in the failure +-- of 'toEnum' when converting back to 'Enum'. Therefore we require a 'Bounded' +-- instance for this function to be safely used. +-- +{-# INLINE enumerateFromThenSmallBounded #-} +enumerateFromThenSmallBounded :: (Monad m, Enumerable a, Bounded a) + => a -> a -> Stream m a +enumerateFromThenSmallBounded from next = + if fromEnum next >= fromEnum from + then enumerateFromThenTo from next maxBound + else enumerateFromThenTo from next minBound + +------------------------------------------------------------------------------- +-- Enumerable type class +------------------------------------------------------------------------------- +-- +-- NOTE: We would like to rewrite calls to fromList [1..] etc. to stream +-- enumerations like this: +-- +-- {-# RULES "fromList enumFrom" [1] +-- forall (a :: Int). D.fromList (enumFrom a) = D.enumerateFromIntegral a #-} +-- +-- But this does not work because enumFrom is a class method and GHC rewrites +-- it quickly, so we do not get a chance to have our rule fired. + +-- | Types that can be enumerated as a stream. The operations in this type +-- class are equivalent to those in the 'Enum' type class, except that these +-- generate a stream instead of a list. Use the functions in +-- "Streamly.Internal.Data.Stream.Enumeration" module to define new instances. +-- +class Enum a => Enumerable a where + -- | @enumerateFrom from@ generates a stream starting with the element + -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or + -- generating an infinite stream when the type is not 'Bounded'. + -- + -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFrom (0 :: Int) + -- [0,1,2,3] + -- + -- For 'Fractional' types, enumeration is numerically stable. However, no + -- overflow or underflow checks are performed. + -- + -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFrom 1.1 + -- [1.1,2.1,3.1,4.1] + -- + enumerateFrom :: (Monad m) => a -> Stream m a + + -- | Generate a finite stream starting with the element @from@, enumerating + -- the type up to the value @to@. If @to@ is smaller than @from@ then an + -- empty stream is returned. + -- + -- >>> Stream.toList $ Stream.enumerateFromTo 0 4 + -- [0,1,2,3,4] + -- + -- For 'Fractional' types, the last element is equal to the specified @to@ + -- value after rounding to the nearest integral value. + -- + -- >>> Stream.toList $ Stream.enumerateFromTo 1.1 4 + -- [1.1,2.1,3.1,4.1] + -- + -- >>> Stream.toList $ Stream.enumerateFromTo 1.1 4.6 + -- [1.1,2.1,3.1,4.1,5.1] + -- + enumerateFromTo :: (Monad m) => a -> a -> Stream m a + + -- | @enumerateFromThen from then@ generates a stream whose first element + -- is @from@, the second element is @then@ and the successive elements are + -- in increments of @then - from@. Enumeration can occur downwards or + -- upwards depending on whether @then@ comes before or after @from@. For + -- 'Bounded' types the stream ends when 'maxBound' is reached, for + -- unbounded types it keeps enumerating infinitely. + -- + -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 2 + -- [0,2,4,6] + -- + -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 (-2) + -- [0,-2,-4,-6] + -- + enumerateFromThen :: (Monad m) => a -> a -> Stream m a + + -- | @enumerateFromThenTo from then to@ generates a finite stream whose + -- first element is @from@, the second element is @then@ and the successive + -- elements are in increments of @then - from@ up to @to@. Enumeration can + -- occur downwards or upwards depending on whether @then@ comes before or + -- after @from@. + -- + -- >>> Stream.toList $ Stream.enumerateFromThenTo 0 2 6 + -- [0,2,4,6] + -- + -- >>> Stream.toList $ Stream.enumerateFromThenTo 0 (-2) (-6) + -- [0,-2,-4,-6] + -- + enumerateFromThenTo :: (Monad m) => a -> a -> a -> Stream m a + +-- MAYBE: Sometimes it is more convenient to know the count rather then the +-- ending or starting element. For those cases we can define the folllowing +-- APIs. All of these will work only for bounded types if we represent the +-- count by Int. +-- +-- enumerateN +-- enumerateFromN +-- enumerateToN +-- enumerateFromStep +-- enumerateFromStepN + +------------------------------------------------------------------------------- +-- Convenient functions for bounded types +------------------------------------------------------------------------------- +-- +-- | +-- > enumerate = enumerateFrom minBound +-- +-- Enumerate a 'Bounded' type from its 'minBound' to 'maxBound' +-- +{-# INLINE enumerate #-} +enumerate :: (Monad m, Bounded a, Enumerable a) => Stream m a +enumerate = enumerateFrom minBound + +-- | +-- >>> enumerateTo = Stream.enumerateFromTo minBound +-- +-- Enumerate a 'Bounded' type from its 'minBound' to specified value. +-- +{-# INLINE enumerateTo #-} +enumerateTo :: (Monad m, Bounded a, Enumerable a) => a -> Stream m a +enumerateTo = enumerateFromTo minBound + +-- | +-- >>> enumerateFromBounded from = Stream.enumerateFromTo from maxBound +-- +-- 'enumerateFrom' for 'Bounded' 'Enum' types. +-- +{-# INLINE enumerateFromBounded #-} +enumerateFromBounded :: (Monad m, Enumerable a, Bounded a) + => a -> Stream m a +enumerateFromBounded from = enumerateFromTo from maxBound + +------------------------------------------------------------------------------- +-- Enumerable Instances +------------------------------------------------------------------------------- +-- +-- For Enum types smaller than or equal to Int size. +#define ENUMERABLE_BOUNDED_SMALL(SMALL_TYPE) \ +instance Enumerable SMALL_TYPE where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom = enumerateFromBounded; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenSmallBounded; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToSmall; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToSmall } + +ENUMERABLE_BOUNDED_SMALL(()) +ENUMERABLE_BOUNDED_SMALL(Bool) +ENUMERABLE_BOUNDED_SMALL(Ordering) +ENUMERABLE_BOUNDED_SMALL(Char) + +-- For bounded Integral Enum types, may be larger than Int. +#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ +instance Enumerable INTEGRAL_TYPE where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom = enumerateFromIntegral; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenIntegral; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToIntegral; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToIntegral } + +ENUMERABLE_BOUNDED_INTEGRAL(Int) +ENUMERABLE_BOUNDED_INTEGRAL(Int8) +ENUMERABLE_BOUNDED_INTEGRAL(Int16) +ENUMERABLE_BOUNDED_INTEGRAL(Int32) +ENUMERABLE_BOUNDED_INTEGRAL(Int64) +ENUMERABLE_BOUNDED_INTEGRAL(Word) +ENUMERABLE_BOUNDED_INTEGRAL(Word8) +ENUMERABLE_BOUNDED_INTEGRAL(Word16) +ENUMERABLE_BOUNDED_INTEGRAL(Word32) +ENUMERABLE_BOUNDED_INTEGRAL(Word64) + +-- For unbounded Integral Enum types. +#define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \ +instance Enumerable INTEGRAL_TYPE where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom from = enumerateFromStepIntegral from 1; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen from next = \ + enumerateFromStepIntegral from (next - from); \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToIntegral; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToIntegral } + +ENUMERABLE_UNBOUNDED_INTEGRAL(Integer) +ENUMERABLE_UNBOUNDED_INTEGRAL(Natural) + +#define ENUMERABLE_FRACTIONAL(FRACTIONAL_TYPE,CONSTRAINT) \ +instance (CONSTRAINT) => Enumerable FRACTIONAL_TYPE where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom = enumerateFromFractional; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenFractional; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToFractional; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToFractional } + +ENUMERABLE_FRACTIONAL(Float,) +ENUMERABLE_FRACTIONAL(Double,) +ENUMERABLE_FRACTIONAL((Fixed a),HasResolution a) +ENUMERABLE_FRACTIONAL((Ratio a),Integral a) + +instance Enumerable a => Enumerable (Identity a) where + {-# INLINE enumerateFrom #-} + enumerateFrom (Identity from) = + fmap Identity $ enumerateFrom from + {-# INLINE enumerateFromThen #-} + enumerateFromThen (Identity from) (Identity next) = + fmap Identity $ enumerateFromThen from next + {-# INLINE enumerateFromTo #-} + enumerateFromTo (Identity from) (Identity to) = + fmap Identity $ enumerateFromTo from to + {-# INLINE enumerateFromThenTo #-} + enumerateFromThenTo (Identity from) (Identity next) (Identity to) = + fmap Identity + $ enumerateFromThenTo from next to + +-- TODO +{- +instance Enumerable a => Enumerable (Last a) +instance Enumerable a => Enumerable (First a) +instance Enumerable a => Enumerable (Max a) +instance Enumerable a => Enumerable (Min a) +instance Enumerable a => Enumerable (Const a b) +instance Enumerable (f a) => Enumerable (Alt f a) +instance Enumerable (f a) => Enumerable (Ap f a) +-} diff --git a/core/src/Streamly/Internal/Data/Stream/Generate.hs b/core/src/Streamly/Internal/Data/Stream/Generate.hs index 91b98ca93d..a48876f97b 100644 --- a/core/src/Streamly/Internal/Data/Stream/Generate.hs +++ b/core/src/Streamly/Internal/Data/Stream/Generate.hs @@ -28,45 +28,6 @@ module Streamly.Internal.Data.Stream.Generate , replicate , replicateM - -- NOTE: We enumerate up variants only, we can add enumerateDown* variants - -- as well, to enumerate downwards to the final value. Currently that is - -- achieved by enumerateFromThen variants. - - -- ** Enumerating 'Num' Types - , enumerateFromStepNum - , enumerateFromNum - , enumerateFromThenNum - - -- ** Enumerating 'Bounded' 'Enum' Types - , enumerate - , enumerateTo - , enumerateFromBounded - - -- ** Enumerating 'Enum' Types not larger than 'Int' - , enumerateFromToSmall - , enumerateFromThenToSmall - , enumerateFromThenSmallBounded - - -- ** Enumerating 'Bounded' 'Integral' Types - , enumerateFromIntegral - , enumerateFromThenIntegral - - -- ** Enumerating 'Integral' Types - , enumerateFromToIntegral - , enumerateFromThenToIntegral - - -- ** Enumerating unbounded 'Integral' Types - , enumerateFromStepIntegral - - -- ** Enumerating 'Fractional' Types - , enumerateFromFractional - , enumerateFromToFractional - , enumerateFromThenFractional - , enumerateFromThenToFractional - - -- ** Enumerable Type Class - , Enumerable(..) - -- * Time Enumeration , times , timesWith @@ -110,7 +71,6 @@ where #include "ArrayMacros.h" import Control.Monad.IO.Class (MonadIO(..)) -import Data.Functor.Identity (Identity(..)) import Foreign.Ptr (Ptr, plusPtr) import Foreign.Storable (Storable (peek), sizeOf) import GHC.Exts (Addr#, Ptr (Ptr)) @@ -126,11 +86,7 @@ import qualified Streamly.Internal.Data.Unfold as Unfold import qualified Streamly.Internal.Data.Producer as Producer #endif -import Data.Fixed -import Data.Int -import Data.Ratio import Data.Word -import Numeric.Natural import Prelude hiding (iterate, repeat, replicate, take, takeWhile) import Streamly.Internal.Data.Stream.Type @@ -321,500 +277,6 @@ replicateM n p = Stream step n replicate :: Monad m => Int -> a -> Stream m a replicate n x = replicateM n (return x) ------------------------------------------------------------------------------- --- Enumeration of Num ------------------------------------------------------------------------------- - --- | For floating point numbers if the increment is less than the precision then --- it just gets lost. Therefore we cannot always increment it correctly by just --- repeated addition. --- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15 --- 9007199254740992 + 2 :: Double => 9.007199254740994e15 --- --- Instead we accumulate the increment counter and compute the increment --- every time before adding it to the starting number. --- --- This works for Integrals as well as floating point numbers, but --- enumerateFromStepIntegral is faster for integrals. -{-# INLINE_NORMAL enumerateFromStepNum #-} -enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromStepNum from stride = - unfold Unfold.enumerateFromStepNum (from, stride) -#else -enumerateFromStepNum !from !stride = - Stream (const Producer.enumerateFromStepNum) (from, stride, 0) -#endif - -{-# INLINE_NORMAL enumerateFromNum #-} -enumerateFromNum :: (Monad m, Num a) => a -> Stream m a -enumerateFromNum from = enumerateFromStepNum from 1 - -{-# INLINE_NORMAL enumerateFromThenNum #-} -enumerateFromThenNum :: (Monad m, Num a) => a -> a -> Stream m a -enumerateFromThenNum from next = enumerateFromStepNum from (next - from) - ------------------------------------------------------------------------------- --- Enumeration of Integrals ------------------------------------------------------------------------------- - --- | Enumerate an 'Integral' type in steps up to a given limit. --- @enumerateFromThenToIntegral from then to@ generates a finite stream whose --- first element is @from@, the second element is @then@ and the successive --- elements are in increments of @then - from@ up to @to@. --- --- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 2 6 --- [0,2,4,6] --- --- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 (-2) (-6) --- [0,-2,-4,-6] --- -{-# INLINE_NORMAL enumerateFromThenToIntegral #-} -enumerateFromThenToIntegral - :: (Monad m, Integral a) - => a -> a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromThenToIntegral from next to = - unfold Unfold.enumerateFromThenToIntegral (from, next, to) -#else -enumerateFromThenToIntegral from next to = - Stream - (const Producer.enumerateFromThenToIntegral) - (Producer.EnumInit from next to) -#endif - --- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from --- then@ generates a stream whose first element is @from@, the second element --- is @then@ and the successive elements are in increments of @then - from@. --- The stream is bounded by the size of the 'Integral' type. --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) 2 --- [0,2,4,6] --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2) --- [0,-2,-4,-6] --- -{-# INLINE_NORMAL enumerateFromThenIntegral #-} -enumerateFromThenIntegral - :: (Monad m, Integral a, Bounded a) - => a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromThenIntegral from next = - unfold Unfold.enumerateFromThenIntegralBounded (from, next) -#else -enumerateFromThenIntegral from next = - enumerateFromThenToIntegral - from next (if next >= from then maxBound else minBound) -#endif - --- | @enumerateFromStepIntegral from step@ generates an infinite stream whose --- first element is @from@ and the successive elements are in increments of --- @step@. --- --- CAUTION: This function is not safe for finite integral types. It does not --- check for overflow, underflow or bounds. --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2 --- [0,2,4,6] --- --- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2) --- [0,-2,-4] --- -{-# INLINE_NORMAL enumerateFromStepIntegral #-} -enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromStepIntegral from stride = - unfold Unfold.enumerateFromStepIntegral (from, stride) -#else -enumerateFromStepIntegral from stride = - from `seq` stride `seq` - Stream (const Producer.enumerateFromStepIntegral) (from, stride) -#endif - --- | Enumerate an 'Integral' type up to a given limit. --- @enumerateFromToIntegral from to@ generates a finite stream whose first --- element is @from@ and successive elements are in increments of @1@ up to --- @to@. --- --- >>> Stream.toList $ Stream.enumerateFromToIntegral 0 4 --- [0,1,2,3,4] --- -{-# INLINE enumerateFromToIntegral #-} -enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a -enumerateFromToIntegral from to = - takeWhile (<= to) $ takeEndBy (== to) $ enumerateFromStepIntegral from 1 - --- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a --- stream whose first element is @from@ and the successive elements are in --- increments of @1@. The stream is bounded by the size of the 'Integral' type. --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int) --- [0,1,2,3] --- -{-# INLINE enumerateFromIntegral #-} -enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a -enumerateFromIntegral from = enumerateFromToIntegral from maxBound - ------------------------------------------------------------------------------- --- Enumeration of Fractionals ------------------------------------------------------------------------------- - --- We cannot write a general function for Num. The only way to write code --- portable between the two is to use a 'Real' constraint and convert between --- Fractional and Integral using fromRational which is horribly slow. - --- Even though the underlying implementation of enumerateFromFractional and --- enumerateFromThenFractional works for any 'Num' we have restricted these to --- 'Fractional' because these do not perform any bounds check, in contrast to --- integral versions and are therefore not equivalent substitutes for those. - --- | Numerically stable enumeration from a 'Fractional' number in steps of size --- @1@. @enumerateFromFractional from@ generates a stream whose first element --- is @from@ and the successive elements are in increments of @1@. No overflow --- or underflow checks are performed. --- --- This is the equivalent to 'enumFrom' for 'Fractional' types. For example: --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromFractional 1.1 --- [1.1,2.1,3.1,4.1] --- -{-# INLINE enumerateFromFractional #-} -enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a -enumerateFromFractional = enumerateFromNum - --- | Numerically stable enumeration from a 'Fractional' number in steps. --- @enumerateFromThenFractional from then@ generates a stream whose first --- element is @from@, the second element is @then@ and the successive elements --- are in increments of @then - from@. No overflow or underflow checks are --- performed. --- --- This is the equivalent of 'enumFromThen' for 'Fractional' types. For --- example: --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 2.1 --- [1.1,2.1,3.1,4.1] --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 (-2.1) --- [1.1,-2.1,-5.300000000000001,-8.500000000000002] --- -{-# INLINE enumerateFromThenFractional #-} -enumerateFromThenFractional - :: (Monad m, Fractional a) - => a -> a -> Stream m a -enumerateFromThenFractional = enumerateFromThenNum - --- | Numerically stable enumeration from a 'Fractional' number to a given --- limit. @enumerateFromToFractional from to@ generates a finite stream whose --- first element is @from@ and successive elements are in increments of @1@ up --- to @to@. --- --- This is the equivalent of 'enumFromTo' for 'Fractional' types. For --- example: --- --- >>> Stream.toList $ Stream.enumerateFromToFractional 1.1 4 --- [1.1,2.1,3.1,4.1] --- --- >>> Stream.toList $ Stream.enumerateFromToFractional 1.1 4.6 --- [1.1,2.1,3.1,4.1,5.1] --- --- Notice that the last element is equal to the specified @to@ value after --- rounding to the nearest integer. --- -{-# INLINE_NORMAL enumerateFromToFractional #-} -enumerateFromToFractional - :: (Monad m, Fractional a, Ord a) - => a -> a -> Stream m a -enumerateFromToFractional from to = - takeWhile (<= to + 1 / 2) $ enumerateFromStepNum from 1 - --- | Numerically stable enumeration from a 'Fractional' number in steps up to a --- given limit. @enumerateFromThenToFractional from then to@ generates a --- finite stream whose first element is @from@, the second element is @then@ --- and the successive elements are in increments of @then - from@ up to @to@. --- --- This is the equivalent of 'enumFromThenTo' for 'Fractional' types. For --- example: --- --- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 2 6 --- [0.1,2.0,3.9,5.799999999999999] --- --- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 (-2) (-6) --- [0.1,-2.0,-4.1000000000000005,-6.200000000000001] --- -{-# INLINE_NORMAL enumerateFromThenToFractional #-} -enumerateFromThenToFractional - :: (Monad m, Fractional a, Ord a) - => a -> a -> a -> Stream m a -enumerateFromThenToFractional from next to = - takeWhile predicate $ enumerateFromThenFractional from next - where - mid = (next - from) / 2 - predicate | next >= from = (<= to + mid) - | otherwise = (>= to + mid) - -------------------------------------------------------------------------------- --- Enumeration of Enum types not larger than Int -------------------------------------------------------------------------------- --- --- | 'enumerateFromTo' for 'Enum' types not larger than 'Int'. --- -{-# INLINE enumerateFromToSmall #-} -enumerateFromToSmall :: (Monad m, Enum a) => a -> a -> Stream m a -enumerateFromToSmall from to = - fmap toEnum - $ enumerateFromToIntegral (fromEnum from) (fromEnum to) - --- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'. --- -{-# INLINE enumerateFromThenToSmall #-} -enumerateFromThenToSmall :: (Monad m, Enum a) - => a -> a -> a -> Stream m a -enumerateFromThenToSmall from next to = - fmap toEnum - $ enumerateFromThenToIntegral - (fromEnum from) (fromEnum next) (fromEnum to) - --- | 'enumerateFromThen' for 'Enum' types not larger than 'Int'. --- --- Note: We convert the 'Enum' to 'Int' and enumerate the 'Int'. If a --- type is bounded but does not have a 'Bounded' instance then we can go on --- enumerating it beyond the legal values of the type, resulting in the failure --- of 'toEnum' when converting back to 'Enum'. Therefore we require a 'Bounded' --- instance for this function to be safely used. --- -{-# INLINE enumerateFromThenSmallBounded #-} -enumerateFromThenSmallBounded :: (Monad m, Enumerable a, Bounded a) - => a -> a -> Stream m a -enumerateFromThenSmallBounded from next = - if fromEnum next >= fromEnum from - then enumerateFromThenTo from next maxBound - else enumerateFromThenTo from next minBound - -------------------------------------------------------------------------------- --- Enumerable type class -------------------------------------------------------------------------------- --- --- NOTE: We would like to rewrite calls to fromList [1..] etc. to stream --- enumerations like this: --- --- {-# RULES "fromList enumFrom" [1] --- forall (a :: Int). D.fromList (enumFrom a) = D.enumerateFromIntegral a #-} --- --- But this does not work because enumFrom is a class method and GHC rewrites --- it quickly, so we do not get a chance to have our rule fired. - --- | Types that can be enumerated as a stream. The operations in this type --- class are equivalent to those in the 'Enum' type class, except that these --- generate a stream instead of a list. Use the functions in --- "Streamly.Internal.Data.Stream.Enumeration" module to define new instances. --- -class Enum a => Enumerable a where - -- | @enumerateFrom from@ generates a stream starting with the element - -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or - -- generating an infinite stream when the type is not 'Bounded'. - -- - -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFrom (0 :: Int) - -- [0,1,2,3] - -- - -- For 'Fractional' types, enumeration is numerically stable. However, no - -- overflow or underflow checks are performed. - -- - -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFrom 1.1 - -- [1.1,2.1,3.1,4.1] - -- - enumerateFrom :: (Monad m) => a -> Stream m a - - -- | Generate a finite stream starting with the element @from@, enumerating - -- the type up to the value @to@. If @to@ is smaller than @from@ then an - -- empty stream is returned. - -- - -- >>> Stream.toList $ Stream.enumerateFromTo 0 4 - -- [0,1,2,3,4] - -- - -- For 'Fractional' types, the last element is equal to the specified @to@ - -- value after rounding to the nearest integral value. - -- - -- >>> Stream.toList $ Stream.enumerateFromTo 1.1 4 - -- [1.1,2.1,3.1,4.1] - -- - -- >>> Stream.toList $ Stream.enumerateFromTo 1.1 4.6 - -- [1.1,2.1,3.1,4.1,5.1] - -- - enumerateFromTo :: (Monad m) => a -> a -> Stream m a - - -- | @enumerateFromThen from then@ generates a stream whose first element - -- is @from@, the second element is @then@ and the successive elements are - -- in increments of @then - from@. Enumeration can occur downwards or - -- upwards depending on whether @then@ comes before or after @from@. For - -- 'Bounded' types the stream ends when 'maxBound' is reached, for - -- unbounded types it keeps enumerating infinitely. - -- - -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 2 - -- [0,2,4,6] - -- - -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThen 0 (-2) - -- [0,-2,-4,-6] - -- - enumerateFromThen :: (Monad m) => a -> a -> Stream m a - - -- | @enumerateFromThenTo from then to@ generates a finite stream whose - -- first element is @from@, the second element is @then@ and the successive - -- elements are in increments of @then - from@ up to @to@. Enumeration can - -- occur downwards or upwards depending on whether @then@ comes before or - -- after @from@. - -- - -- >>> Stream.toList $ Stream.enumerateFromThenTo 0 2 6 - -- [0,2,4,6] - -- - -- >>> Stream.toList $ Stream.enumerateFromThenTo 0 (-2) (-6) - -- [0,-2,-4,-6] - -- - enumerateFromThenTo :: (Monad m) => a -> a -> a -> Stream m a - --- MAYBE: Sometimes it is more convenient to know the count rather then the --- ending or starting element. For those cases we can define the folllowing --- APIs. All of these will work only for bounded types if we represent the --- count by Int. --- --- enumerateN --- enumerateFromN --- enumerateToN --- enumerateFromStep --- enumerateFromStepN - -------------------------------------------------------------------------------- --- Convenient functions for bounded types -------------------------------------------------------------------------------- --- --- | --- > enumerate = enumerateFrom minBound --- --- Enumerate a 'Bounded' type from its 'minBound' to 'maxBound' --- -{-# INLINE enumerate #-} -enumerate :: (Monad m, Bounded a, Enumerable a) => Stream m a -enumerate = enumerateFrom minBound - --- | --- >>> enumerateTo = Stream.enumerateFromTo minBound --- --- Enumerate a 'Bounded' type from its 'minBound' to specified value. --- -{-# INLINE enumerateTo #-} -enumerateTo :: (Monad m, Bounded a, Enumerable a) => a -> Stream m a -enumerateTo = enumerateFromTo minBound - --- | --- >>> enumerateFromBounded from = Stream.enumerateFromTo from maxBound --- --- 'enumerateFrom' for 'Bounded' 'Enum' types. --- -{-# INLINE enumerateFromBounded #-} -enumerateFromBounded :: (Monad m, Enumerable a, Bounded a) - => a -> Stream m a -enumerateFromBounded from = enumerateFromTo from maxBound - -------------------------------------------------------------------------------- --- Enumerable Instances -------------------------------------------------------------------------------- --- --- For Enum types smaller than or equal to Int size. -#define ENUMERABLE_BOUNDED_SMALL(SMALL_TYPE) \ -instance Enumerable SMALL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromBounded; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenSmallBounded; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToSmall; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToSmall } - -ENUMERABLE_BOUNDED_SMALL(()) -ENUMERABLE_BOUNDED_SMALL(Bool) -ENUMERABLE_BOUNDED_SMALL(Ordering) -ENUMERABLE_BOUNDED_SMALL(Char) - --- For bounded Integral Enum types, may be larger than Int. -#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ -instance Enumerable INTEGRAL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegral; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegral; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegral; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegral } - -ENUMERABLE_BOUNDED_INTEGRAL(Int) -ENUMERABLE_BOUNDED_INTEGRAL(Int8) -ENUMERABLE_BOUNDED_INTEGRAL(Int16) -ENUMERABLE_BOUNDED_INTEGRAL(Int32) -ENUMERABLE_BOUNDED_INTEGRAL(Int64) -ENUMERABLE_BOUNDED_INTEGRAL(Word) -ENUMERABLE_BOUNDED_INTEGRAL(Word8) -ENUMERABLE_BOUNDED_INTEGRAL(Word16) -ENUMERABLE_BOUNDED_INTEGRAL(Word32) -ENUMERABLE_BOUNDED_INTEGRAL(Word64) - --- For unbounded Integral Enum types. -#define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \ -instance Enumerable INTEGRAL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom from = enumerateFromStepIntegral from 1; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen from next = \ - enumerateFromStepIntegral from (next - from); \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegral; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegral } - -ENUMERABLE_UNBOUNDED_INTEGRAL(Integer) -ENUMERABLE_UNBOUNDED_INTEGRAL(Natural) - -#define ENUMERABLE_FRACTIONAL(FRACTIONAL_TYPE,CONSTRAINT) \ -instance (CONSTRAINT) => Enumerable FRACTIONAL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromFractional; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenFractional; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToFractional; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToFractional } - -ENUMERABLE_FRACTIONAL(Float,) -ENUMERABLE_FRACTIONAL(Double,) -ENUMERABLE_FRACTIONAL((Fixed a),HasResolution a) -ENUMERABLE_FRACTIONAL((Ratio a),Integral a) - -instance Enumerable a => Enumerable (Identity a) where - {-# INLINE enumerateFrom #-} - enumerateFrom (Identity from) = - fmap Identity $ enumerateFrom from - {-# INLINE enumerateFromThen #-} - enumerateFromThen (Identity from) (Identity next) = - fmap Identity $ enumerateFromThen from next - {-# INLINE enumerateFromTo #-} - enumerateFromTo (Identity from) (Identity to) = - fmap Identity $ enumerateFromTo from to - {-# INLINE enumerateFromThenTo #-} - enumerateFromThenTo (Identity from) (Identity next) (Identity to) = - fmap Identity - $ enumerateFromThenTo from next to - --- TODO -{- -instance Enumerable a => Enumerable (Last a) -instance Enumerable a => Enumerable (First a) -instance Enumerable a => Enumerable (Max a) -instance Enumerable a => Enumerable (Min a) -instance Enumerable a => Enumerable (Const a b) -instance Enumerable (f a) => Enumerable (Alt f a) -instance Enumerable (f a) => Enumerable (Ap f a) --} ------------------------------------------------------------------------------ -- Time Enumeration ------------------------------------------------------------------------------ diff --git a/core/streamly-core.cabal b/core/streamly-core.cabal index 40828f85c2..ea18dd1182 100644 --- a/core/streamly-core.cabal +++ b/core/streamly-core.cabal @@ -510,6 +510,7 @@ library , Streamly.Internal.Data.Stream.Container , Streamly.Internal.Data.Stream.Eliminate + , Streamly.Internal.Data.Stream.Enumeration , Streamly.Internal.Data.Stream.Exception , Streamly.Internal.Data.Stream.Generate , Streamly.Internal.Data.Stream.Lift From 3a2ae669931295e861ee7bfba89bd2626d774de0 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 16:57:13 +0530 Subject: [PATCH 13/23] Synchronize Stream and Unfold Enumeration modules --- core/src/Streamly/Internal/Data/Parser.hs | 2 +- .../Internal/Data/Stream/Enumeration.hs | 177 +++++++++++------- .../Internal/Data/Unfold/Enumeration.hs | 98 +++++----- 3 files changed, 163 insertions(+), 114 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Parser.hs b/core/src/Streamly/Internal/Data/Parser.hs index ef01b87ff3..e42ae179ce 100644 --- a/core/src/Streamly/Internal/Data/Parser.hs +++ b/core/src/Streamly/Internal/Data/Parser.hs @@ -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.enumerateFromIntegralBounded 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 diff --git a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs index 57e51dc7a9..d4c07a919b 100644 --- a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -7,50 +7,73 @@ -- Stability : experimental -- Portability : GHC -- +-- NOTE: keep this module in sync with the +-- Streamly.Internal.Data.Unfold.Enumeration.hs module. +-- module Streamly.Internal.Data.Stream.Enumeration ( -- NOTE: We enumerate up variants only, we can add enumerateDown* variants -- as well, to enumerate downwards to the final value. Currently that is -- achieved by enumerateFromThen variants. - -- ** Enumerating 'Num' Types - enumerateFromStepNum + -- ** Enumerable Type Class + Enumerable (..) + + -- ** 'Num' Type class Types + -- | Most general operations via the 'Num' type class. All other + -- enumeraitons can be expressed in terms of these. These are numerically + -- stable for floating precision numbers. For that reason these may be + -- slightly less efficient than intergal operations. + -- + -- These are unbounded, can overflow, there are no bounds possible for + -- 'Num'. + , enumerateFromStepNum , enumerateFromNum , enumerateFromThenNum - -- ** Enumerating 'Bounded' 'Enum' Types + -- ** 'Integral' Type class Types (Unbounded Enumeration) + -- | More efficient than 'Num' based operations for 'Integral' types. + , enumerateFromStepIntegral + , enumerateFromIntegralUnbounded + , enumerateFromThenIntegralUnbounded + , enumerateFromToIntegral + , enumerateFromThenToIntegral + + -- ** 'Integral' Types (Bounded Enumeration) + -- | These are implemented in terms of integral operations using maxBound + -- or minBound as the terminating codition. + , enumerateFromIntegralBounded + , enumerateFromThenIntegralBounded + -- Convenient functions for 'Bounded' Types , enumerate , enumerateTo , enumerateFromBounded - -- ** Enumerating 'Enum' Types not larger than 'Int' + -- ** 'Enum' Types not larger than 'Int' + -- | These are implemented by converting Enum to Int and using integral + -- operations. , enumerateFromToSmall , enumerateFromThenToSmall , enumerateFromThenSmallBounded - -- ** Enumerating 'Bounded' 'Integral' Types - , enumerateFromIntegral - , enumerateFromThenIntegral - - -- ** Enumerating 'Integral' Types - , enumerateFromToIntegral - , enumerateFromThenToIntegral - - -- ** Enumerating unbounded 'Integral' Types - , enumerateFromStepIntegral - - -- ** Enumerating 'Fractional' Types + -- ** 'Fractional' Types + -- | These are simply specialization of Num based operations to Fractional + -- types. , enumerateFromFractional , enumerateFromToFractional , enumerateFromThenFractional , enumerateFromThenToFractional - -- ** Enumerable Type Class - , Enumerable(..) + -- * Deprecated + -- XXX These are bounded integral types, should be removed and then + -- unbounded ones should be renamed to these. + , enumerateFromIntegral + , enumerateFromThenIntegral ) where #include "inline.hs" +#include "deprecation.h" import Data.Fixed import Data.Functor.Identity (Identity(..)) @@ -58,7 +81,6 @@ import Data.Int import Data.Ratio import Data.Word import Numeric.Natural -import Prelude hiding (takeWhile) import Streamly.Internal.Data.Stream.Type #ifdef USE_UNFOLDS_EVERYWHERE @@ -66,6 +88,7 @@ import qualified Streamly.Internal.Data.Unfold as Unfold #else import qualified Streamly.Internal.Data.Producer as Producer #endif +import Prelude hiding (takeWhile) #include "DocTestDataStream.hs" @@ -94,18 +117,42 @@ enumerateFromStepNum !from !stride = Stream (const Producer.enumerateFromStepNum) (from, stride, 0) #endif -{-# INLINE_NORMAL enumerateFromNum #-} -enumerateFromNum :: (Monad m, Num a) => a -> Stream m a -enumerateFromNum from = enumerateFromStepNum from 1 - {-# INLINE_NORMAL enumerateFromThenNum #-} enumerateFromThenNum :: (Monad m, Num a) => a -> a -> Stream m a enumerateFromThenNum from next = enumerateFromStepNum from (next - from) +{-# INLINE_NORMAL enumerateFromNum #-} +enumerateFromNum :: (Monad m, Num a) => a -> Stream m a +enumerateFromNum from = enumerateFromStepNum from 1 + ------------------------------------------------------------------------------ -- Enumeration of Integrals ------------------------------------------------------------------------------ +-- | @enumerateFromStepIntegral from step@ generates an infinite stream whose +-- first element is @from@ and the successive elements are in increments of +-- @step@. +-- +-- CAUTION: This function is not safe for finite integral types. It does not +-- check for overflow, underflow or bounds. +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2 +-- [0,2,4,6] +-- +-- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2) +-- [0,-2,-4] +-- +{-# INLINE_NORMAL enumerateFromStepIntegral #-} +enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a +#ifdef USE_UNFOLDS_EVERYWHERE +enumerateFromStepIntegral from stride = + unfold Unfold.enumerateFromStepIntegral (from, stride) +#else +enumerateFromStepIntegral from stride = + from `seq` stride `seq` + Stream (const Producer.enumerateFromStepIntegral) (from, stride) +#endif + -- | Enumerate an 'Integral' type in steps up to a given limit. -- @enumerateFromThenToIntegral from then to@ generates a finite stream whose -- first element is @from@, the second element is @then@ and the successive @@ -142,42 +189,20 @@ enumerateFromThenToIntegral from next to = -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2) -- [0,-2,-4,-6] -- -{-# INLINE_NORMAL enumerateFromThenIntegral #-} -enumerateFromThenIntegral +{-# INLINE_NORMAL enumerateFromThenIntegralBounded #-} +enumerateFromThenIntegralBounded, enumerateFromThenIntegral :: (Monad m, Integral a, Bounded a) => a -> a -> Stream m a #ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromThenIntegral from next = +enumerateFromThenIntegralBounded from next = unfold Unfold.enumerateFromThenIntegralBounded (from, next) #else -enumerateFromThenIntegral from next = +enumerateFromThenIntegralBounded from next = enumerateFromThenToIntegral from next (if next >= from then maxBound else minBound) #endif --- | @enumerateFromStepIntegral from step@ generates an infinite stream whose --- first element is @from@ and the successive elements are in increments of --- @step@. --- --- CAUTION: This function is not safe for finite integral types. It does not --- check for overflow, underflow or bounds. --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2 --- [0,2,4,6] --- --- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2) --- [0,-2,-4] --- -{-# INLINE_NORMAL enumerateFromStepIntegral #-} -enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromStepIntegral from stride = - unfold Unfold.enumerateFromStepIntegral (from, stride) -#else -enumerateFromStepIntegral from stride = - from `seq` stride `seq` - Stream (const Producer.enumerateFromStepIntegral) (from, stride) -#endif +RENAME(enumerateFromThenIntegral,enumerateFromThenIntegralBounded) -- | Enumerate an 'Integral' type up to a given limit. -- @enumerateFromToIntegral from to@ generates a finite stream whose first @@ -199,9 +224,23 @@ enumerateFromToIntegral from to = -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int) -- [0,1,2,3] -- -{-# INLINE enumerateFromIntegral #-} -enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a -enumerateFromIntegral from = enumerateFromToIntegral from maxBound +{-# INLINE enumerateFromIntegralBounded #-} +enumerateFromIntegralBounded, enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a +enumerateFromIntegralBounded from = enumerateFromToIntegral from maxBound + +RENAME(enumerateFromIntegral,enumerateFromIntegralBounded) + +-- XXX Drop the unbounded suffix and export when the conflicting deprecated +-- functions are removed. + +{-# INLINE enumerateFromIntegralUnbounded #-} +enumerateFromIntegralUnbounded :: (Integral a, Monad m) => a -> Stream m a +enumerateFromIntegralUnbounded from = enumerateFromStepIntegral from 1 + +{-# INLINE enumerateFromThenIntegralUnbounded #-} +enumerateFromThenIntegralUnbounded :: (Integral a, Monad m) => a -> a -> Stream m a +enumerateFromThenIntegralUnbounded from next = + enumerateFromStepIntegral from (next - from) ------------------------------------------------------------------------------ -- Enumeration of Fractionals @@ -322,6 +361,10 @@ enumerateFromThenToSmall from next to = $ enumerateFromThenToIntegral (fromEnum from) (fromEnum next) (fromEnum to) +------------------------------------------------------------------------------- +-- Bounded Enumeration of Enum types not larger than Int +------------------------------------------------------------------------------- + -- | 'enumerateFromThen' for 'Enum' types not larger than 'Int'. -- -- Note: We convert the 'Enum' to 'Int' and enumerate the 'Int'. If a @@ -357,6 +400,7 @@ enumerateFromThenSmallBounded from next = -- "Streamly.Internal.Data.Stream.Enumeration" module to define new instances. -- class Enum a => Enumerable a where + -- | @enumerateFrom from@ generates a stream starting with the element -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or -- generating an infinite stream when the type is not 'Bounded'. @@ -370,7 +414,7 @@ class Enum a => Enumerable a where -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFrom 1.1 -- [1.1,2.1,3.1,4.1] -- - enumerateFrom :: (Monad m) => a -> Stream m a + enumerateFrom :: Monad m => a -> Stream m a -- | Generate a finite stream starting with the element @from@, enumerating -- the type up to the value @to@. If @to@ is smaller than @from@ then an @@ -484,15 +528,15 @@ ENUMERABLE_BOUNDED_SMALL(Ordering) ENUMERABLE_BOUNDED_SMALL(Char) -- For bounded Integral Enum types, may be larger than Int. -#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ -instance Enumerable INTEGRAL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegral; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegral; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegral; \ - {-# INLINE enumerateFromThenTo #-}; \ +#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ +instance Enumerable INTEGRAL_TYPE where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom = enumerateFromIntegralBounded; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenIntegralBounded; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToIntegral; \ + {-# INLINE enumerateFromThenTo #-}; \ enumerateFromThenTo = enumerateFromThenToIntegral } ENUMERABLE_BOUNDED_INTEGRAL(Int) @@ -510,10 +554,9 @@ ENUMERABLE_BOUNDED_INTEGRAL(Word64) #define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \ instance Enumerable INTEGRAL_TYPE where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom from = enumerateFromStepIntegral from 1; \ + enumerateFrom = enumerateFromIntegralUnbounded; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen from next = \ - enumerateFromStepIntegral from (next - from); \ + enumerateFromThen = enumerateFromThenIntegralUnbounded; \ {-# INLINE enumerateFromTo #-}; \ enumerateFromTo = enumerateFromToIntegral; \ {-# INLINE enumerateFromThenTo #-}; \ @@ -523,7 +566,7 @@ ENUMERABLE_UNBOUNDED_INTEGRAL(Integer) ENUMERABLE_UNBOUNDED_INTEGRAL(Natural) #define ENUMERABLE_FRACTIONAL(FRACTIONAL_TYPE,CONSTRAINT) \ -instance (CONSTRAINT) => Enumerable FRACTIONAL_TYPE where { \ +instance (CONSTRAINT) => Enumerable FRACTIONAL_TYPE where { \ {-# INLINE enumerateFrom #-}; \ enumerateFrom = enumerateFromFractional; \ {-# INLINE enumerateFromThen #-}; \ diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index eb307076d8..9ba4bcf16f 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -6,6 +6,9 @@ -- Stability : experimental -- Portability : GHC -- +-- NOTE: keep this module in sync with the +-- Streamly.Internal.Data.Stream.Enumeration.hs module. +-- -- The functions defined in this module should be rarely needed for direct use, -- try to use the operations from the 'Enumerable' type class -- instances instead. @@ -21,43 +24,56 @@ -- module Streamly.Internal.Data.Unfold.Enumeration ( + -- ** Enumerable Type Class Enumerable (..) - -- ** Enumerating 'Num' Types + -- ** 'Num' Type class Types + -- | Most general operations via the 'Num' type class. All other + -- enumeraitons can be expressed in terms of these. These are numerically + -- stable for floating precision numbers. For that reason these may be + -- slightly less efficient than intergal operations. , enumerateFromStepNum , enumerateFromNum , enumerateFromThenNum - -- ** Enumerating unbounded 'Integral' Types + -- ** 'Integral' Type class Types (Unbounded Enumeration) + -- | More efficient than 'Num' based operations for 'Integral' types. , enumerateFromStepIntegral , enumerateFromIntegral , enumerateFromThenIntegral , enumerateFromToIntegral , enumerateFromThenToIntegral - -- ** Enumerating 'Bounded' 'Integral' Types + -- ** 'Integral' Types (Bounded Enumeration) + -- | These are implemented in terms of integral operations using maxBound + -- or minBound as the terminating codition. , enumerateFromIntegralBounded , enumerateFromThenIntegralBounded - , enumerateFromToIntegralBounded - , enumerateFromThenToIntegralBounded - -- ** Enumerating small 'Integral' Types - -- | Small types are always bounded. - , enumerateFromSmallBounded - , enumerateFromThenSmallBounded + -- ** 'Enum' Types not larger than 'Int' + -- | These are implemented by converting Enum to Int and using integral + -- operations. , enumerateFromToSmall , enumerateFromThenToSmall + , enumerateFromSmallBounded + , enumerateFromThenSmallBounded - -- ** Enumerating 'Fractional' Types - -- | Enumeration of 'Num' specialized to 'Fractional' types. + -- ** 'Fractional' Types + -- | These are simply specialization of Num based operations to Fractional + -- types. , enumerateFromFractional - , enumerateFromThenFractional , enumerateFromToFractional + , enumerateFromThenFractional , enumerateFromThenToFractional + + -- * Deprecated + , enumerateFromToIntegralBounded + , enumerateFromThenToIntegralBounded ) where #include "inline.hs" + import Data.Fixed import Data.Bifunctor (bimap) import Data.Int @@ -67,9 +83,7 @@ import Numeric.Natural import Data.Functor.Identity (Identity(..)) import Streamly.Internal.Data.Unfold.Type hiding (takeWhileMWithInput) import qualified Streamly.Internal.Data.Producer as Producer -import Prelude - hiding (map, mapM, takeWhile, take, filter, const, zipWith - , drop, dropWhile) +import Prelude hiding (map, takeWhile, zipWith) -- $setup -- >>> :m @@ -185,25 +199,6 @@ enumerateFromStepIntegral = Unfold Producer.enumerateFromStepIntegral inject inject (from, stride) = from `seq` stride `seq` return (from, stride) --- Enumerate Unbounded Integrals ---------------------------------------------- -{-# INLINE enumerateFromIntegral #-} -enumerateFromIntegral :: (Monad m, Integral a) => Unfold m a a -enumerateFromIntegral = lmap (\from -> (from, 1)) enumerateFromStepIntegral - -{-# INLINE enumerateFromThenIntegral #-} -enumerateFromThenIntegral :: (Monad m, Integral a ) => Unfold m (a, a) a -enumerateFromThenIntegral = - lmap (\(from, next) -> (from, next - from)) enumerateFromStepIntegral - -{-# INLINE enumerateFromToIntegral #-} -enumerateFromToIntegral :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromToIntegral = - map snd - $ takeWhile (\((_, to), b) -> b <= to) - $ takeEndBy (\((_, to), b) -> b == to) - $ carryInput - $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegral - {-# INLINE enumerateFromThenToIntegral #-} enumerateFromThenToIntegral :: (Monad m, Integral a) => Unfold m (a, a, a) a enumerateFromThenToIntegral = @@ -213,12 +208,6 @@ enumerateFromThenToIntegral = inject (from, next, to) = return (Producer.EnumInit from next to) --- Enumerate Bounded Integrals ------------------------------------------------ -{-# INLINE enumerateFromIntegralBounded #-} -enumerateFromIntegralBounded :: (Monad m, Integral a, Bounded a) => - Unfold m a a -enumerateFromIntegralBounded = supplySecond maxBound enumerateFromToIntegral - {-# INLINE enumerateFromThenIntegralBounded #-} enumerateFromThenIntegralBounded :: (Monad m, Integral a, Bounded a ) => Unfold m (a, a) a @@ -230,6 +219,29 @@ enumerateFromThenIntegralBounded = toFromThenTo (from, next) = (from, next, if next >= from then maxBound else minBound) +{-# INLINE enumerateFromToIntegral #-} +enumerateFromToIntegral :: (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromToIntegral = + map snd + $ takeWhile (\((_, to), b) -> b <= to) + $ takeEndBy (\((_, to), b) -> b == to) + $ carryInput + $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegral + +{-# INLINE enumerateFromIntegralBounded #-} +enumerateFromIntegralBounded :: (Monad m, Integral a, Bounded a) => + Unfold m a a +enumerateFromIntegralBounded = supplySecond maxBound enumerateFromToIntegral + +{-# INLINE enumerateFromIntegral #-} +enumerateFromIntegral :: (Monad m, Integral a) => Unfold m a a +enumerateFromIntegral = lmap (\from -> (from, 1)) enumerateFromStepIntegral + +{-# INLINE enumerateFromThenIntegral #-} +enumerateFromThenIntegral :: (Monad m, Integral a ) => Unfold m (a, a) a +enumerateFromThenIntegral = + lmap (\(from, next) -> (from, next - from)) enumerateFromStepIntegral + {-# DEPRECATED enumerateFromToIntegralBounded "Use enumerateFromToIntegral instead." #-} {-# INLINE enumerateFromToIntegralBounded #-} enumerateFromToIntegralBounded :: (Monad m, Integral a) => @@ -356,7 +368,6 @@ enumerateFromThenSmallBounded = -- generate a stream instead of a list. Use the functions in -- "Streamly.Internal.Data.Unfold.Enumeration" module to define new instances. -- --- /Pre-release/ class Enum a => Enumerable a where -- | Unfolds @from@ generating a stream starting with the element @@ -372,8 +383,6 @@ class Enum a => Enumerable a where -- >>> Stream.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFrom 1.1 -- [1.1,2.1,3.1,4.1] -- - -- /Pre-release/ - -- enumerateFrom :: Monad m => Unfold m a a -- | Unfolds @(from, to)@ generating a finite stream starting with the element @@ -392,7 +401,6 @@ class Enum a => Enumerable a where -- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromTo (1.1, 4.6) -- [1.1,2.1,3.1,4.1,5.1] -- - -- /Pre-release/ enumerateFromTo :: Monad m => Unfold m (a, a) a -- | Unfolds @(from, then)@ generating a stream whose first element is @@ -407,7 +415,6 @@ class Enum a => Enumerable a where -- >>> Stream.toList $ Stream.take 4 $ Stream.unfold Unfold.enumerateFromThen (0,(-2)) -- [0,-2,-4,-6] -- - -- /Pre-release/ enumerateFromThen :: Monad m => Unfold m (a, a) a -- | Unfolds @(from, then, to)@ generating a finite stream whose first element @@ -421,7 +428,6 @@ class Enum a => Enumerable a where -- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromThenTo (0, (-2), (-6)) -- [0,-2,-4,-6] -- - -- /Pre-release/ enumerateFromThenTo :: Monad m => Unfold m (a, a, a) a ------------------------------------------------------------------------------- From e1e242ee4335007a2751c8ad1b6cb088f3f7fbd3 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 17:46:19 +0530 Subject: [PATCH 14/23] Fix enumerateFromTo Unfold --- core/docs/Changelog.md | 2 + .../Internal/Data/Unfold/Enumeration.hs | 2 +- test/Streamly/Test/Data/Stream/Generate.hs | 85 +++++++++++++ test/Streamly/Test/Data/Unfold.hs | 113 ++++++++++++++++++ 4 files changed, 201 insertions(+), 1 deletion(-) diff --git a/core/docs/Changelog.md b/core/docs/Changelog.md index 19f7ce5e31..5578b0208e 100644 --- a/core/docs/Changelog.md +++ b/core/docs/Changelog.md @@ -13,6 +13,8 @@ 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 diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 9ba4bcf16f..5eccfd1cc9 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -514,7 +514,7 @@ instance Enumerable a => Enumerable (Identity a) where map Identity $ lmap (bimap runIdentity runIdentity) enumerateFromThen {-# INLINE enumerateFromTo #-} enumerateFromTo = - map Identity $ lmap (bimap runIdentity runIdentity) enumerateFromThen + map Identity $ lmap (bimap runIdentity runIdentity) enumerateFromTo {-# INLINE enumerateFromThenTo #-} enumerateFromThenTo = map Identity $ diff --git a/test/Streamly/Test/Data/Stream/Generate.hs b/test/Streamly/Test/Data/Stream/Generate.hs index 5b08d8a2d9..163d2d3f37 100644 --- a/test/Streamly/Test/Data/Stream/Generate.hs +++ b/test/Streamly/Test/Data/Stream/Generate.hs @@ -9,6 +9,7 @@ module Streamly.Test.Data.Stream.Generate (main) where +import Data.Functor.Identity (Identity(..)) import Data.IORef (newIORef, readIORef, writeIORef) import Data.Int (Int8) import Data.Word (Word8, Word16) @@ -243,6 +244,78 @@ testEnumerateFromToFractional = toList (Stream.enumerateFromToFractional (1.1 :: Double) 4.0) `shouldReturn` [1.1, 2.1, 3.1, 4.1] +------------------------------------------------------------------------------- +-- Enumerable type class dispatch +-- +-- The tests above exercise the concrete per-type functions (e.g. +-- Stream.enumerateFromToIntegral) directly. The tests below instead go +-- through the polymorphic 'Enumerable' class methods (Stream.enumerateFrom, +-- Stream.enumerateFromTo, Stream.enumerateFromThen, +-- Stream.enumerateFromThenTo) so that a bug in how a particular instance +-- wires the class methods to the underlying functions (e.g. enumerateFromTo +-- accidentally calling enumerateFromThen) is actually caught. The +-- 'Identity' instance is hand-written rather than macro generated and is +-- therefore particularly prone to such copy-paste mistakes. +------------------------------------------------------------------------------- + +testEnumerableFromInt :: Expectation +testEnumerableFromInt = + toList (Stream.take 5 (Stream.enumerateFrom (0 :: Int))) + `shouldReturn` [0, 1, 2, 3, 4] + +testEnumerableFromToInt :: Expectation +testEnumerableFromToInt = + toList (Stream.enumerateFromTo (0 :: Int) 4) + `shouldReturn` [0, 1, 2, 3, 4] + +testEnumerableFromThenInt :: Expectation +testEnumerableFromThenInt = + toList (Stream.take 4 (Stream.enumerateFromThen (0 :: Int) 2)) + `shouldReturn` [0, 2, 4, 6] + +testEnumerableFromThenToInt :: Expectation +testEnumerableFromThenToInt = + toList (Stream.enumerateFromThenTo (0 :: Int) 2 6) + `shouldReturn` [0, 2, 4, 6] + +testEnumerableFromToChar :: Expectation +testEnumerableFromToChar = + toList (Stream.enumerateFromTo 'a' 'e') + `shouldReturn` "abcde" + +testEnumerableFromToDouble :: Expectation +testEnumerableFromToDouble = + toList (Stream.enumerateFromTo (1.1 :: Double) 4.0) + `shouldReturn` [1.1, 2.1, 3.1, 4.1] + +-- Regression test: the Identity Enumerable instance's enumerateFromTo in +-- the Unfold.Enumeration module was once wired to enumerateFromThen instead +-- of enumerateFromTo. Stream.Enumeration's instance was not affected, but +-- we test it here too so both modules stay covered symmetrically. +testEnumerableFromIdentity :: Expectation +testEnumerableFromIdentity = + toList (Stream.take 4 (Stream.enumerateFrom (Identity (0 :: Int)))) + `shouldReturn` fmap Identity [0, 1, 2, 3] + +testEnumerableFromToIdentity :: Expectation +testEnumerableFromToIdentity = + toList (Stream.enumerateFromTo (Identity (0 :: Int)) (Identity 4)) + `shouldReturn` fmap Identity [0, 1, 2, 3, 4] + +testEnumerableFromThenIdentity :: Expectation +testEnumerableFromThenIdentity = + toList + (Stream.take 4 + (Stream.enumerateFromThen (Identity (0 :: Int)) (Identity 2))) + `shouldReturn` fmap Identity [0, 2, 4, 6] + +testEnumerableFromThenToIdentity :: Expectation +testEnumerableFromThenToIdentity = + toList + (Stream.enumerateFromThenTo + (Identity (0 :: Int)) (Identity 2) (Identity 6)) + `shouldReturn` fmap Identity [0, 2, 4, 6] + ------------------------------------------------------------------------------- -- Overflow at the bound of a fixed-size Integral type -- @@ -370,6 +443,18 @@ main = hspec $ describe moduleName $ do it "enumerateFromFractional" testEnumerateFromFractional it "enumerateFromToFractional" testEnumerateFromToFractional + describe "Enumerable type class dispatch" $ do + it "enumerateFrom Int" testEnumerableFromInt + it "enumerateFromTo Int" testEnumerableFromToInt + it "enumerateFromThen Int" testEnumerableFromThenInt + it "enumerateFromThenTo Int" testEnumerableFromThenToInt + it "enumerateFromTo Char" testEnumerableFromToChar + it "enumerateFromTo Double" testEnumerableFromToDouble + it "enumerateFrom Identity" testEnumerableFromIdentity + it "enumerateFromTo Identity" testEnumerableFromToIdentity + it "enumerateFromThen Identity" testEnumerableFromThenIdentity + it "enumerateFromThenTo Identity" testEnumerableFromThenToIdentity + describe "Enumeration overflow at type bound" $ do it "enumerateFromToIntegral overflow" testEnumerateFromToIntegralOverflow it "enumerateFromIntegral overflow" testEnumerateFromIntegralOverflow diff --git a/test/Streamly/Test/Data/Unfold.hs b/test/Streamly/Test/Data/Unfold.hs index 4222eafb6e..ae770a994d 100644 --- a/test/Streamly/Test/Data/Unfold.hs +++ b/test/Streamly/Test/Data/Unfold.hs @@ -496,6 +496,108 @@ enumerateFromToFractional = let unf = UF.enumerateFromToFractional in testUnfold unf (f :: Double, t) [f..(t :: Double)] +------------------------------------------------------------------------------- +-- Enumerable type class dispatch +-- +-- The tests above exercise the concrete per-type functions (e.g. +-- UF.enumerateFromToIntegral) directly. The tests below instead go through +-- the polymorphic 'Enumerable' class methods (UF.enumerateFrom, +-- UF.enumerateFromTo, UF.enumerateFromThen, UF.enumerateFromThenTo) so that +-- a bug in how a particular instance wires the class methods to the +-- underlying functions (e.g. enumerateFromTo accidentally calling +-- enumerateFromThen) is actually caught. The 'Identity' instance is +-- hand-written rather than macro generated and is therefore particularly +-- prone to such copy-paste mistakes. +------------------------------------------------------------------------------- + +enumerableFromInt :: Property +enumerableFromInt = + property + $ \f -> + let unf = UF.take 50 UF.enumerateFrom + in testUnfold unf (f :: Int) $ + Prelude.take 50 $ Prelude.enumFrom f + +enumerableFromToInt :: Property +enumerableFromToInt = + property + $ \f to -> + let unf = UF.take 50 UF.enumerateFromTo + in testUnfold unf (f :: Int, to) $ + Prelude.take 50 $ Prelude.enumFromTo f to + +enumerableFromThenInt :: Property +enumerableFromThenInt = + property + $ \f th -> + let unf = UF.take 50 UF.enumerateFromThen + in testUnfold unf (f :: Int, th) $ + Prelude.take 50 $ Prelude.enumFromThen f th + +enumerableFromThenToInt :: Property +enumerableFromThenToInt = + property + $ \f th to -> + let unf = UF.take 50 UF.enumerateFromThenTo + in testUnfold unf (f :: Int, th, to) $ + Prelude.take 50 $ Prelude.enumFromThenTo f th to + +enumerableFromToChar :: Property +enumerableFromToChar = + property + $ \f to -> + let unf = UF.take 50 UF.enumerateFromTo + in testUnfold unf (f :: Char, to) $ + Prelude.take 50 $ Prelude.enumFromTo f to + +enumerableFromToDouble :: Property +enumerableFromToDouble = + property + $ \f to -> + let unf = UF.take 50 UF.enumerateFromTo + in testUnfold unf (f :: Double, to) $ + Prelude.take 50 $ Prelude.enumFromTo f to + +-- Regression test: the Identity Enumerable instance's enumerateFromTo was +-- once wired to enumerateFromThen instead of enumerateFromTo, so this would +-- have failed (or hung, since enumerateFromThen never stops at "to"). +enumerableFromIdentity :: Property +enumerableFromIdentity = + property + $ \f -> + let unf = UF.take 50 UF.enumerateFrom + in testUnfold unf (Identity (f :: Int)) $ + Prelude.map Identity $ Prelude.take 50 $ Prelude.enumFrom f + +enumerableFromToIdentity :: Property +enumerableFromToIdentity = + property + $ \f to -> + let unf = UF.take 50 UF.enumerateFromTo + in testUnfold unf (Identity (f :: Int), Identity to) $ + Prelude.map Identity $ + Prelude.take 50 $ Prelude.enumFromTo f to + +enumerableFromThenIdentity :: Property +enumerableFromThenIdentity = + property + $ \f th -> + let unf = UF.take 50 UF.enumerateFromThen + in testUnfold unf (Identity (f :: Int), Identity th) $ + Prelude.map Identity $ + Prelude.take 50 $ Prelude.enumFromThen f th + +enumerableFromThenToIdentity :: Property +enumerableFromThenToIdentity = + property + $ \f th to -> + let unf = UF.take 50 UF.enumerateFromThenTo + in testUnfold + unf + (Identity (f :: Int), Identity th, Identity to) $ + Prelude.map Identity $ + Prelude.take 50 $ Prelude.enumFromThenTo f th to + ------------------------------------------------------------------------------- -- Overflow at the bound of a fixed-size Integral type -- @@ -1168,6 +1270,17 @@ testGeneration = prop "enumerateFromThenFractional" enumerateFromThenFractional prop "enumerateFromToFractional" enumerateFromToFractional prop "enumerateFromThenToFractional" enumerateFromThenToFractional + ----------- Enumerable type class dispatch ------------------------- + prop "enumerableFromInt" enumerableFromInt + prop "enumerableFromToInt" enumerableFromToInt + prop "enumerableFromThenInt" enumerableFromThenInt + prop "enumerableFromThenToInt" enumerableFromThenToInt + prop "enumerableFromToChar" enumerableFromToChar + prop "enumerableFromToDouble" enumerableFromToDouble + prop "enumerableFromIdentity" enumerableFromIdentity + prop "enumerableFromToIdentity" enumerableFromToIdentity + prop "enumerableFromThenIdentity" enumerableFromThenIdentity + prop "enumerableFromThenToIdentity" enumerableFromThenToIdentity testTransformation :: Spec testTransformation = From ca70b338fdbe308f7122f3a932879a0998dc97ab Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Thu, 2 Jul 2026 17:54:36 +0530 Subject: [PATCH 15/23] Make bounded as default in Stream Enumeration Remove Enumerable constraint from enumerateFromThenSmallBounded --- core/src/Streamly/Internal/Data/Parser.hs | 2 +- .../Internal/Data/Stream/Enumeration.hs | 131 ++++++++++-------- test/Streamly/Test/Data/Stream/Generate.hs | 23 ++- 3 files changed, 81 insertions(+), 75 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Parser.hs b/core/src/Streamly/Internal/Data/Parser.hs index e42ae179ce..ef01b87ff3 100644 --- a/core/src/Streamly/Internal/Data/Parser.hs +++ b/core/src/Streamly/Internal/Data/Parser.hs @@ -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.enumerateFromIntegralBounded 0 :: D.Stream m Int) +indexed = zip (D.enumerateFromIntegral 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 diff --git a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs index d4c07a919b..37501c7aea 100644 --- a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -33,28 +33,28 @@ module Streamly.Internal.Data.Stream.Enumeration -- ** 'Integral' Type class Types (Unbounded Enumeration) -- | More efficient than 'Num' based operations for 'Integral' types. - , enumerateFromStepIntegral + , enumerateFromStepIntegralUnbounded , enumerateFromIntegralUnbounded , enumerateFromThenIntegralUnbounded - , enumerateFromToIntegral - , enumerateFromThenToIntegral -- ** 'Integral' Types (Bounded Enumeration) -- | These are implemented in terms of integral operations using maxBound - -- or minBound as the terminating codition. - , enumerateFromIntegralBounded - , enumerateFromThenIntegralBounded - -- Convenient functions for 'Bounded' Types - , enumerate - , enumerateTo - , enumerateFromBounded + -- or minBound as the terminating codition. This is the default behavior + -- for 'Integral' enumeration, used when no 'Unbounded' suffix is + -- specified; use the explicit 'Unbounded' suffixed operations above for + -- faster, unchecked enumeration. + , enumerateFromIntegral + , enumerateFromToIntegral + , enumerateFromThenIntegral + , enumerateFromThenToIntegral -- ** 'Enum' Types not larger than 'Int' -- | These are implemented by converting Enum to Int and using integral -- operations. + , enumerateFromSmall , enumerateFromToSmall + , enumerateFromThenSmall , enumerateFromThenToSmall - , enumerateFromThenSmallBounded -- ** 'Fractional' Types -- | These are simply specialization of Num based operations to Fractional @@ -64,16 +64,18 @@ module Streamly.Internal.Data.Stream.Enumeration , enumerateFromThenFractional , enumerateFromThenToFractional + -- ** Convenient functions using 'Enumerable' type class + , enumerate + , enumerateTo + -- * Deprecated - -- XXX These are bounded integral types, should be removed and then - -- unbounded ones should be renamed to these. - , enumerateFromIntegral - , enumerateFromThenIntegral + , enumerateFromBounded + , enumerateFromThenSmallBounded + , enumerateFromStepIntegral ) where #include "inline.hs" -#include "deprecation.h" import Data.Fixed import Data.Functor.Identity (Identity(..)) @@ -106,7 +108,7 @@ import Prelude hiding (takeWhile) -- every time before adding it to the starting number. -- -- This works for Integrals as well as floating point numbers, but --- enumerateFromStepIntegral is faster for integrals. +-- enumerateFromStepIntegralUnbounded is faster for integrals. {-# INLINE_NORMAL enumerateFromStepNum #-} enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a #ifdef USE_UNFOLDS_EVERYWHERE @@ -129,30 +131,35 @@ enumerateFromNum from = enumerateFromStepNum from 1 -- Enumeration of Integrals ------------------------------------------------------------------------------ --- | @enumerateFromStepIntegral from step@ generates an infinite stream whose --- first element is @from@ and the successive elements are in increments of --- @step@. +-- | @enumerateFromStepIntegralUnbounded from step@ generates an infinite +-- stream whose first element is @from@ and the successive elements are in +-- increments of @step@. -- -- CAUTION: This function is not safe for finite integral types. It does not -- check for overflow, underflow or bounds. -- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegral 0 2 +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegralUnbounded 0 2 -- [0,2,4,6] -- --- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegral 0 (-2) +-- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegralUnbounded 0 (-2) -- [0,-2,-4] -- -{-# INLINE_NORMAL enumerateFromStepIntegral #-} -enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a +{-# INLINE_NORMAL enumerateFromStepIntegralUnbounded #-} +enumerateFromStepIntegralUnbounded :: (Integral a, Monad m) => a -> a -> Stream m a #ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromStepIntegral from stride = +enumerateFromStepIntegralUnbounded from stride = unfold Unfold.enumerateFromStepIntegral (from, stride) #else -enumerateFromStepIntegral from stride = +enumerateFromStepIntegralUnbounded from stride = from `seq` stride `seq` Stream (const Producer.enumerateFromStepIntegral) (from, stride) #endif +{-# DEPRECATED enumerateFromStepIntegral "Please use enumerateFromStepIntegralUnbounded instead." #-} +{-# INLINE enumerateFromStepIntegral #-} +enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a +enumerateFromStepIntegral = enumerateFromStepIntegralUnbounded + -- | Enumerate an 'Integral' type in steps up to a given limit. -- @enumerateFromThenToIntegral from then to@ generates a finite stream whose -- first element is @from@, the second element is @then@ and the successive @@ -189,21 +196,18 @@ enumerateFromThenToIntegral from next to = -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2) -- [0,-2,-4,-6] -- -{-# INLINE_NORMAL enumerateFromThenIntegralBounded #-} -enumerateFromThenIntegralBounded, enumerateFromThenIntegral - :: (Monad m, Integral a, Bounded a) +{-# INLINE_NORMAL enumerateFromThenIntegral #-} +enumerateFromThenIntegral :: (Monad m, Integral a, Bounded a) => a -> a -> Stream m a #ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromThenIntegralBounded from next = +enumerateFromThenIntegral from next = unfold Unfold.enumerateFromThenIntegralBounded (from, next) #else -enumerateFromThenIntegralBounded from next = +enumerateFromThenIntegral from next = enumerateFromThenToIntegral from next (if next >= from then maxBound else minBound) #endif -RENAME(enumerateFromThenIntegral,enumerateFromThenIntegralBounded) - -- | Enumerate an 'Integral' type up to a given limit. -- @enumerateFromToIntegral from to@ generates a finite stream whose first -- element is @from@ and successive elements are in increments of @1@ up to @@ -215,7 +219,8 @@ RENAME(enumerateFromThenIntegral,enumerateFromThenIntegralBounded) {-# INLINE enumerateFromToIntegral #-} enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a enumerateFromToIntegral from to = - takeWhile (<= to) $ takeEndBy (== to) $ enumerateFromStepIntegral from 1 + takeWhile (<= to) + $ takeEndBy (== to) $ enumerateFromStepIntegralUnbounded from 1 -- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a -- stream whose first element is @from@ and the successive elements are in @@ -224,23 +229,18 @@ enumerateFromToIntegral from to = -- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int) -- [0,1,2,3] -- -{-# INLINE enumerateFromIntegralBounded #-} -enumerateFromIntegralBounded, enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a -enumerateFromIntegralBounded from = enumerateFromToIntegral from maxBound - -RENAME(enumerateFromIntegral,enumerateFromIntegralBounded) - --- XXX Drop the unbounded suffix and export when the conflicting deprecated --- functions are removed. +{-# INLINE enumerateFromIntegral #-} +enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a +enumerateFromIntegral from = enumerateFromToIntegral from maxBound {-# INLINE enumerateFromIntegralUnbounded #-} enumerateFromIntegralUnbounded :: (Integral a, Monad m) => a -> Stream m a -enumerateFromIntegralUnbounded from = enumerateFromStepIntegral from 1 +enumerateFromIntegralUnbounded from = enumerateFromStepIntegralUnbounded from 1 {-# INLINE enumerateFromThenIntegralUnbounded #-} enumerateFromThenIntegralUnbounded :: (Integral a, Monad m) => a -> a -> Stream m a enumerateFromThenIntegralUnbounded from next = - enumerateFromStepIntegral from (next - from) + enumerateFromStepIntegralUnbounded from (next - from) ------------------------------------------------------------------------------ -- Enumeration of Fractionals @@ -373,13 +373,25 @@ enumerateFromThenToSmall from next to = -- of 'toEnum' when converting back to 'Enum'. Therefore we require a 'Bounded' -- instance for this function to be safely used. -- -{-# INLINE enumerateFromThenSmallBounded #-} -enumerateFromThenSmallBounded :: (Monad m, Enumerable a, Bounded a) +{-# INLINE enumerateFromThenSmall #-} +enumerateFromThenSmall :: (Monad m, Enum a, Bounded a) => a -> a -> Stream m a -enumerateFromThenSmallBounded from next = +enumerateFromThenSmall from next = if fromEnum next >= fromEnum from - then enumerateFromThenTo from next maxBound - else enumerateFromThenTo from next minBound + then enumerateFromThenToSmall from next maxBound + else enumerateFromThenToSmall from next minBound + +{-# DEPRECATED enumerateFromThenSmallBounded "Please use enumerateFromThenSmall instead." #-} +{-# INLINE enumerateFromThenSmallBounded #-} +enumerateFromThenSmallBounded :: (Monad m, Enum a, Bounded a) + => a -> a -> Stream m a +enumerateFromThenSmallBounded = enumerateFromThenSmall + +-- | 'enumerateFrom' for 'Enum' types not larger than 'Int'. +-- +{-# INLINE enumerateFromSmall #-} +enumerateFromSmall :: (Monad m, Enum a, Bounded a) => a -> Stream m a +enumerateFromSmall from = enumerateFromToSmall from maxBound ------------------------------------------------------------------------------- -- Enumerable type class @@ -496,15 +508,14 @@ enumerate = enumerateFrom minBound enumerateTo :: (Monad m, Bounded a, Enumerable a) => a -> Stream m a enumerateTo = enumerateFromTo minBound --- | --- >>> enumerateFromBounded from = Stream.enumerateFromTo from maxBound --- --- 'enumerateFrom' for 'Bounded' 'Enum' types. +-- | Same as 'enumerateFrom'. For a 'Bounded' type, 'enumerateFrom' is +-- already guaranteed to enumerate up to 'maxBound', so this function is +-- redundant. -- +{-# DEPRECATED enumerateFromBounded "Please use enumerateFrom instead." #-} {-# INLINE enumerateFromBounded #-} -enumerateFromBounded :: (Monad m, Enumerable a, Bounded a) - => a -> Stream m a -enumerateFromBounded from = enumerateFromTo from maxBound +enumerateFromBounded :: (Monad m, Enumerable a) => a -> Stream m a +enumerateFromBounded = enumerateFrom ------------------------------------------------------------------------------- -- Enumerable Instances @@ -514,9 +525,9 @@ enumerateFromBounded from = enumerateFromTo from maxBound #define ENUMERABLE_BOUNDED_SMALL(SMALL_TYPE) \ instance Enumerable SMALL_TYPE where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromBounded; \ + enumerateFrom = enumerateFromSmall; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenSmallBounded; \ + enumerateFromThen = enumerateFromThenSmall; \ {-# INLINE enumerateFromTo #-}; \ enumerateFromTo = enumerateFromToSmall; \ {-# INLINE enumerateFromThenTo #-}; \ @@ -531,9 +542,9 @@ ENUMERABLE_BOUNDED_SMALL(Char) #define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ instance Enumerable INTEGRAL_TYPE where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegralBounded; \ + enumerateFrom = enumerateFromIntegral; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegralBounded; \ + enumerateFromThen = enumerateFromThenIntegral; \ {-# INLINE enumerateFromTo #-}; \ enumerateFromTo = enumerateFromToIntegral; \ {-# INLINE enumerateFromThenTo #-}; \ diff --git a/test/Streamly/Test/Data/Stream/Generate.hs b/test/Streamly/Test/Data/Stream/Generate.hs index 163d2d3f37..84afa12959 100644 --- a/test/Streamly/Test/Data/Stream/Generate.hs +++ b/test/Streamly/Test/Data/Stream/Generate.hs @@ -157,11 +157,6 @@ testFromW16CStringEmpty = -- Enumeration primitives ------------------------------------------------------------------------------- -testEnumerateFromBounded :: Expectation -testEnumerateFromBounded = - toList (Stream.take 5 (Stream.enumerateFromBounded (0 :: Int))) - `shouldReturn` [0, 1, 2, 3, 4] - testEnumerateFromIntegral :: Expectation testEnumerateFromIntegral = toList (Stream.take 5 (Stream.enumerateFromIntegral (0 :: Int))) @@ -177,9 +172,10 @@ testEnumerateFromStepNum = toList (Stream.take 5 (Stream.enumerateFromStepNum (0 :: Int) 3)) `shouldReturn` [0, 3, 6, 9, 12] -testEnumerateFromStepIntegral :: Expectation -testEnumerateFromStepIntegral = - toList (Stream.take 5 (Stream.enumerateFromStepIntegral (0 :: Int) 2)) +testEnumerateFromStepIntegralUnbounded :: Expectation +testEnumerateFromStepIntegralUnbounded = + toList + (Stream.take 5 (Stream.enumerateFromStepIntegralUnbounded (0 :: Int) 2)) `shouldReturn` [0, 2, 4, 6, 8] testEnumerateFromThenNum :: Expectation @@ -219,9 +215,9 @@ testEnumerateFromThenToFractional = toList (Stream.enumerateFromThenToFractional (0.1 :: Double) 2.0 6.0) `shouldReturn` [0.1, 2.0, 3.9, 5.799999999999999] -testEnumerateFromThenSmallBounded :: Expectation -testEnumerateFromThenSmallBounded = - toList (Stream.take 4 (Stream.enumerateFromThenSmallBounded 'a' 'c')) +testEnumerateFromThenSmall :: Expectation +testEnumerateFromThenSmall = + toList (Stream.take 4 (Stream.enumerateFromThenSmall 'a' 'c')) `shouldReturn` "aceg" testEnumerateFromToSmall :: Expectation @@ -427,17 +423,16 @@ main = hspec $ describe moduleName $ do it "fromW16CString# empty" testFromW16CStringEmpty describe "Enumeration Primitives" $ do - it "enumerateFromBounded" testEnumerateFromBounded it "enumerateFromIntegral" testEnumerateFromIntegral it "enumerateFromNum" testEnumerateFromNum it "enumerateFromStepNum" testEnumerateFromStepNum - it "enumerateFromStepIntegral" testEnumerateFromStepIntegral + it "enumerateFromStepIntegralUnbounded" testEnumerateFromStepIntegralUnbounded it "enumerateFromThenNum" testEnumerateFromThenNum it "enumerateFromThenIntegral" testEnumerateFromThenIntegral it "enumerateFromThenFractional" testEnumerateFromThenFractional it "enumerateFromThenToIntegral" testEnumerateFromThenToIntegral it "enumerateFromThenToFractional" testEnumerateFromThenToFractional - it "enumerateFromThenSmallBounded" testEnumerateFromThenSmallBounded + it "enumerateFromThenSmall" testEnumerateFromThenSmall it "enumerateFromToSmall" testEnumerateFromToSmall it "enumerateFromThenToSmall" testEnumerateFromThenToSmall it "enumerateFromFractional" testEnumerateFromFractional From a25d16d3ee7aae66b16fb81c2f8c8785c8fa78cd Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Fri, 3 Jul 2026 09:36:32 +0530 Subject: [PATCH 16/23] Make bounded versions default in Unfold enumeration --- .../Internal/Data/Stream/Enumeration.hs | 4 +- .../Internal/Data/Unfold/Enumeration.hs | 136 +++++++++++------- test/Streamly/Test/Data/Unfold.hs | 116 +++++++-------- 3 files changed, 141 insertions(+), 115 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs index 37501c7aea..e787985fc9 100644 --- a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -148,7 +148,7 @@ enumerateFromNum from = enumerateFromStepNum from 1 enumerateFromStepIntegralUnbounded :: (Integral a, Monad m) => a -> a -> Stream m a #ifdef USE_UNFOLDS_EVERYWHERE enumerateFromStepIntegralUnbounded from stride = - unfold Unfold.enumerateFromStepIntegral (from, stride) + unfold Unfold.enumerateFromStepIntegralUnbounded (from, stride) #else enumerateFromStepIntegralUnbounded from stride = from `seq` stride `seq` @@ -201,7 +201,7 @@ enumerateFromThenIntegral :: (Monad m, Integral a, Bounded a) => a -> a -> Stream m a #ifdef USE_UNFOLDS_EVERYWHERE enumerateFromThenIntegral from next = - unfold Unfold.enumerateFromThenIntegralBounded (from, next) + unfold Unfold.enumerateFromThenIntegral (from, next) #else enumerateFromThenIntegral from next = enumerateFromThenToIntegral diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 5eccfd1cc9..d0306b09e6 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -32,31 +32,40 @@ module Streamly.Internal.Data.Unfold.Enumeration -- enumeraitons can be expressed in terms of these. These are numerically -- stable for floating precision numbers. For that reason these may be -- slightly less efficient than intergal operations. + -- + -- These are unbounded, can overflow, there are no bounds possible for + -- 'Num'. , enumerateFromStepNum , enumerateFromNum , enumerateFromThenNum -- ** 'Integral' Type class Types (Unbounded Enumeration) -- | More efficient than 'Num' based operations for 'Integral' types. - , enumerateFromStepIntegral - , enumerateFromIntegral - , enumerateFromThenIntegral - , enumerateFromToIntegral - , enumerateFromThenToIntegral + , enumerateFromStepIntegralUnbounded + , enumerateFromIntegralUnbounded + , enumerateFromThenIntegralUnbounded -- ** 'Integral' Types (Bounded Enumeration) -- | These are implemented in terms of integral operations using maxBound - -- or minBound as the terminating codition. - , enumerateFromIntegralBounded - , enumerateFromThenIntegralBounded + -- or minBound as the terminating codition. This is the default behavior + -- for 'Integral' enumeration, used when no 'Unbounded' suffix is + -- specified; use the explicit 'Unbounded' suffixed operations above for + -- faster, unchecked enumeration. + , enumerateFromToIntegral + , enumerateFromThenToIntegral + + -- XXX For these two the behavior changed from unbounded to bounded but + -- that is a safe direction. + , enumerateFromIntegral + , enumerateFromThenIntegral -- ** 'Enum' Types not larger than 'Int' -- | These are implemented by converting Enum to Int and using integral -- operations. + , enumerateFromSmall , enumerateFromToSmall + , enumerateFromThenSmall , enumerateFromThenToSmall - , enumerateFromSmallBounded - , enumerateFromThenSmallBounded -- ** 'Fractional' Types -- | These are simply specialization of Num based operations to Fractional @@ -67,12 +76,18 @@ module Streamly.Internal.Data.Unfold.Enumeration , enumerateFromThenToFractional -- * Deprecated + , enumerateFromStepIntegral + , enumerateFromIntegralBounded + , enumerateFromThenIntegralBounded + , enumerateFromSmallBounded + , enumerateFromThenSmallBounded , enumerateFromToIntegralBounded , enumerateFromThenToIntegralBounded ) where #include "inline.hs" +#include "deprecation.h" import Data.Fixed import Data.Bifunctor (bimap) @@ -109,7 +124,7 @@ import Prelude hiding (map, takeWhile, zipWith) -- -- The implementation is numerically stable for floating point values. -- --- Note 'enumerateFromStepIntegral' is faster for integrals. +-- Note 'enumerateFromStepIntegralUnbounded' is faster for integrals. -- -- /Internal/ -- @@ -137,7 +152,7 @@ enumerateFromStepNum = Unfold Producer.enumerateFromStepNum inject -- -- The implementation is numerically stable for floating point values. -- --- Note that 'enumerateFromThenIntegral' is faster for integrals. +-- Note that 'enumerateFromThenIntegralUnbounded' is faster for integrals. -- -- Note that in the strange world of floating point numbers, using -- @enumerateFromThenNum (from, from + 1)@ is almost exactly the same as @@ -191,16 +206,21 @@ takeWhileMWithInput f = map snd . takeWhileM (uncurry f) . carryInput -- overflow or underflow for bounded integrals. -- -- /Internal/ -{-# INLINE_NORMAL enumerateFromStepIntegral #-} -enumerateFromStepIntegral :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromStepIntegral = Unfold Producer.enumerateFromStepIntegral inject +{-# INLINE_NORMAL enumerateFromStepIntegralUnbounded #-} +enumerateFromStepIntegralUnbounded, enumerateFromStepIntegral :: + (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromStepIntegralUnbounded = + Unfold Producer.enumerateFromStepIntegral inject where inject (from, stride) = from `seq` stride `seq` return (from, stride) +RENAME(enumerateFromStepIntegral,enumerateFromStepIntegralUnbounded) + {-# INLINE enumerateFromThenToIntegral #-} -enumerateFromThenToIntegral :: (Monad m, Integral a) => Unfold m (a, a, a) a +enumerateFromThenToIntegral, enumerateFromThenToIntegralBounded :: + (Monad m, Integral a) => Unfold m (a, a, a) a enumerateFromThenToIntegral = Unfold Producer.enumerateFromThenToIntegral inject @@ -208,10 +228,12 @@ enumerateFromThenToIntegral = inject (from, next, to) = return (Producer.EnumInit from next to) -{-# INLINE enumerateFromThenIntegralBounded #-} -enumerateFromThenIntegralBounded :: (Monad m, Integral a, Bounded a ) => - Unfold m (a, a) a -enumerateFromThenIntegralBounded = +RENAME(enumerateFromThenToIntegralBounded,enumerateFromThenToIntegral) + +{-# INLINE enumerateFromThenIntegral #-} +enumerateFromThenIntegral, enumerateFromThenIntegralBounded :: + (Monad m, Integral a, Bounded a) => Unfold m (a, a) a +enumerateFromThenIntegral = lmap toFromThenTo enumerateFromThenToIntegral where @@ -219,40 +241,38 @@ enumerateFromThenIntegralBounded = toFromThenTo (from, next) = (from, next, if next >= from then maxBound else minBound) +RENAME(enumerateFromThenIntegralBounded,enumerateFromThenIntegral) + +{-# INLINE enumerateFromThenIntegralUnbounded #-} +enumerateFromThenIntegralUnbounded :: (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromThenIntegralUnbounded = + lmap + (\(from, next) -> (from, next - from)) + enumerateFromStepIntegralUnbounded + {-# INLINE enumerateFromToIntegral #-} -enumerateFromToIntegral :: (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromToIntegral, enumerateFromToIntegralBounded :: + (Monad m, Integral a) => Unfold m (a, a) a enumerateFromToIntegral = map snd $ takeWhile (\((_, to), b) -> b <= to) $ takeEndBy (\((_, to), b) -> b == to) $ carryInput - $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegral + $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegralUnbounded -{-# INLINE enumerateFromIntegralBounded #-} -enumerateFromIntegralBounded :: (Monad m, Integral a, Bounded a) => - Unfold m a a -enumerateFromIntegralBounded = supplySecond maxBound enumerateFromToIntegral +RENAME(enumerateFromToIntegralBounded,enumerateFromToIntegral) {-# INLINE enumerateFromIntegral #-} -enumerateFromIntegral :: (Monad m, Integral a) => Unfold m a a -enumerateFromIntegral = lmap (\from -> (from, 1)) enumerateFromStepIntegral - -{-# INLINE enumerateFromThenIntegral #-} -enumerateFromThenIntegral :: (Monad m, Integral a ) => Unfold m (a, a) a -enumerateFromThenIntegral = - lmap (\(from, next) -> (from, next - from)) enumerateFromStepIntegral +enumerateFromIntegral, enumerateFromIntegralBounded :: + (Monad m, Integral a, Bounded a) => Unfold m a a +enumerateFromIntegral = supplySecond maxBound enumerateFromToIntegral -{-# DEPRECATED enumerateFromToIntegralBounded "Use enumerateFromToIntegral instead." #-} -{-# INLINE enumerateFromToIntegralBounded #-} -enumerateFromToIntegralBounded :: (Monad m, Integral a) => - Unfold m (a, a) a -enumerateFromToIntegralBounded = enumerateFromToIntegral +RENAME(enumerateFromIntegralBounded,enumerateFromIntegral) -{-# DEPRECATED enumerateFromThenToIntegralBounded "Use enumerateFromThenToIntegral instead." #-} -{-# INLINE enumerateFromThenToIntegralBounded #-} -enumerateFromThenToIntegralBounded :: (Monad m, Integral a) => - Unfold m (a, a, a) a -enumerateFromThenToIntegralBounded = enumerateFromThenToIntegral +{-# INLINE enumerateFromIntegralUnbounded #-} +enumerateFromIntegralUnbounded :: (Monad m, Integral a) => Unfold m a a +enumerateFromIntegralUnbounded = + lmap (\from -> (from, 1)) enumerateFromStepIntegralUnbounded ------------------------------------------------------------------------------ -- Enumeration of Fractionals @@ -336,19 +356,23 @@ enumerateFromThenToSmall = -- -- /Internal/ -- -{-# INLINE enumerateFromSmallBounded #-} -enumerateFromSmallBounded :: (Monad m, Enum a, Bounded a) => Unfold m a a -enumerateFromSmallBounded = supplySecond maxBound enumerateFromToSmall +{-# INLINE enumerateFromSmall #-} +enumerateFromSmall, enumerateFromSmallBounded :: + (Monad m, Enum a, Bounded a) => Unfold m a a +enumerateFromSmall = supplySecond maxBound enumerateFromToSmall + +RENAME(enumerateFromSmallBounded,enumerateFromSmall) -- | Enumerate from given starting Enum value 'from' and next Enum value 'next' -- with stride of (fromEnum next - fromEnum from) till maxBound. -- -- /Internal/ -- -{-# INLINE enumerateFromThenSmallBounded #-} -enumerateFromThenSmallBounded :: forall m a. (Monad m, Enum a, Bounded a) => +{-# INLINE enumerateFromThenSmall #-} +enumerateFromThenSmall, enumerateFromThenSmallBounded + :: forall m a. (Monad m, Enum a, Bounded a) => Unfold m (a, a) a -enumerateFromThenSmallBounded = +enumerateFromThenSmall = let adapt (from, next) = let frm = fromEnum from nxt = fromEnum next @@ -359,6 +383,8 @@ enumerateFromThenSmallBounded = in (frm, nxt, to) in fmap toEnum (lmap adapt enumerateFromThenToIntegral) +RENAME(enumerateFromThenSmallBounded,enumerateFromThenSmall) + ------------------------------------------------------------------------------- -- Enumerable type class ------------------------------------------------------------------------------- @@ -438,9 +464,9 @@ class Enum a => Enumerable a where #define ENUMERABLE_BOUNDED_SMALL(SMALL_TYPE) \ instance Enumerable SMALL_TYPE where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromSmallBounded; \ + enumerateFrom = enumerateFromSmall; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenSmallBounded; \ + enumerateFromThen = enumerateFromThenSmall; \ {-# INLINE enumerateFromTo #-}; \ enumerateFromTo = enumerateFromToSmall; \ {-# INLINE enumerateFromThenTo #-}; \ @@ -455,9 +481,9 @@ ENUMERABLE_BOUNDED_SMALL(Char) #define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ instance Enumerable INTEGRAL_TYPE where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegralBounded; \ + enumerateFrom = enumerateFromIntegral; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegralBounded; \ + enumerateFromThen = enumerateFromThenIntegral; \ {-# INLINE enumerateFromTo #-}; \ enumerateFromTo = enumerateFromToIntegral; \ {-# INLINE enumerateFromThenTo #-}; \ @@ -478,9 +504,9 @@ ENUMERABLE_BOUNDED_INTEGRAL(Word64) #define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \ instance Enumerable INTEGRAL_TYPE where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegral; \ + enumerateFrom = enumerateFromIntegralUnbounded; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegral; \ + enumerateFromThen = enumerateFromThenIntegralUnbounded; \ {-# INLINE enumerateFromTo #-}; \ enumerateFromTo = enumerateFromToIntegral; \ {-# INLINE enumerateFromThenTo #-}; \ diff --git a/test/Streamly/Test/Data/Unfold.hs b/test/Streamly/Test/Data/Unfold.hs index ae770a994d..ef0c25a43a 100644 --- a/test/Streamly/Test/Data/Unfold.hs +++ b/test/Streamly/Test/Data/Unfold.hs @@ -259,19 +259,19 @@ enumerateFromThenNum = ------------------------------------------------------------------------------- -- Test for Integral type ------------------------------------------------------------------------------- -enumerateFromIntegral :: Property -enumerateFromIntegral = +enumerateFromIntegralUnbounded :: Property +enumerateFromIntegralUnbounded = property $ \f -> - let unf = UF.take 50 UF.enumerateFromIntegral + let unf = UF.take 50 UF.enumerateFromIntegralUnbounded in testUnfold unf (f :: Integer) $ Prelude.take 50 $ Prelude.enumFrom f -enumerateFromThenIntegral :: Property -enumerateFromThenIntegral = +enumerateFromThenIntegralUnbounded :: Property +enumerateFromThenIntegralUnbounded = property $ \f th -> - let unf = UF.take 50 UF.enumerateFromThenIntegral + let unf = UF.take 50 UF.enumerateFromThenIntegralUnbounded in testUnfold unf (f :: Integer, th) $ Prelude.take 50 $ Prelude.enumFromThen f th @@ -304,19 +304,19 @@ enumerateFromThenToIntegralLargeStride = [-7537527385297985025, 5092559113693760989] `shouldBe` True -enumerateFromIntegralBounded :: Property -enumerateFromIntegralBounded = +enumerateFromIntegral :: Property +enumerateFromIntegral = property $ \f -> - let unf = UF.take 50 UF.enumerateFromIntegralBounded + let unf = UF.take 50 UF.enumerateFromIntegral in testUnfold unf (f :: Int) $ Prelude.take 50 $ Prelude.enumFrom f -enumerateFromThenIntegralBounded :: Property -enumerateFromThenIntegralBounded = +enumerateFromThenIntegral :: Property +enumerateFromThenIntegral = property $ \f th -> - let unf = UF.take 50 UF.enumerateFromThenIntegralBounded + let unf = UF.take 50 UF.enumerateFromThenIntegral in testUnfold unf (f :: Int, th) $ Prelude.take 50 $ Prelude.enumFromThen f th @@ -336,19 +336,19 @@ enumerateFromThenToIntegralBounded = in testUnfold unf (f :: Int, th, to) $ Prelude.take 50 $ Prelude.enumFromThenTo f th to -enumerateFromSmallBounded :: Property -enumerateFromSmallBounded = +enumerateFromSmall :: Property +enumerateFromSmall = property $ \f -> - let unf = UF.take 50 UF.enumerateFromSmallBounded + let unf = UF.take 50 UF.enumerateFromSmall in testUnfold unf (f :: Char) $ Prelude.take 50 $ Prelude.enumFrom f -enumerateFromThenSmallBounded :: Property -enumerateFromThenSmallBounded = +enumerateFromThenSmall :: Property +enumerateFromThenSmall = property $ \f th -> - let unf = UF.take 50 UF.enumerateFromThenSmallBounded + let unf = UF.take 50 UF.enumerateFromThenSmall in testUnfold unf (f :: Char, th) $ Prelude.take 50 $ Prelude.enumFromThen f th @@ -368,19 +368,19 @@ enumerateFromThenToSmall = in testUnfold unf (f :: Char, th, to) $ Prelude.take 50 $ Prelude.enumFromThenTo f th to -enumerateFromSmallBoundedOrd :: Property -enumerateFromSmallBoundedOrd = +enumerateFromSmallOrd :: Property +enumerateFromSmallOrd = property $ \f -> - let unf = UF.take 3 UF.enumerateFromSmallBounded + let unf = UF.take 3 UF.enumerateFromSmall in testUnfold unf (f :: Ordering) $ Prelude.take 3 $ Prelude.enumFrom f -enumerateFromThenSmallBoundedOrd :: Property -enumerateFromThenSmallBoundedOrd = +enumerateFromThenSmallOrd :: Property +enumerateFromThenSmallOrd = property $ \f th -> - let unf = UF.take 3 UF.enumerateFromThenSmallBounded + let unf = UF.take 3 UF.enumerateFromThenSmall in testUnfold unf (f :: Ordering, th) $ Prelude.take 3 $ Prelude.enumFromThen f th @@ -401,19 +401,19 @@ enumerateFromThenToSmallOrd = Prelude.take 3 $ Prelude.enumFromThenTo f th to ------------------------------------------------------------------------------- -enumerateFromSmallBoundedBool :: Property -enumerateFromSmallBoundedBool = +enumerateFromSmallBool :: Property +enumerateFromSmallBool = property $ \f -> - let unf = UF.take 2 UF.enumerateFromSmallBounded + let unf = UF.take 2 UF.enumerateFromSmall in testUnfold unf (f :: Bool) $ Prelude.take 2 $ Prelude.enumFrom f -enumerateFromThenSmallBoundedBool :: Property -enumerateFromThenSmallBoundedBool = +enumerateFromThenSmallBool :: Property +enumerateFromThenSmallBool = property $ \f th -> - let unf = UF.take 2 UF.enumerateFromThenSmallBounded + let unf = UF.take 2 UF.enumerateFromThenSmall in testUnfold unf (f :: Bool, th) $ Prelude.take 2 $ Prelude.enumFromThen f th @@ -433,19 +433,19 @@ enumerateFromThenToSmallBool = in testUnfold unf (f :: Bool, th, to) $ Prelude.take 2 $ Prelude.enumFromThenTo f th to ------------------------------------------------------------------------------- -enumerateFromSmallBoundedUnit :: Property -enumerateFromSmallBoundedUnit = +enumerateFromSmallUnit :: Property +enumerateFromSmallUnit = property $ \f -> - let unf = UF.take 1 UF.enumerateFromSmallBounded + let unf = UF.take 1 UF.enumerateFromSmall in testUnfold unf (f :: ()) $ Prelude.take 1 $ Prelude.enumFrom f -enumerateFromThenSmallBoundedUnit :: Property -enumerateFromThenSmallBoundedUnit = +enumerateFromThenSmallUnit :: Property +enumerateFromThenSmallUnit = property $ \f th -> - let unf = UF.take 1 UF.enumerateFromThenSmallBounded + let unf = UF.take 1 UF.enumerateFromThenSmall in testUnfold unf (f :: (), th) $ Prelude.take 1 $ Prelude.enumFromThen f th @@ -629,18 +629,18 @@ enumerateFromThenToIntegralOverflowDn = [-124, -126, -128] `shouldBe` True -enumerateFromIntegralBoundedOverflow :: Expectation -enumerateFromIntegralBoundedOverflow = +enumerateFromIntegralOverflow :: Expectation +enumerateFromIntegralOverflow = testUnfold - (UF.take 10 UF.enumerateFromIntegralBounded) + (UF.take 10 UF.enumerateFromIntegral) (253 :: Word8) [253, 254, 255] `shouldBe` True -enumerateFromThenIntegralBoundedOverflow :: Expectation -enumerateFromThenIntegralBoundedOverflow = +enumerateFromThenIntegralOverflow :: Expectation +enumerateFromThenIntegralOverflow = testUnfold - (UF.take 10 UF.enumerateFromThenIntegralBounded) + (UF.take 10 UF.enumerateFromThenIntegral) (250 :: Word8, 252) [250, 252, 254] `shouldBe` True @@ -1220,15 +1220,15 @@ testGeneration = prop "enumerateFromNum" enumerateFromNum prop "enumerateFromThenNum" enumerateFromThenNum ----------- Enumerate from Integral ------------------------------- - prop "enumerateFromIntegral" enumerateFromIntegral - prop "enumerateFromThenIntegral" enumerateFromThenIntegral + prop "enumerateFromIntegralUnbounded" enumerateFromIntegralUnbounded + prop "enumerateFromThenIntegralUnbounded" enumerateFromThenIntegralUnbounded prop "enumerateFromToIntegral" enumerateFromToIntegral prop "enumerateFromThenToIntegral" enumerateFromThenToIntegral it "enumerateFromThenToIntegral large stride" enumerateFromThenToIntegralLargeStride - prop "enumerateFromIntegralBounded" enumerateFromIntegralBounded - prop "enumerateFromThenIntegralBounded" enumerateFromThenIntegralBounded + prop "enumerateFromIntegral" enumerateFromIntegral + prop "enumerateFromThenIntegral" enumerateFromThenIntegral prop "enumerateFromToIntegralBounded" enumerateFromToIntegralBounded prop "enumerateFromThenToIntegralBounded" enumerateFromThenToIntegralBounded ----------- Overflow at the bound of a fixed-size Integral type ---- @@ -1237,32 +1237,32 @@ testGeneration = enumerateFromThenToIntegralOverflowUp it "enumerateFromThenToIntegral overflow dn" enumerateFromThenToIntegralOverflowDn - it "enumerateFromIntegralBounded overflow" - enumerateFromIntegralBoundedOverflow - it "enumerateFromThenIntegralBounded overflow" - enumerateFromThenIntegralBoundedOverflow + it "enumerateFromIntegral overflow" + enumerateFromIntegralOverflow + it "enumerateFromThenIntegral overflow" + enumerateFromThenIntegralOverflow it "enumerateFromToIntegralBounded overflow" enumerateFromToIntegralBoundedOverflow it "enumerateFromThenToIntegralBounded overflow" enumerateFromThenToIntegralBoundedOverflow ----------- Enumerate from Small Integral ------------------------- - prop "enumerateFromSmallBounded" enumerateFromSmallBounded - prop "enumerateFromThenSmallBounded" enumerateFromThenSmallBounded + prop "enumerateFromSmall" enumerateFromSmall + prop "enumerateFromThenSmall" enumerateFromThenSmall prop "enumerateFromToSmall" enumerateFromToSmall prop "enumerateFromThenToSmall" enumerateFromThenToSmall -- - prop "enumerateFromSmallBoundedOrd" enumerateFromSmallBoundedOrd - prop "enumerateFromThenSmallBoundedOrd" enumerateFromThenSmallBoundedOrd + prop "enumerateFromSmallOrd" enumerateFromSmallOrd + prop "enumerateFromThenSmallOrd" enumerateFromThenSmallOrd prop "enumerateFromToSmallOrd" enumerateFromToSmallOrd prop "enumerateFromThenToSmallOrd" enumerateFromThenToSmallOrd - prop "enumerateFromSmallBoundedUnit" enumerateFromSmallBoundedUnit - prop "enumerateFromThenSmallBoundedUnit" enumerateFromThenSmallBoundedUnit + prop "enumerateFromSmallUnit" enumerateFromSmallUnit + prop "enumerateFromThenSmallUnit" enumerateFromThenSmallUnit prop "enumerateFromToSmallUnit" enumerateFromToSmallUnit prop "enumerateFromThenToSmallUnit" enumerateFromThenToSmallUnit - prop "enumerateFromSmallBoundedUnit" enumerateFromSmallBoundedBool - prop "enumerateFromThenSmallBoundedBool" enumerateFromThenSmallBoundedBool + prop "enumerateFromSmallBool" enumerateFromSmallBool + prop "enumerateFromThenSmallBool" enumerateFromThenSmallBool prop "enumerateFromToSmallBool" enumerateFromToSmallBool prop "enumerateFromThenToSmallBool" enumerateFromThenToSmallBool From 91eb84270f10966be1335e34ec9daa251f14744a Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Sat, 4 Jul 2026 17:47:45 +0530 Subject: [PATCH 17/23] Add a simplified upward enumeration operation --- core/src/Streamly/Internal/Data/Producer.hs | 43 +++++++++++++++++++ .../Internal/Data/Stream/Enumeration.hs | 16 +++++++ 2 files changed, 59 insertions(+) diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index 70dbd0417a..8c2235e492 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -18,6 +18,7 @@ module Streamly.Internal.Data.Producer , FairCrossState(..) , TupleState(..) , EnumState(..) + , EnumStateUp(..) , ConcatState(..) , InterleaveState(..) , InterleaveEachState(..) @@ -48,12 +49,14 @@ module Streamly.Internal.Data.Producer , enumerateFromStepNum , enumerateFromStepIntegral , enumerateFromThenToIntegral + , enumerateFromThenUpToIntegral ) where #include "inline.hs" import Data.Functor ((<&>)) +import Fusion.Plugin.Types (Fuse(..)) import Streamly.Internal.Data.Stream.Step (Step(..)) import Prelude hiding (mapM) @@ -639,3 +642,43 @@ enumerateFromThenToIntegral (EnumYieldDownward x stride toMinus) = then Yield x EnumStop else Yield x $ EnumYieldDownward (x + stride) stride toMinus enumerateFromThenToIntegral EnumStop = pure Stop + +-- | State for 'enumerateFromThenUpToIntegral'. Same as 'EnumState' but +-- without the downward direction, since the function only ever moves +-- upward. +{-# ANN type EnumStateUp Fuse #-} +data EnumStateUp a = + EnumUpInit a a a + | EnumUpYield a a a + | EnumUpNext a a a + | EnumUpStop + +-- | Like 'enumerateFromThenToIntegral' but a simplified version that only +-- works in the upward direction i.e. it assumes @next >= from@. The +-- generated stream's first element is @from@, the second element is @next@ +-- and the successive elements are in increments of @next - from@ up to +-- @to@. +{-# INLINE_LATE enumerateFromThenUpToIntegral #-} +enumerateFromThenUpToIntegral :: + (Applicative m, Integral a) => Producer m (EnumStateUp a) a +enumerateFromThenUpToIntegral (EnumUpInit from next to) = + pure $ + if next < from + then Stop + else + if to < next + then + if to < from + then Stop + else Yield from EnumUpStop + else -- from <= next <= to + let stride = next - from + in Skip $ EnumUpYield from stride (to - stride) +enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = + pure $ Yield x (EnumUpNext x stride toMinus) +enumerateFromThenUpToIntegral (EnumUpNext x stride toMinus) = + pure $ + if x > toMinus + then Stop + else Skip $ EnumUpYield (x + stride) stride toMinus +enumerateFromThenUpToIntegral EnumUpStop = pure Stop diff --git a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs index e787985fc9..f3406d79b5 100644 --- a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -47,6 +47,7 @@ module Streamly.Internal.Data.Stream.Enumeration , enumerateFromToIntegral , enumerateFromThenIntegral , enumerateFromThenToIntegral + , enumerateFromThenUpToIntegral -- ** 'Enum' Types not larger than 'Int' -- | These are implemented by converting Enum to Int and using integral @@ -185,6 +186,21 @@ enumerateFromThenToIntegral from next to = (Producer.EnumInit from next to) #endif +-- | Like 'enumerateFromThenToIntegral' but a simplified version that only +-- works in the upward direction i.e. return empty stream if @then < from@. +-- +-- >>> Stream.toList $ Stream.enumerateFromThenUpToIntegral 0 2 6 +-- [0,2,4,6] +-- +{-# INLINE_NORMAL enumerateFromThenUpToIntegral #-} +enumerateFromThenUpToIntegral + :: (Monad m, Integral a) + => a -> a -> a -> Stream m a +enumerateFromThenUpToIntegral from next to = + Stream + (const Producer.enumerateFromThenUpToIntegral) + (Producer.EnumUpInit from next to) + -- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from -- then@ generates a stream whose first element is @from@, the second element -- is @then@ and the successive elements are in increments of @then - from@. From 662d6c0ca77a09cc8f83a39966b906a934f7c6a8 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Sat, 4 Jul 2026 17:48:45 +0530 Subject: [PATCH 18/23] Update single yield point guidelines --- docs/Developer/optimization-guidelines.md | 90 +++++++++++++++++++---- 1 file changed, 76 insertions(+), 14 deletions(-) diff --git a/docs/Developer/optimization-guidelines.md b/docs/Developer/optimization-guidelines.md index 45b23b928d..3b38334bdc 100644 --- a/docs/Developer/optimization-guidelines.md +++ b/docs/Developer/optimization-guidelines.md @@ -149,13 +149,75 @@ Step function of a stream or unfold: (e.g. `Stream (const Producer.fromList)`) are unaffected because returning a top-level function reference does not allocate. -Multiple yield points or single?: +Minimize the number of yield points and try to keep a single `Yield` point per +state. In Scanl benchmarks we observed that the following state machine code +does not fuse: +``` +enumerateFromThenUpToIntegral :: + (Applicative m, Integral a) => Producer m (EnumStateUp a) a +enumerateFromThenUpToIntegral (EnumUpInit from next to) = + pure $ + if to < next + then if to < from then Stop else Yield from EnumUpStop + else + let stride = next - from + in Skip $ EnumUpYield from stride (to - stride) +enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = + pure $ + if x > toMinus + then Yield x EnumUpStop + else Yield x $ EnumUpYield (x + stride) stride toMinus +enumerateFromThenUpToIntegral EnumUpStop = pure Stop +``` + +However the following variation fuses quickly: +``` +enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = + if x > toMinus + then Yield x Stop + else Yield x $ EnumUpYield (x + stride) stride toMinus +``` + +If we use a single yield point to transfer control to the next state machine +then the state machine is likely to fuse better. For example, this works +absolutely fine: +``` +enumerateFromThenUpToIntegral :: + (Applicative m, Integral a) => Producer m (EnumStateUp a) a +enumerateFromThenUpToIntegral (EnumUpInit from next to) = + pure $ + if to < next + then if to < from then Stop else Yield from EnumUpStop + else -- from <= next <= to + let stride = next - from + in Skip $ EnumUpYield from stride (to - stride) +enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = + pure $ Yield x (EnumUpNext x stride toMinus) +enumerateFromThenUpToIntegral (EnumUpNext x stride toMinus) = + pure $ + if x > toMinus + then Stop + else Skip $ EnumUpYield (x + stride) stride toMinus +enumerateFromThenUpToIntegral EnumUpStop = pure Stop +``` + +A Skip ties a loop into the current, local state machine, a Yield however +stitches this state machine with another state machine, the control is +transferred to another loop, and if there are multiple such points to stitch +one state machine loop with a different state machines then the compiler is +less likely to be able to tie them up into a single closed large loop. So try +to keep the state machine as simple as possible and try to keep a common yield +point. Also, do not introduce unnecessary states because a Skip translates to a +jump instruction which can cause some performance hit if it is not necessary +for fusion. + +In general, I guess one yield per state should optimize fine, though more yield +exit points may lead to more code bloat. Also, if two yields lead to the same +next state, the code can be refactored as a single yield, or Skip to a common +state from both the yield points and yield in the common state; this is +essentially a case of Skip forcing an explicit join point. -* A single yield point is usually desirable, however, not always necessary. - In some cases multiple yield points may in fact be needed for fusion, - see `splitOnSeq` for an example. Or maybe its fusing because of a - direct yield instead of going through an indirect common yielding - state. + See `splitOnSeq` for a fusing example with multiple yield points. Recursion in step function: @@ -164,14 +226,14 @@ Recursion in step function: introduce optimization barriers that are harder to remove by the GHC simplifier. - However, in some cases it may be better to have a local recursive - loop. A recursive loop can help us avoid threading around some large - state values every time. Values that do not change across a loop can - be factored in the scope outside the loop (static argument transform), - this way we can create a local loop which is more efficient than - threading around the state in a larger loop. See the `splitOnSeq` - combinator as an example where we use a local recursive loop, it fuses - and is significantly efficient compared to using `Skip`. + However, in some cases it may be better to have a local recursive loop (when + it is not spanning multiple state constructors). A recursive loop can help us + avoid threading around some large state values every time. Values that do not + change across a loop can be factored in the scope outside the loop (static + argument transform), this way we can create a local loop which is more + efficient than threading around the state in a larger loop. See the + `splitOnSeq` combinator as an example where we use a local recursive loop, it + fuses and is significantly efficient compared to using `Skip`. In a local recursive loop use SPEC and annotate even the rest of the loop arguments as strict where needed. We have observed that when the From f55495dc45e00ace70a3cada12d608919dfbed3f Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Tue, 7 Jul 2026 19:25:07 +0530 Subject: [PATCH 19/23] Add Fuse annotation on EnumState This reverts commit 5deb373b1b29687bea9db5f828d97883909be593. --- core/src/Streamly/Internal/Data/Producer.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index 8c2235e492..f975ab5f48 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -603,6 +603,7 @@ enumerateFromStepIntegral (x, stride) = pure $ Yield x $! (x + stride, stride) -- or 'EnumYieldDownward', which carry the current value, the stride and -- @to - stride@ (checked against before incrementing, so that the increment -- itself cannot overflow past the bound). +{-# ANN type EnumState Fuse #-} data EnumState a = EnumInit a a a | EnumYieldUpward a a a From 4fb1a8960c7baf09ce47deb32e9094ab2089aa43 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Sat, 4 Jul 2026 18:30:56 +0530 Subject: [PATCH 20/23] Make enumerateFromThenTo fusible --- core/src/Streamly/Internal/Data/Producer.hs | 26 ++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index f975ab5f48..52c3b318aa 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -607,7 +607,10 @@ enumerateFromStepIntegral (x, stride) = pure $ Yield x $! (x + stride, stride) data EnumState a = EnumInit a a a | EnumYieldUpward a a a + | EnumNextUpward a a a | EnumYieldDownward a a a + | EnumNextDownward a a a + | EnumSingle a | EnumStop -- | 'Producer' for enumerating an 'Integral' type in steps up to a given @@ -622,26 +625,37 @@ enumerateFromThenToIntegral (EnumInit from next to) = if next >= from then if to < next - then if to < from then Stop else Yield from EnumStop + then + if to < from + then Stop + else Skip (EnumSingle from) else -- from <= next <= to let stride = next - from in Skip $ EnumYieldUpward from stride (to - stride) else if to > next - then if to > from then Stop else Yield from EnumStop + then + if to > from + then Stop + else Skip (EnumSingle from) else -- from >= next >= to let stride = next - from in Skip $ EnumYieldDownward from stride (to - stride) enumerateFromThenToIntegral (EnumYieldUpward x stride toMinus) = + pure $ Yield x (EnumNextUpward x stride toMinus) +enumerateFromThenToIntegral (EnumNextUpward x stride toMinus) = pure $ if x > toMinus - then Yield x EnumStop - else Yield x $ EnumYieldUpward (x + stride) stride toMinus + then Stop + else Skip $ EnumYieldUpward (x + stride) stride toMinus enumerateFromThenToIntegral (EnumYieldDownward x stride toMinus) = + pure $ Yield x (EnumNextDownward x stride toMinus) +enumerateFromThenToIntegral (EnumNextDownward x stride toMinus) = pure $ if x < toMinus - then Yield x EnumStop - else Yield x $ EnumYieldDownward (x + stride) stride toMinus + then Stop + else Skip $ EnumYieldDownward (x + stride) stride toMinus +enumerateFromThenToIntegral (EnumSingle x) = pure $ Yield x EnumStop enumerateFromThenToIntegral EnumStop = pure Stop -- | State for 'enumerateFromThenUpToIntegral'. Same as 'EnumState' but From 788144fa01db91017fd68ba12c51ea2b767948a8 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Mon, 6 Jul 2026 14:02:16 +0530 Subject: [PATCH 21/23] Use Num APIs in general and specialize RealFloat, Bounded --- .../Data/Stream/Nesting/LogicUnfold.hs | 2 +- .../Benchmark/Data/Stream/Type/Logic.hs | 6 +- benchmark/Streamly/Benchmark/Data/Unfold.hs | 10 +- .../Benchmark/Data/Unfold/Enumeration.hs | 8 +- .../Streamly/Benchmark/Data/Unfold/Type.hs | 20 +- core/docs/Changelog.md | 2 + .../Internal/Data/Array/Generic/Type.hs | 4 +- .../Internal/Data/MutArray/Generic.hs | 4 +- core/src/Streamly/Internal/Data/Parser.hs | 2 +- core/src/Streamly/Internal/Data/Producer.hs | 95 ++- .../Internal/Data/Stream/Enumeration.hs | 568 +++++++++++------- .../Internal/Data/Unfold/Enumeration.hs | 500 +++++++++------ docs/Developer/optimization-guidelines.md | 20 +- test/Streamly/Test/Data/Stream/Generate.hs | 30 +- test/Streamly/Test/Data/Stream/Nesting.hs | 2 +- test/Streamly/Test/Data/Stream/Serial.hs | 2 +- test/Streamly/Test/Data/Stream/Type.hs | 2 +- test/Streamly/Test/Data/Unfold.hs | 86 ++- test/Streamly/Test/Prelude/Serial.hs | 2 +- 19 files changed, 850 insertions(+), 515 deletions(-) diff --git a/benchmark/Streamly/Benchmark/Data/Stream/Nesting/LogicUnfold.hs b/benchmark/Streamly/Benchmark/Data/Stream/Nesting/LogicUnfold.hs index 704f0b9ce1..91b5205979 100644 --- a/benchmark/Streamly/Benchmark/Data/Stream/Nesting/LogicUnfold.hs +++ b/benchmark/Streamly/Benchmark/Data/Stream/Nesting/LogicUnfold.hs @@ -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 () diff --git a/benchmark/Streamly/Benchmark/Data/Stream/Type/Logic.hs b/benchmark/Streamly/Benchmark/Data/Stream/Type/Logic.hs index 5ab5dfaf55..356e84df17 100644 --- a/benchmark/Streamly/Benchmark/Data/Stream/Type/Logic.hs +++ b/benchmark/Streamly/Benchmark/Data/Stream/Type/Logic.hs @@ -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 => diff --git a/benchmark/Streamly/Benchmark/Data/Unfold.hs b/benchmark/Streamly/Benchmark/Data/Unfold.hs index 5ec12f3c64..bdd3d1e675 100644 --- a/benchmark/Streamly/Benchmark/Data/Unfold.hs +++ b/benchmark/Streamly/Benchmark/Data/Unfold.hs @@ -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 @@ -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 @@ -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 @@ -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, @@ -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 diff --git a/benchmark/Streamly/Benchmark/Data/Unfold/Enumeration.hs b/benchmark/Streamly/Benchmark/Data/Unfold/Enumeration.hs index f22b6b1196..16862fa727 100644 --- a/benchmark/Streamly/Benchmark/Data/Unfold/Enumeration.hs +++ b/benchmark/Streamly/Benchmark/Data/Unfold/Enumeration.hs @@ -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 @@ -71,7 +71,7 @@ enumerateFromToIntegral size start = drainGeneration ( UF.supplySecond (size + start) - UF.enumerateFromToIntegral + UF.enumerateFromToNum ) start #ifdef INSPECTION @@ -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 @@ -119,7 +119,7 @@ enumerateFromToFractional size start = in drainGeneration ( UF.supplySecond (intToDouble $ start + size) - UF.enumerateFromToFractional + UF.enumerateFromToRealFloat ) (intToDouble start) diff --git a/benchmark/Streamly/Benchmark/Data/Unfold/Type.hs b/benchmark/Streamly/Benchmark/Data/Unfold/Type.hs index c7189ba533..23fa565168 100644 --- a/benchmark/Streamly/Benchmark/Data/Unfold/Type.hs +++ b/benchmark/Streamly/Benchmark/Data/Unfold/Type.hs @@ -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 @@ -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 :: @@ -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 @@ -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 @@ -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 @@ -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) @@ -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. @@ -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 diff --git a/core/docs/Changelog.md b/core/docs/Changelog.md index 5578b0208e..7c19b39e40 100644 --- a/core/docs/Changelog.md +++ b/core/docs/Changelog.md @@ -42,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) diff --git a/core/src/Streamly/Internal/Data/Array/Generic/Type.hs b/core/src/Streamly/Internal/Data/Array/Generic/Type.hs index 035ed80f1a..7b01f0113a 100644 --- a/core/src/Streamly/Internal/Data/Array/Generic/Type.hs +++ b/core/src/Streamly/Internal/Data/Array/Generic/Type.hs @@ -227,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 diff --git a/core/src/Streamly/Internal/Data/MutArray/Generic.hs b/core/src/Streamly/Internal/Data/MutArray/Generic.hs index bfd1653b38..f02a8682a1 100644 --- a/core/src/Streamly/Internal/Data/MutArray/Generic.hs +++ b/core/src/Streamly/Internal/Data/MutArray/Generic.hs @@ -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 #-} @@ -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 diff --git a/core/src/Streamly/Internal/Data/Parser.hs b/core/src/Streamly/Internal/Data/Parser.hs index ef01b87ff3..f4fef3ae57 100644 --- a/core/src/Streamly/Internal/Data/Parser.hs +++ b/core/src/Streamly/Internal/Data/Parser.hs @@ -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 diff --git a/core/src/Streamly/Internal/Data/Producer.hs b/core/src/Streamly/Internal/Data/Producer.hs index 52c3b318aa..3d0b74d813 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -46,16 +46,18 @@ module Streamly.Internal.Data.Producer , takeWhileM , takeEndByM , unfoldrM - , enumerateFromStepNum - , enumerateFromStepIntegral - , enumerateFromThenToIntegral - , enumerateFromThenUpToIntegral + , enumerateFromStepRealFloat + , enumerateFromStep + , enumerateFromThenTo + , enumerateUpFromThenTo + , enumerateDownFromThenTo ) where #include "inline.hs" import Data.Functor ((<&>)) +import Data.Ord (Down(..)) import Fusion.Plugin.Types (Fuse(..)) import Streamly.Internal.Data.Stream.Step (Step(..)) @@ -582,9 +584,24 @@ unfoldrM next a = -- @stride@ every time. The state @(from, stride, i)@ carries the counter -- @i@ used to compute @from + i * stride@ on each step; @from@ and @stride@ -- are threaded through unchanged. -{-# INLINE_LATE enumerateFromStepNum #-} -enumerateFromStepNum :: (Applicative m, Num a) => Producer m (a, a, a) a -enumerateFromStepNum (from, stride, i) = +-- +-- For floating point numbers if the increment is less than the precision then +-- it just gets lost. Therefore we cannot always increment it correctly by just +-- repeated addition. +-- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15 +-- 9007199254740992 + 2 :: Double => 9.007199254740994e15 +-- +-- Instead we accumulate the increment counter and compute the increment +-- every time before adding it to the starting number. +-- +-- This is numerically stable, for that it enumertaes using multiplication +-- instead of repeated addition, therefore, it is slower than it needs to be +-- for non-floating precision types. +-- +-- This is not overflow safe. +{-# INLINE_LATE enumerateFromStepRealFloat #-} +enumerateFromStepRealFloat :: (Applicative m, RealFloat a) => Producer m (a, a, a) a +enumerateFromStepRealFloat (from, stride, i) = -- Note that the counter "i" is the same type as the type being enumerated. -- It may overflow, for example, if we are enumerating Word8, after 255 the -- counter will become 0, but the overflow does not affect the enumeration @@ -594,9 +611,15 @@ enumerateFromStepNum (from, stride, i) = -- | 'Producer' for enumerating integrals starting from @x@, incrementing by -- a constant @stride@ every time. The state @(x, stride)@ carries the -- current value and the stride, which is threaded through unchanged. -{-# INLINE_LATE enumerateFromStepIntegral #-} -enumerateFromStepIntegral :: (Applicative m, Integral a) => Producer m (a, a) a -enumerateFromStepIntegral (x, stride) = pure $ Yield x $! (x + stride, stride) +-- +-- This is faster than the stable version because we do not need to worry about +-- numerical stability, therefore, can do it by repeated addition. Do not use +-- this for floating point types if numerical stability is required. +-- +-- This is not overflow safe. +{-# INLINE_LATE enumerateFromStep #-} +enumerateFromStep :: (Applicative m, Num a) => Producer m (a, a) a +enumerateFromStep (x, stride) = pure $ Yield x $! (x + stride, stride) -- | State for 'enumerateFromThenToIntegral'. @EnumInit from next to@ is the -- starting state carrying the arguments; it transitions to 'EnumYieldUpward' @@ -617,10 +640,15 @@ data EnumState a = -- limit. @EnumInit from next to@ generates a finite stream whose first -- element is @from@, the second element is @next@ and the successive -- elements are in increments of @next - from@ up to @to@. -{-# INLINE_LATE enumerateFromThenToIntegral #-} -enumerateFromThenToIntegral :: - (Applicative m, Integral a) => Producer m (EnumState a) a -enumerateFromThenToIntegral (EnumInit from next to) = +-- +-- This is not numerically stable for floating point numbers as it enumerates +-- by repeated addition. +-- +-- This is overflow safe. +{-# INLINE_LATE enumerateFromThenTo #-} +enumerateFromThenTo :: + (Applicative m, Num a, Ord a) => Producer m (EnumState a) a +enumerateFromThenTo (EnumInit from next to) = pure $ if next >= from then @@ -641,24 +669,24 @@ enumerateFromThenToIntegral (EnumInit from next to) = else -- from >= next >= to let stride = next - from in Skip $ EnumYieldDownward from stride (to - stride) -enumerateFromThenToIntegral (EnumYieldUpward x stride toMinus) = +enumerateFromThenTo (EnumYieldUpward x stride toMinus) = pure $ Yield x (EnumNextUpward x stride toMinus) -enumerateFromThenToIntegral (EnumNextUpward x stride toMinus) = +enumerateFromThenTo (EnumNextUpward x stride toMinus) = pure $ if x > toMinus then Stop else Skip $ EnumYieldUpward (x + stride) stride toMinus -enumerateFromThenToIntegral (EnumYieldDownward x stride toMinus) = +enumerateFromThenTo (EnumYieldDownward x stride toMinus) = pure $ Yield x (EnumNextDownward x stride toMinus) -enumerateFromThenToIntegral (EnumNextDownward x stride toMinus) = +enumerateFromThenTo (EnumNextDownward x stride toMinus) = pure $ if x < toMinus then Stop else Skip $ EnumYieldDownward (x + stride) stride toMinus -enumerateFromThenToIntegral (EnumSingle x) = pure $ Yield x EnumStop -enumerateFromThenToIntegral EnumStop = pure Stop +enumerateFromThenTo (EnumSingle x) = pure $ Yield x EnumStop +enumerateFromThenTo EnumStop = pure Stop --- | State for 'enumerateFromThenUpToIntegral'. Same as 'EnumState' but +-- | State for 'enumerateUpFromThenToIntegral'. Same as 'EnumState' but -- without the downward direction, since the function only ever moves -- upward. {-# ANN type EnumStateUp Fuse #-} @@ -668,15 +696,15 @@ data EnumStateUp a = | EnumUpNext a a a | EnumUpStop --- | Like 'enumerateFromThenToIntegral' but a simplified version that only +-- | Like 'enumerateFromThenTo' but a simplified version that only -- works in the upward direction i.e. it assumes @next >= from@. The -- generated stream's first element is @from@, the second element is @next@ -- and the successive elements are in increments of @next - from@ up to -- @to@. -{-# INLINE_LATE enumerateFromThenUpToIntegral #-} -enumerateFromThenUpToIntegral :: - (Applicative m, Integral a) => Producer m (EnumStateUp a) a -enumerateFromThenUpToIntegral (EnumUpInit from next to) = +{-# INLINE_LATE enumerateUpFromThenTo #-} +enumerateUpFromThenTo :: + (Applicative m, Num a, Ord a) => Producer m (EnumStateUp a) a +enumerateUpFromThenTo (EnumUpInit from next to) = pure $ if next < from then Stop @@ -689,11 +717,20 @@ enumerateFromThenUpToIntegral (EnumUpInit from next to) = else -- from <= next <= to let stride = next - from in Skip $ EnumUpYield from stride (to - stride) -enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = +enumerateUpFromThenTo (EnumUpYield x stride toMinus) = pure $ Yield x (EnumUpNext x stride toMinus) -enumerateFromThenUpToIntegral (EnumUpNext x stride toMinus) = +enumerateUpFromThenTo (EnumUpNext x stride toMinus) = pure $ if x > toMinus then Stop else Skip $ EnumUpYield (x + stride) stride toMinus -enumerateFromThenUpToIntegral EnumUpStop = pure Stop +enumerateUpFromThenTo EnumUpStop = pure Stop + +-- | Like 'enumerateDownFromThenTo' but enumerates in decreasing order. +-- +-- If this works correctly then it is a proof that enumerateUpFromThenTo is +-- mathematically correct. +enumerateDownFromThenTo :: + (Applicative m, Num a, Ord a) + => Producer m (EnumStateUp (Down a)) a +enumerateDownFromThenTo s = fmap (fmap getDown) (enumerateUpFromThenTo s) diff --git a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs index f3406d79b5..bfd1cdf4df 100644 --- a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -21,49 +21,52 @@ module Streamly.Internal.Data.Stream.Enumeration -- ** 'Num' Type class Types -- | Most general operations via the 'Num' type class. All other - -- enumeraitons can be expressed in terms of these. These are numerically - -- stable for floating precision numbers. For that reason these may be - -- slightly less efficient than intergal operations. + -- enumeraitons can be expressed in terms of these. -- - -- These are unbounded, can overflow, there are no bounds possible for - -- 'Num'. + -- These are numerically unstable for floating precision numbers. Use the + -- RealFloat specific operations for numerical stability. + -- + -- The "To" vesions are overflow protected, others can overflow and wrap + -- around for bounded types. Use the "To" versions with max or min bound to + -- stop at the bound or use the bounded versions. , enumerateFromStepNum , enumerateFromNum + , enumerateDownFromNum , enumerateFromThenNum + , enumerateFromToNum + , enumerateDownFromToNum + , enumerateFromThenToNum + , enumerateUpFromThenToNum + , enumerateDownFromThenToNum - -- ** 'Integral' Type class Types (Unbounded Enumeration) - -- | More efficient than 'Num' based operations for 'Integral' types. - , enumerateFromStepIntegralUnbounded - , enumerateFromIntegralUnbounded - , enumerateFromThenIntegralUnbounded - - -- ** 'Integral' Types (Bounded Enumeration) - -- | These are implemented in terms of integral operations using maxBound - -- or minBound as the terminating codition. This is the default behavior - -- for 'Integral' enumeration, used when no 'Unbounded' suffix is - -- specified; use the explicit 'Unbounded' suffixed operations above for - -- faster, unchecked enumeration. - , enumerateFromIntegral - , enumerateFromToIntegral - , enumerateFromThenIntegral - , enumerateFromThenToIntegral - , enumerateFromThenUpToIntegral + -- ** Bounded Num Types + , enumerateFromBoundedNum + , enumerateFromThenBoundedNum + , enumerateDownFromBoundedNum -- ** 'Enum' Types not larger than 'Int' -- | These are implemented by converting Enum to Int and using integral - -- operations. + -- operations. Note small Enum types are always bounded though they may not + -- have a Bounded instance. , enumerateFromSmall , enumerateFromToSmall , enumerateFromThenSmall , enumerateFromThenToSmall - -- ** 'Fractional' Types - -- | These are simply specialization of Num based operations to Fractional - -- types. - , enumerateFromFractional - , enumerateFromToFractional - , enumerateFromThenFractional - , enumerateFromThenToFractional + -- ** 'RealFloat' Types + -- | For floating point numbers if the increment is less than the precision + -- then it just gets lost. Therefore we cannot always increment it + -- correctly by just repeated addition. + -- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15 + -- 9007199254740992 + 2 :: Double => 9.007199254740994e15 + -- + -- Instead we accumulate the increment counter and compute the increment + -- every time before adding it to the starting number. + -- + , enumerateFromRealFloat + , enumerateFromToRealFloat + , enumerateFromThenRealFloat + , enumerateFromThenToRealFloat -- ** Convenient functions using 'Enumerable' type class , enumerate @@ -72,7 +75,15 @@ module Streamly.Internal.Data.Stream.Enumeration -- * Deprecated , enumerateFromBounded , enumerateFromThenSmallBounded + , enumerateFromIntegral + , enumerateFromThenIntegral + , enumerateFromToIntegral + , enumerateFromThenToIntegral , enumerateFromStepIntegral + , enumerateFromFractional + , enumerateFromToFractional + , enumerateFromThenFractional + , enumerateFromThenToFractional ) where @@ -81,6 +92,7 @@ where import Data.Fixed import Data.Functor.Identity (Identity(..)) import Data.Int +import Data.Ord (Down(..)) import Data.Ratio import Data.Word import Numeric.Natural @@ -99,251 +111,345 @@ import Prelude hiding (takeWhile) -- Enumeration of Num ------------------------------------------------------------------------------ --- | For floating point numbers if the increment is less than the precision then --- it just gets lost. Therefore we cannot always increment it correctly by just --- repeated addition. --- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15 --- 9007199254740992 + 2 :: Double => 9.007199254740994e15 +-- | @enumerateFromStepNum from step@ generates an infinite stream whose first +-- element is @from@ and the successive elements are in increments of @step@. -- --- Instead we accumulate the increment counter and compute the increment --- every time before adding it to the starting number. +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepNum 0 2 +-- [0,2,4,6] +-- +-- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepNum 0 (-2) +-- [0,-2,-4] +-- +-- CAUTION: This is NOT NUMERICALLY STABLE for floating point numbers. -- --- This works for Integrals as well as floating point numbers, but --- enumerateFromStepIntegralUnbounded is faster for integrals. +-- CAUTION: This will overflow or underflow and wrap around for bounded types. {-# INLINE_NORMAL enumerateFromStepNum #-} -enumerateFromStepNum :: (Monad m, Num a) => a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromStepNum from stride = - unfold Unfold.enumerateFromStepNum (from, stride) -#else +enumerateFromStepNum :: (Applicative m, Num a) => a -> a -> Stream m a enumerateFromStepNum !from !stride = - Stream (const Producer.enumerateFromStepNum) (from, stride, 0) -#endif + Stream (const Producer.enumerateFromStep) (from, stride) +-- | @enumerateFromThenNum from then@ generates a stream whose first element is +-- @from@, the second element is @then@ and the successive elements are in +-- increments of @then - from@. +-- +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenNum (254 :: Word8) 255 +-- [254,255,0,1] +-- +-- >>> import Data.Int (Int8) +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenNum (-126 :: Int8) (-127) +-- [-126,-127,-128,127] +-- +-- CAUTION: This is NOT NUMERICALLY STABLE for floating point numbers. +-- +-- CAUTION: This will overflow or underflow and wrap around for bounded types. {-# INLINE_NORMAL enumerateFromThenNum #-} -enumerateFromThenNum :: (Monad m, Num a) => a -> a -> Stream m a +enumerateFromThenNum :: (Applicative m, Num a) => a -> a -> Stream m a enumerateFromThenNum from next = enumerateFromStepNum from (next - from) -{-# INLINE_NORMAL enumerateFromNum #-} -enumerateFromNum :: (Monad m, Num a) => a -> Stream m a -enumerateFromNum from = enumerateFromStepNum from 1 - ------------------------------------------------------------------------------- --- Enumeration of Integrals ------------------------------------------------------------------------------- - --- | @enumerateFromStepIntegralUnbounded from step@ generates an infinite --- stream whose first element is @from@ and the successive elements are in --- increments of @step@. +-- | Same as: -- --- CAUTION: This function is not safe for finite integral types. It does not --- check for overflow, underflow or bounds. +-- >> enumerateFromThenNum from (from + 1) -- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromStepIntegralUnbounded 0 2 --- [0,2,4,6] --- --- >>> Stream.toList $ Stream.take 3 $ Stream.enumerateFromStepIntegralUnbounded 0 (-2) --- [0,-2,-4] --- -{-# INLINE_NORMAL enumerateFromStepIntegralUnbounded #-} -enumerateFromStepIntegralUnbounded :: (Integral a, Monad m) => a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromStepIntegralUnbounded from stride = - unfold Unfold.enumerateFromStepIntegralUnbounded (from, stride) -#else -enumerateFromStepIntegralUnbounded from stride = - from `seq` stride `seq` - Stream (const Producer.enumerateFromStepIntegral) (from, stride) -#endif +{-# INLINE_NORMAL enumerateFromNum #-} +enumerateFromNum :: (Applicative m, Num a) => a -> Stream m a +enumerateFromNum from = enumerateFromStepNum from 1 -{-# DEPRECATED enumerateFromStepIntegral "Please use enumerateFromStepIntegralUnbounded instead." #-} -{-# INLINE enumerateFromStepIntegral #-} -enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a -enumerateFromStepIntegral = enumerateFromStepIntegralUnbounded +{-# INLINE_NORMAL enumerateDownFromNum #-} +enumerateDownFromNum :: (Applicative m, Num a) => a -> Stream m a +enumerateDownFromNum from = enumerateFromStepNum from (-1) --- | Enumerate an 'Integral' type in steps up to a given limit. --- @enumerateFromThenToIntegral from then to@ generates a finite stream whose +-- | @enumerateFromThenToNum from then to@ generates a finite stream whose -- first element is @from@, the second element is @then@ and the successive -- elements are in increments of @then - from@ up to @to@. -- --- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 2 6 +-- >>> Stream.toList $ Stream.enumerateFromThenToNum 0 2 6 -- [0,2,4,6] -- --- >>> Stream.toList $ Stream.enumerateFromThenToIntegral 0 (-2) (-6) +-- >>> Stream.toList $ Stream.enumerateFromThenToNum 0 (-2) (-6) -- [0,-2,-4,-6] -- -{-# INLINE_NORMAL enumerateFromThenToIntegral #-} -enumerateFromThenToIntegral - :: (Monad m, Integral a) +{-# INLINE_NORMAL enumerateFromThenToNum #-} +enumerateFromThenToNum + :: (Applicative m, Num a, Ord a) => a -> a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromThenToIntegral from next to = - unfold Unfold.enumerateFromThenToIntegral (from, next, to) -#else -enumerateFromThenToIntegral from next to = +enumerateFromThenToNum from next to = Stream - (const Producer.enumerateFromThenToIntegral) + (const Producer.enumerateFromThenTo) (Producer.EnumInit from next to) -#endif --- | Like 'enumerateFromThenToIntegral' but a simplified version that only --- works in the upward direction i.e. return empty stream if @then < from@. +-- | Like 'enumerateFromThenToNum' but a simplified version that only works in +-- the upward direction. It returns an empty stream if @then < from@. -- --- >>> Stream.toList $ Stream.enumerateFromThenUpToIntegral 0 2 6 +-- >>> Stream.toList $ Stream.enumerateUpFromThenToNum 0 2 6 -- [0,2,4,6] -- -{-# INLINE_NORMAL enumerateFromThenUpToIntegral #-} -enumerateFromThenUpToIntegral - :: (Monad m, Integral a) - => a -> a -> a -> Stream m a -enumerateFromThenUpToIntegral from next to = +{-# INLINE_NORMAL enumerateUpFromThenToNum #-} +enumerateUpFromThenToNum + :: (Applicative m, Num a, Ord a) => a -> a -> a -> Stream m a +enumerateUpFromThenToNum from next to = Stream - (const Producer.enumerateFromThenUpToIntegral) + (const Producer.enumerateUpFromThenTo) (Producer.EnumUpInit from next to) --- | Enumerate an 'Integral' type in steps. @enumerateFromThenIntegral from --- then@ generates a stream whose first element is @from@, the second element --- is @then@ and the successive elements are in increments of @then - from@. --- The stream is bounded by the size of the 'Integral' type. +-- | Like 'enumerateFromThenToNum' but a simplified version that only works in +-- the downward direction. It returns an empty stream if @then > from@. -- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) 2 --- [0,2,4,6] +-- >>> Stream.toList $ Stream.enumerateDownFromThenToNum 6 4 0 +-- [6,4,2,0] -- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenIntegral (0 :: Int) (-2) --- [0,-2,-4,-6] +{-# INLINE_NORMAL enumerateDownFromThenToNum #-} +enumerateDownFromThenToNum + :: (Applicative m, Num a, Ord a) => a -> a -> a -> Stream m a +enumerateDownFromThenToNum from next to = + Stream + (const Producer.enumerateDownFromThenTo) + (Producer.EnumUpInit (Down from) (Down next) (Down to)) + +-- | @enumerateFromToNum from to@ generates a finite stream whose first element +-- is @from@ and successive elements are in increments of @1@ up to @to@. -- -{-# INLINE_NORMAL enumerateFromThenIntegral #-} -enumerateFromThenIntegral :: (Monad m, Integral a, Bounded a) +-- >>> Stream.toList $ Stream.enumerateFromToNum (254 :: Word8) 255 +-- [254,255] +-- +{-# INLINE enumerateFromToNum #-} +enumerateFromToNum :: (Monad m, Num a, Ord a) => a -> a -> Stream m a +enumerateFromToNum from to = + -- See the perf note in the Unfold impl of this. the alternate + -- takeWhile based implementation looks better. + -- enumerateUpFromThenToNum from (from + 1) to + takeWhile (<= to) + $ takeEndBy (== to) $ enumerateFromStepNum from 1 + +{-# INLINE enumerateDownFromToNum #-} +enumerateDownFromToNum :: (Monad m, Num a, Ord a) => a -> a -> Stream m a +enumerateDownFromToNum from to = + -- See the perf note in the Unfold impl of this. the alternate + -- takeWhile based implementation looks better. + takeWhile (>= to) + $ takeEndBy (== to) $ enumerateFromStepNum from (-1) + +------------------------------------------------------------------------------ +-- Enumeration of Bounded Num +------------------------------------------------------------------------------ + +-- | @enumerateFromThenBoundedNum from then@ generates a stream whose first +-- element is @from@, the second element is @then@ and the successive elements +-- are in increments of @then - from@. The stream is bounded by the size of the +-- 'Integral' type. +-- +-- >>> Stream.toList $ Stream.enumerateFromThenBoundedNum (254 :: Word8) 255 +-- [254,255] +-- +-- >>> import Data.Int (Int8) +-- >>> Stream.toList $ Stream.enumerateFromThenBoundedNum (-126 :: Int8) (-127) +-- [-126,-127,-128] +-- +{-# INLINE_NORMAL enumerateFromThenBoundedNum #-} +enumerateFromThenBoundedNum :: (Applicative m, Num a, Ord a, Bounded a) => a -> a -> Stream m a -#ifdef USE_UNFOLDS_EVERYWHERE -enumerateFromThenIntegral from next = - unfold Unfold.enumerateFromThenIntegral (from, next) -#else -enumerateFromThenIntegral from next = - enumerateFromThenToIntegral +enumerateFromThenBoundedNum from next = + enumerateFromThenToNum from next (if next >= from then maxBound else minBound) -#endif --- | Enumerate an 'Integral' type up to a given limit. --- @enumerateFromToIntegral from to@ generates a finite stream whose first --- element is @from@ and successive elements are in increments of @1@ up to --- @to@. +-- | @enumerateFromBoundedNum from@ generates a stream whose first element is +-- @from@ and the successive elements are in increments of @1@. The stream is +-- bounded by the size of the type. -- --- >>> Stream.toList $ Stream.enumerateFromToIntegral 0 4 --- [0,1,2,3,4] +-- >>> Stream.toList $ Stream.enumerateFromBoundedNum (254 :: Word8) +-- [254,255] -- +{-# INLINE enumerateFromBoundedNum #-} +enumerateFromBoundedNum :: + (Monad m, Num a, Ord a, Bounded a) => a -> Stream m a +enumerateFromBoundedNum from = + enumerateFromToNum from maxBound + +{-# INLINE enumerateDownFromBoundedNum #-} +enumerateDownFromBoundedNum :: + (Monad m, Num a, Ord a, Bounded a) => a -> Stream m a +enumerateDownFromBoundedNum from = + enumerateDownFromToNum from maxBound + +------------------------------------------------------------------------------ +-- Enumeration of Integrals +------------------------------------------------------------------------------ + +{-# DEPRECATED enumerateFromStepIntegral "Please use enumerateFromStepNum instead." #-} +{-# INLINE enumerateFromStepIntegral #-} +enumerateFromStepIntegral :: (Integral a, Monad m) => a -> a -> Stream m a +enumerateFromStepIntegral = enumerateFromStepNum + +{-# DEPRECATED enumerateFromThenToIntegral "Please use enumerateFromThenToNum instead." #-} +{-# INLINE_NORMAL enumerateFromThenToIntegral #-} +enumerateFromThenToIntegral + :: (Monad m, Integral a) + => a -> a -> a -> Stream m a +enumerateFromThenToIntegral = enumerateFromThenToNum + +{-# DEPRECATED enumerateFromThenIntegral "Please use enumerateFromThenBoundedNum instead." #-} +{-# INLINE_NORMAL enumerateFromThenIntegral #-} +enumerateFromThenIntegral :: (Monad m, Integral a, Bounded a) + => a -> a -> Stream m a +enumerateFromThenIntegral = enumerateFromThenBoundedNum + +{-# DEPRECATED enumerateFromToIntegral "Please use enumerateFromToNum instead." #-} {-# INLINE enumerateFromToIntegral #-} enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a -enumerateFromToIntegral from to = - takeWhile (<= to) - $ takeEndBy (== to) $ enumerateFromStepIntegralUnbounded from 1 +enumerateFromToIntegral = enumerateFromToNum --- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a --- stream whose first element is @from@ and the successive elements are in --- increments of @1@. The stream is bounded by the size of the 'Integral' type. --- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromIntegral (0 :: Int) --- [0,1,2,3] --- +{-# DEPRECATED enumerateFromIntegral "Please use enumerateFromBoundedNum instead." #-} {-# INLINE enumerateFromIntegral #-} enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a -enumerateFromIntegral from = enumerateFromToIntegral from maxBound - -{-# INLINE enumerateFromIntegralUnbounded #-} -enumerateFromIntegralUnbounded :: (Integral a, Monad m) => a -> Stream m a -enumerateFromIntegralUnbounded from = enumerateFromStepIntegralUnbounded from 1 - -{-# INLINE enumerateFromThenIntegralUnbounded #-} -enumerateFromThenIntegralUnbounded :: (Integral a, Monad m) => a -> a -> Stream m a -enumerateFromThenIntegralUnbounded from next = - enumerateFromStepIntegralUnbounded from (next - from) +enumerateFromIntegral = enumerateFromBoundedNum ------------------------------------------------------------------------------ --- Enumeration of Fractionals +-- Enumeration of RealFloat ------------------------------------------------------------------------------ -- We cannot write a general function for Num. The only way to write code -- portable between the two is to use a 'Real' constraint and convert between -- Fractional and Integral using fromRational which is horribly slow. --- Even though the underlying implementation of enumerateFromFractional and +-- Even though the underlying implementation of enumerateFromRealFloat and -- enumerateFromThenFractional works for any 'Num' we have restricted these to -- 'Fractional' because these do not perform any bounds check, in contrast to -- integral versions and are therefore not equivalent substitutes for those. +-- | For floating point numbers if the increment is less than the precision then +-- it just gets lost. Therefore we cannot always increment it correctly by just +-- repeated addition. +-- 9007199254740992 + 1 + 1 :: Double => 9.007199254740992e15 +-- 9007199254740992 + 2 :: Double => 9.007199254740994e15 +-- +-- Instead we accumulate the increment counter and compute the increment +-- every time before adding it to the starting number. +-- +{-# INLINE_NORMAL enumerateFromStepRealFloat #-} +enumerateFromStepRealFloat :: (Applicative m, RealFloat a) => a -> a -> Stream m a +enumerateFromStepRealFloat !from !stride = + Stream (const Producer.enumerateFromStepRealFloat) (from, stride, 0) + -- | Numerically stable enumeration from a 'Fractional' number in steps of size --- @1@. @enumerateFromFractional from@ generates a stream whose first element +-- @1@. @enumerateFromRealFloat from@ generates a stream whose first element -- is @from@ and the successive elements are in increments of @1@. No overflow -- or underflow checks are performed. -- -- This is the equivalent to 'enumFrom' for 'Fractional' types. For example: -- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromFractional 1.1 +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromRealFloat 1.1 -- [1.1,2.1,3.1,4.1] -- -{-# INLINE enumerateFromFractional #-} -enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a -enumerateFromFractional = enumerateFromNum +{-# INLINE enumerateFromRealFloat #-} +enumerateFromRealFloat :: (Applicative m, RealFloat a) => a -> Stream m a +enumerateFromRealFloat from = enumerateFromStepRealFloat from 1 --- | Numerically stable enumeration from a 'Fractional' number in steps. --- @enumerateFromThenFractional from then@ generates a stream whose first +-- | Numerically stable enumeration from a 'RealFloat' number in steps. +-- @enumerateFromThenRealFloat from then@ generates a stream whose first -- element is @from@, the second element is @then@ and the successive elements -- are in increments of @then - from@. No overflow or underflow checks are -- performed. -- --- This is the equivalent of 'enumFromThen' for 'Fractional' types. For +-- This is the equivalent of 'enumFromThen' for 'RealFloat' types. For -- example: -- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 2.1 +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenRealFloat 1.1 2.1 -- [1.1,2.1,3.1,4.1] -- --- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenFractional 1.1 (-2.1) +-- >>> Stream.toList $ Stream.take 4 $ Stream.enumerateFromThenRealFloat 1.1 (-2.1) -- [1.1,-2.1,-5.300000000000001,-8.500000000000002] -- -{-# INLINE enumerateFromThenFractional #-} -enumerateFromThenFractional - :: (Monad m, Fractional a) +{-# INLINE enumerateFromThenRealFloat #-} +enumerateFromThenRealFloat + :: (Applicative m, RealFloat a) => a -> a -> Stream m a -enumerateFromThenFractional = enumerateFromThenNum +enumerateFromThenRealFloat from next = + enumerateFromStepRealFloat from (next - from) --- | Numerically stable enumeration from a 'Fractional' number to a given --- limit. @enumerateFromToFractional from to@ generates a finite stream whose +-- | Numerically stable enumeration from a 'RealFloat' number to a given +-- limit. @enumerateFromToRealFloat from to@ generates a finite stream whose -- first element is @from@ and successive elements are in increments of @1@ up -- to @to@. -- --- This is the equivalent of 'enumFromTo' for 'Fractional' types. For +-- This is the equivalent of 'enumFromTo' for 'RealFloat' types. For -- example: -- --- >>> Stream.toList $ Stream.enumerateFromToFractional 1.1 4 +-- >>> Stream.toList $ Stream.enumerateFromToRealFloat 1.1 4 -- [1.1,2.1,3.1,4.1] -- --- >>> Stream.toList $ Stream.enumerateFromToFractional 1.1 4.6 +-- >>> Stream.toList $ Stream.enumerateFromToRealFloat 1.1 4.6 -- [1.1,2.1,3.1,4.1,5.1] -- -- Notice that the last element is equal to the specified @to@ value after -- rounding to the nearest integer. -- -{-# INLINE_NORMAL enumerateFromToFractional #-} -enumerateFromToFractional - :: (Monad m, Fractional a, Ord a) +{-# INLINE_NORMAL enumerateFromToRealFloat #-} +enumerateFromToRealFloat + :: (Monad m, RealFloat a) => a -> a -> Stream m a -enumerateFromToFractional from to = - takeWhile (<= to + 1 / 2) $ enumerateFromStepNum from 1 +enumerateFromToRealFloat from to = + takeWhile (<= to + 1 / 2) $ enumerateFromStepRealFloat from 1 --- | Numerically stable enumeration from a 'Fractional' number in steps up to a --- given limit. @enumerateFromThenToFractional from then to@ generates a +-- | Numerically stable enumeration from a 'RealFloat' number in steps up to a +-- given limit. @enumerateFromThenToRealFloat from then to@ generates a -- finite stream whose first element is @from@, the second element is @then@ -- and the successive elements are in increments of @then - from@ up to @to@. -- --- This is the equivalent of 'enumFromThenTo' for 'Fractional' types. For +-- This is the equivalent of 'enumFromThenTo' for 'RealFloat' types. For -- example: -- --- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 2 6 +-- >>> Stream.toList $ Stream.enumerateFromThenToRealFloat 0.1 2 6 -- [0.1,2.0,3.9,5.799999999999999] -- --- >>> Stream.toList $ Stream.enumerateFromThenToFractional 0.1 (-2) (-6) +-- >>> Stream.toList $ Stream.enumerateFromThenToRealFloat 0.1 (-2) (-6) -- [0.1,-2.0,-4.1000000000000005,-6.200000000000001] -- +{-# INLINE_NORMAL enumerateFromThenToRealFloat #-} +enumerateFromThenToRealFloat + :: (Monad m, RealFloat a) + => a -> a -> a -> Stream m a +enumerateFromThenToRealFloat from next to = + takeWhile predicate $ enumerateFromThenRealFloat from next + where + mid = (next - from) / 2 + predicate | next >= from = (<= to + mid) + | otherwise = (>= to + mid) + +------------------------------------------------------------------------------ +-- Enumeration of Fractionals (Deprecated) +------------------------------------------------------------------------------ + +{-# INLINE_NORMAL enumerateFromStepFractional #-} +enumerateFromStepFractional :: (Monad m, Fractional a) => a -> a -> Stream m a +enumerateFromStepFractional !from !stride = Stream step (from, stride, 0) + + where + + {-# INLINE_LATE step #-} + step _ (from1, stride1, i) = + pure $ Yield (from1 + i * stride1) (from1, stride1, i + 1) + +{-# DEPRECATED enumerateFromFractional "Please use enumerateFromRealFloat instead." #-} +{-# INLINE enumerateFromFractional #-} +enumerateFromFractional :: (Monad m, Fractional a) => a -> Stream m a +enumerateFromFractional from = enumerateFromStepFractional from 1 + +{-# DEPRECATED enumerateFromThenFractional "Please use enumerateFromThenRealFloat instead." #-} +{-# INLINE enumerateFromThenFractional #-} +enumerateFromThenFractional + :: (Monad m, Fractional a) + => a -> a -> Stream m a +enumerateFromThenFractional from next = + enumerateFromStepFractional from (next - from) + +{-# DEPRECATED enumerateFromToFractional "Please use enumerateFromToRealFloat instead." #-} +{-# INLINE_NORMAL enumerateFromToFractional #-} +enumerateFromToFractional + :: (Monad m, Fractional a, Ord a) + => a -> a -> Stream m a +enumerateFromToFractional from to = + takeWhile (<= to + 1 / 2) $ enumerateFromStepFractional from 1 + +{-# DEPRECATED enumerateFromThenToFractional "Please use enumerateFromThenToRealFloat instead." #-} {-# INLINE_NORMAL enumerateFromThenToFractional #-} enumerateFromThenToFractional :: (Monad m, Fractional a, Ord a) @@ -365,7 +471,7 @@ enumerateFromThenToFractional from next to = enumerateFromToSmall :: (Monad m, Enum a) => a -> a -> Stream m a enumerateFromToSmall from to = fmap toEnum - $ enumerateFromToIntegral (fromEnum from) (fromEnum to) + $ enumerateFromToNum (fromEnum from) (fromEnum to) -- | 'enumerateFromThenTo' for 'Enum' types not larger than 'Int'. -- @@ -374,7 +480,7 @@ enumerateFromThenToSmall :: (Monad m, Enum a) => a -> a -> a -> Stream m a enumerateFromThenToSmall from next to = fmap toEnum - $ enumerateFromThenToIntegral + $ enumerateFromThenToNum (fromEnum from) (fromEnum next) (fromEnum to) ------------------------------------------------------------------------------- @@ -427,7 +533,7 @@ enumerateFromSmall from = enumerateFromToSmall from maxBound -- generate a stream instead of a list. Use the functions in -- "Streamly.Internal.Data.Stream.Enumeration" module to define new instances. -- -class Enum a => Enumerable a where +class Enumerable a where -- | @enumerateFrom from@ generates a stream starting with the element -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or @@ -554,59 +660,63 @@ ENUMERABLE_BOUNDED_SMALL(Bool) ENUMERABLE_BOUNDED_SMALL(Ordering) ENUMERABLE_BOUNDED_SMALL(Char) --- For bounded Integral Enum types, may be larger than Int. -#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ -instance Enumerable INTEGRAL_TYPE where { \ +-- For bounded Integral Enum types, may be larger than Int. 'enumerateFrom' +-- and 'enumerateFromThen' use the bounded Num functions so that they stop at +-- 'maxBound'/'minBound' instead of overflowing and wrapping around. +#define ENUMERABLE_BOUNDED_NUM(TYPE_NAME) \ +instance Enumerable TYPE_NAME where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom = enumerateFromBoundedNum; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenBoundedNum; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToNum; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToNum } + +ENUMERABLE_BOUNDED_NUM(Int) +ENUMERABLE_BOUNDED_NUM(Int8) +ENUMERABLE_BOUNDED_NUM(Int16) +ENUMERABLE_BOUNDED_NUM(Int32) +ENUMERABLE_BOUNDED_NUM(Int64) +ENUMERABLE_BOUNDED_NUM(Word) +ENUMERABLE_BOUNDED_NUM(Word8) +ENUMERABLE_BOUNDED_NUM(Word16) +ENUMERABLE_BOUNDED_NUM(Word32) +ENUMERABLE_BOUNDED_NUM(Word64) + +-- For unbounded 'Num' types that are not 'RealFloat' (no numerical +-- stability concerns since these are either exact integrals or exact +-- rational/fixed-precision types). +#define ENUMERABLE_UNBOUNDED_NUM(TYPE_NAME,CONSTRAINT) \ +instance (CONSTRAINT) => Enumerable TYPE_NAME where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegral; \ + enumerateFrom = enumerateFromNum; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegral; \ + enumerateFromThen = enumerateFromThenNum; \ {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegral; \ + enumerateFromTo = enumerateFromToNum; \ {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegral } - -ENUMERABLE_BOUNDED_INTEGRAL(Int) -ENUMERABLE_BOUNDED_INTEGRAL(Int8) -ENUMERABLE_BOUNDED_INTEGRAL(Int16) -ENUMERABLE_BOUNDED_INTEGRAL(Int32) -ENUMERABLE_BOUNDED_INTEGRAL(Int64) -ENUMERABLE_BOUNDED_INTEGRAL(Word) -ENUMERABLE_BOUNDED_INTEGRAL(Word8) -ENUMERABLE_BOUNDED_INTEGRAL(Word16) -ENUMERABLE_BOUNDED_INTEGRAL(Word32) -ENUMERABLE_BOUNDED_INTEGRAL(Word64) - --- For unbounded Integral Enum types. -#define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \ -instance Enumerable INTEGRAL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegralUnbounded; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegralUnbounded; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegral; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegral } - -ENUMERABLE_UNBOUNDED_INTEGRAL(Integer) -ENUMERABLE_UNBOUNDED_INTEGRAL(Natural) - -#define ENUMERABLE_FRACTIONAL(FRACTIONAL_TYPE,CONSTRAINT) \ -instance (CONSTRAINT) => Enumerable FRACTIONAL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromFractional; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenFractional; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToFractional; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToFractional } - -ENUMERABLE_FRACTIONAL(Float,) -ENUMERABLE_FRACTIONAL(Double,) -ENUMERABLE_FRACTIONAL((Fixed a),HasResolution a) -ENUMERABLE_FRACTIONAL((Ratio a),Integral a) + enumerateFromThenTo = enumerateFromThenToNum } + +ENUMERABLE_UNBOUNDED_NUM(Integer,) +ENUMERABLE_UNBOUNDED_NUM(Natural,) +ENUMERABLE_UNBOUNDED_NUM((Fixed a),HasResolution a) +ENUMERABLE_UNBOUNDED_NUM((Ratio a),Integral a) + +#define ENUMERABLE_REAL_FLOAT(FRACTIONAL_TYPE) \ +instance Enumerable FRACTIONAL_TYPE where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom = enumerateFromRealFloat; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenRealFloat; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToRealFloat; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToRealFloat } + +ENUMERABLE_REAL_FLOAT(Float) +ENUMERABLE_REAL_FLOAT(Double) instance Enumerable a => Enumerable (Identity a) where {-# INLINE enumerateFrom #-} diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index d0306b09e6..dfa55cc948 100644 --- a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs @@ -22,6 +22,10 @@ -- in this module can be used to define them. Alternatively, these functions -- can be used directly. -- +-- XXX Will it be better to design the API around "stride" e.g. +-- enumerateFromStrideTo? If "stride" is specified as an argument then there is +-- no issue of overflow of stride itself. +-- module Streamly.Internal.Data.Unfold.Enumeration ( -- ** Enumerable Type Class @@ -29,60 +33,66 @@ module Streamly.Internal.Data.Unfold.Enumeration -- ** 'Num' Type class Types -- | Most general operations via the 'Num' type class. All other - -- enumeraitons can be expressed in terms of these. These are numerically - -- stable for floating precision numbers. For that reason these may be - -- slightly less efficient than intergal operations. + -- enumeraitons can be expressed in terms of these. + -- + -- These are numerically unstable for floating precision numbers. Use the + -- RealFloat specific operations for numerical stability. -- - -- These are unbounded, can overflow, there are no bounds possible for - -- 'Num'. + -- The "To" vesions are overflow protected, others can overflow and wrap + -- around for bounded types. Use the "To" versions with max or min bound to + -- stop at the bound or use the bounded versions. , enumerateFromStepNum , enumerateFromNum + , enumerateDownFromNum , enumerateFromThenNum + , enumerateFromToNum + , enumerateDownFromToNum + , enumerateFromThenToNum + , enumerateUpFromThenToNum + , enumerateDownFromThenToNum - -- ** 'Integral' Type class Types (Unbounded Enumeration) - -- | More efficient than 'Num' based operations for 'Integral' types. - , enumerateFromStepIntegralUnbounded - , enumerateFromIntegralUnbounded - , enumerateFromThenIntegralUnbounded - - -- ** 'Integral' Types (Bounded Enumeration) - -- | These are implemented in terms of integral operations using maxBound - -- or minBound as the terminating codition. This is the default behavior - -- for 'Integral' enumeration, used when no 'Unbounded' suffix is - -- specified; use the explicit 'Unbounded' suffixed operations above for - -- faster, unchecked enumeration. - , enumerateFromToIntegral - , enumerateFromThenToIntegral - - -- XXX For these two the behavior changed from unbounded to bounded but - -- that is a safe direction. - , enumerateFromIntegral - , enumerateFromThenIntegral + -- ** Bounded Num Types + , enumerateFromBoundedNum + , enumerateFromThenBoundedNum + , enumerateDownFromBoundedNum -- ** 'Enum' Types not larger than 'Int' -- | These are implemented by converting Enum to Int and using integral - -- operations. + -- operations. Note small Enum types are always bounded though they may not + -- have a Bounded instance. , enumerateFromSmall , enumerateFromToSmall , enumerateFromThenSmall , enumerateFromThenToSmall - -- ** 'Fractional' Types - -- | These are simply specialization of Num based operations to Fractional - -- types. - , enumerateFromFractional - , enumerateFromToFractional - , enumerateFromThenFractional - , enumerateFromThenToFractional + -- ** 'RealFloat' Types + -- | For floating point numbers if the increment is less than the + -- precision then it just gets lost. Therefore we cannot always increment + -- it correctly by just repeated addition. Instead we accumulate the + -- increment counter and compute the increment every time before adding + -- it to the starting number. This is numerically stable but slower than + -- the 'Num' based operations. + , enumerateFromRealFloat + , enumerateFromToRealFloat + , enumerateFromThenRealFloat + , enumerateFromThenToRealFloat -- * Deprecated , enumerateFromStepIntegral + , enumerateFromIntegral + , enumerateFromThenIntegral + , enumerateFromToIntegral + , enumerateFromThenToIntegral , enumerateFromIntegralBounded , enumerateFromThenIntegralBounded - , enumerateFromSmallBounded - , enumerateFromThenSmallBounded , enumerateFromToIntegralBounded , enumerateFromThenToIntegralBounded + , enumerateFromSmallBounded + , enumerateFromThenSmallBounded + , enumerateFromFractional + , enumerateFromThenFractional + , enumerateFromToFractional + , enumerateFromThenToFractional ) where @@ -92,6 +102,7 @@ where import Data.Fixed import Data.Bifunctor (bimap) import Data.Int +import Data.Ord (Down(..)) import Data.Ratio import Data.Word import Numeric.Natural @@ -122,19 +133,21 @@ import Prelude hiding (map, takeWhile, zipWith) -- -- @ -- --- The implementation is numerically stable for floating point values. +-- CAUTION: This is NOT NUMERICALLY STABLE for floating point numbers. Use +-- 'enumerateFromStepRealFloat' for numerical stability. -- --- Note 'enumerateFromStepIntegralUnbounded' is faster for integrals. +-- CAUTION: This will overflow or underflow and wrap around for bounded +-- types. -- -- /Internal/ -- {-# INLINE enumerateFromStepNum #-} -enumerateFromStepNum :: (Monad m, Num a) => Unfold m (a, a) a -enumerateFromStepNum = Unfold Producer.enumerateFromStepNum inject +enumerateFromStepNum :: (Applicative m, Num a) => Unfold m (a, a) a +enumerateFromStepNum = Unfold Producer.enumerateFromStep inject where - inject (!from, !stride) = return (from, stride, 0) + inject (!from, !stride) = pure (from, stride) -- | Same as 'enumerateFromStepNum (from, next)' using a stride of @next - from@: -- @@ -150,21 +163,15 @@ enumerateFromStepNum = Unfold Producer.enumerateFromStepNum inject -- -- @ -- --- The implementation is numerically stable for floating point values. --- --- Note that 'enumerateFromThenIntegralUnbounded' is faster for integrals. +-- CAUTION: This is NOT NUMERICALLY STABLE for floating point numbers. -- --- Note that in the strange world of floating point numbers, using --- @enumerateFromThenNum (from, from + 1)@ is almost exactly the same as --- @enumerateFromStepNum (from, 1) but not precisely the same. Because @(from + --- 1) - from@ is not exactly 1, it may lose some precision, the loss may also --- be aggregated in each step, if you want that precision then use --- 'enumerateFromStepNum' instead. +-- CAUTION: This will overflow or underflow and wrap around for bounded +-- types. -- -- /Internal/ -- {-# INLINE enumerateFromThenNum #-} -enumerateFromThenNum :: (Monad m, Num a) => Unfold m (a, a) a +enumerateFromThenNum :: (Applicative m, Num a) => Unfold m (a, a) a enumerateFromThenNum = lmap (\(from, next) -> (from, next - from)) enumerateFromStepNum @@ -177,138 +184,275 @@ enumerateFromThenNum = -- -- @ -- --- Also, same as 'enumerateFromThenNum' using a stride of 1 but see the note in --- 'enumerateFromThenNum' about the loss of precision: --- --- @ --- >>> enumerateFromNum = lmap (\from -> (from, from + 1)) Unfold.enumerateFromThenNum --- >>> Stream.toList $ Stream.take 6 $ Stream.unfold enumerateFromNum (0.9) --- [0.9,1.9,2.9,3.8999999999999995,4.8999999999999995,5.8999999999999995] --- --- @ --- -- /Internal/ -- {-# INLINE enumerateFromNum #-} -enumerateFromNum :: (Monad m, Num a) => Unfold m a a +enumerateFromNum :: (Applicative m, Num a) => Unfold m a a enumerateFromNum = lmap (\from -> (from, 1)) enumerateFromStepNum ------------------------------------------------------------------------------- --- Enumeration of Integrals ------------------------------------------------------------------------------- - -{-# INLINE takeWhileMWithInput #-} -takeWhileMWithInput :: Monad m => - (a -> b -> m Bool) -> Unfold m a b -> Unfold m a b -takeWhileMWithInput f = map snd . takeWhileM (uncurry f) . carryInput +{-# INLINE enumerateDownFromNum #-} +enumerateDownFromNum :: (Applicative m, Num a) => Unfold m a a +enumerateDownFromNum = lmap (\from -> (from, -1)) enumerateFromStepNum --- | Can be used to enumerate unbounded integrals. This does not check for --- overflow or underflow for bounded integrals. +-- | Unfolds @(from, next, to)@ generating a finite stream whose first +-- element is @from@, the second element is @next@ and the successive +-- elements are in increments of @next - from@ up to @to@. -- --- /Internal/ -{-# INLINE_NORMAL enumerateFromStepIntegralUnbounded #-} -enumerateFromStepIntegralUnbounded, enumerateFromStepIntegral :: - (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromStepIntegralUnbounded = - Unfold Producer.enumerateFromStepIntegral inject +-- This is overflow safe. +-- +{-# INLINE enumerateFromThenToNum #-} +enumerateFromThenToNum :: (Applicative m, Num a, Ord a) => Unfold m (a, a, a) a +enumerateFromThenToNum = Unfold Producer.enumerateFromThenTo inject where - inject (from, stride) = from `seq` stride `seq` return (from, stride) + inject (from, next, to) = pure (Producer.EnumInit from next to) -RENAME(enumerateFromStepIntegral,enumerateFromStepIntegralUnbounded) +-- | Like 'enumerateFromThenToNum' but a simplified version that only works +-- in the upward direction. It returns an empty stream if @then < from@. +-- +{-# INLINE enumerateUpFromThenToNum #-} +enumerateUpFromThenToNum :: (Applicative m, Num a, Ord a) => Unfold m (a, a, a) a +enumerateUpFromThenToNum = Unfold Producer.enumerateUpFromThenTo inject -{-# INLINE enumerateFromThenToIntegral #-} -enumerateFromThenToIntegral, enumerateFromThenToIntegralBounded :: - (Monad m, Integral a) => Unfold m (a, a, a) a -enumerateFromThenToIntegral = - Unfold Producer.enumerateFromThenToIntegral inject + where + + inject (from, next, to) = pure (Producer.EnumUpInit from next to) + +-- | Like 'enumerateUpFromThenToNum' but a simplified version that only +-- works in the downward direction. It returns an empty stream if +-- @then > from@. +-- +{-# INLINE enumerateDownFromThenToNum #-} +enumerateDownFromThenToNum :: + (Applicative m, Num a, Ord a) => Unfold m (a, a, a) a +enumerateDownFromThenToNum = Unfold Producer.enumerateDownFromThenTo inject where - inject (from, next, to) = return (Producer.EnumInit from next to) + inject (from, next, to) = + pure (Producer.EnumUpInit (Down from) (Down next) (Down to)) -RENAME(enumerateFromThenToIntegralBounded,enumerateFromThenToIntegral) +-- | Unfolds @(from, to)@ generating a finite stream whose first element is +-- @from@ and successive elements are in increments of @1@ up to @to@. +-- +{-# INLINE enumerateFromToNum #-} +enumerateFromToNum :: (Monad m, Num a, Ord a) => Unfold m (a, a) a +enumerateFromToNum = + -- XXX this causes a large allocation regression in the + -- equations/unfoldCross bench + -- lmap (\(from, to) -> (from, from + 1, to)) enumerateUpFromThenToNum + map snd + $ takeWhile (\((_, to), b) -> b <= to) + $ takeEndBy (\((_, to), b) -> b == to) + $ carryInput + $ lmap (\(from, _) -> (from, 1)) enumerateFromStepNum -{-# INLINE enumerateFromThenIntegral #-} -enumerateFromThenIntegral, enumerateFromThenIntegralBounded :: - (Monad m, Integral a, Bounded a) => Unfold m (a, a) a -enumerateFromThenIntegral = - lmap toFromThenTo enumerateFromThenToIntegral +-- NOTE: we can use the Down functor to implement this. +{-# INLINE enumerateDownFromToNum #-} +enumerateDownFromToNum :: (Monad m, Num a, Ord a) => Unfold m (a, a) a +enumerateDownFromToNum = + -- See note in enumerateFromToNum, we switched to the alternate + -- implementation for this one too as a caution. + -- lmap (\(from, to) -> (from, from - 1, to)) enumerateDownFromThenToNum + map snd + $ takeWhile (\((_, to), b) -> b >= to) + $ takeEndBy (\((_, to), b) -> b == to) + $ carryInput + $ lmap (\(from, _) -> (from, -1)) enumerateFromStepNum + +------------------------------------------------------------------------------ +-- Enumeration of Bounded Num +------------------------------------------------------------------------------ + +-- | Unfolds @(from, then)@ generating a stream whose first element is +-- @from@, the second element is @then@ and the successive elements are in +-- increments of @then - from@. The stream is bounded by the size of the +-- 'Integral' type. +-- +-- /Internal/ +-- +{-# INLINE enumerateFromThenBoundedNum #-} +enumerateFromThenBoundedNum :: + (Applicative m, Num a, Ord a, Bounded a) => Unfold m (a, a) a +enumerateFromThenBoundedNum = lmap adapt enumerateFromThenToNum where - toFromThenTo (from, next) = + adapt (from, next) = (from, next, if next >= from then maxBound else minBound) -RENAME(enumerateFromThenIntegralBounded,enumerateFromThenIntegral) +-- | Unfolds @from@ generating a stream whose first element is @from@ and +-- the successive elements are in increments of @1@. The stream is bounded +-- by the size of the type. +-- +-- /Internal/ +-- +{-# INLINE enumerateFromBoundedNum #-} +enumerateFromBoundedNum :: (Monad m, Num a, Ord a, Bounded a) => Unfold m a a +enumerateFromBoundedNum = supplySecond maxBound enumerateFromToNum -{-# INLINE enumerateFromThenIntegralUnbounded #-} -enumerateFromThenIntegralUnbounded :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromThenIntegralUnbounded = - lmap - (\(from, next) -> (from, next - from)) - enumerateFromStepIntegralUnbounded +{-# INLINE enumerateDownFromBoundedNum #-} +enumerateDownFromBoundedNum :: + (Monad m, Num a, Ord a, Bounded a) => Unfold m a a +enumerateDownFromBoundedNum = supplySecond minBound enumerateDownFromToNum +------------------------------------------------------------------------------ +-- Enumeration of Integrals (Bounded) +------------------------------------------------------------------------------ + +{-# INLINE takeWhileMWithInput #-} +takeWhileMWithInput :: Monad m => + (a -> b -> m Bool) -> Unfold m a b -> Unfold m a b +takeWhileMWithInput f = map snd . takeWhileM (uncurry f) . carryInput + +{-# DEPRECATED enumerateFromStepIntegral "Please use enumerateFromStepNum instead." #-} +{-# INLINE enumerateFromStepIntegral #-} +enumerateFromStepIntegral :: (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromStepIntegral = enumerateFromStepNum + +{-# DEPRECATED enumerateFromThenToIntegral "Please use enumerateFromThenToNum instead." #-} +{-# INLINE enumerateFromThenToIntegral #-} +enumerateFromThenToIntegral, enumerateFromThenToIntegralBounded :: + (Monad m, Integral a) => Unfold m (a, a, a) a +enumerateFromThenToIntegral = enumerateFromThenToNum + +RENAME(enumerateFromThenToIntegralBounded,enumerateFromThenToIntegral) + +{-# DEPRECATED enumerateFromThenIntegral "Please use enumerateFromThenNum instead." #-} +{-# INLINE enumerateFromThenIntegral #-} +enumerateFromThenIntegral :: (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromThenIntegral = enumerateFromThenNum + +{-# DEPRECATED enumerateFromThenIntegralBounded "Please use enumerateFromThenBoundedNum instead." #-} +{-# INLINE enumerateFromThenIntegralBounded #-} +enumerateFromThenIntegralBounded :: + (Monad m, Integral a, Bounded a) => Unfold m (a, a) a +enumerateFromThenIntegralBounded = enumerateFromThenBoundedNum + +{-# DEPRECATED enumerateFromToIntegral "Please use enumerateFromToNum instead." #-} {-# INLINE enumerateFromToIntegral #-} enumerateFromToIntegral, enumerateFromToIntegralBounded :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromToIntegral = - map snd - $ takeWhile (\((_, to), b) -> b <= to) - $ takeEndBy (\((_, to), b) -> b == to) - $ carryInput - $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegralUnbounded +enumerateFromToIntegral = enumerateFromToNum RENAME(enumerateFromToIntegralBounded,enumerateFromToIntegral) +{-# DEPRECATED enumerateFromIntegral "Please use enumerateFromNum instead." #-} {-# INLINE enumerateFromIntegral #-} -enumerateFromIntegral, enumerateFromIntegralBounded :: - (Monad m, Integral a, Bounded a) => Unfold m a a -enumerateFromIntegral = supplySecond maxBound enumerateFromToIntegral - -RENAME(enumerateFromIntegralBounded,enumerateFromIntegral) +enumerateFromIntegral :: (Monad m, Integral a) => Unfold m a a +enumerateFromIntegral = enumerateFromNum -{-# INLINE enumerateFromIntegralUnbounded #-} -enumerateFromIntegralUnbounded :: (Monad m, Integral a) => Unfold m a a -enumerateFromIntegralUnbounded = - lmap (\from -> (from, 1)) enumerateFromStepIntegralUnbounded +{-# DEPRECATED enumerateFromIntegralBounded "Please use enumerateFromBoundedNum instead." #-} +{-# INLINE enumerateFromIntegralBounded #-} +enumerateFromIntegralBounded :: (Monad m, Integral a, Bounded a) => Unfold m a a +enumerateFromIntegralBounded = enumerateFromBoundedNum ------------------------------------------------------------------------------ --- Enumeration of Fractionals +-- Enumeration of RealFloat ------------------------------------------------------------------------------ -{-# INLINE_NORMAL enumerateFromFractional #-} -enumerateFromFractional :: (Monad m, Fractional a) => Unfold m a a -enumerateFromFractional = enumerateFromNum +-- | For floating point numbers if the increment is less than the precision +-- then it just gets lost. Therefore we cannot always increment it correctly +-- by just repeated addition. +-- +-- Instead we accumulate the increment counter and compute the increment +-- every time before adding it to the starting number. This is numerically +-- stable but slower than 'enumerateFromStepNum'. +-- +-- /Internal/ +-- +{-# INLINE enumerateFromStepRealFloat #-} +enumerateFromStepRealFloat :: (Applicative m, RealFloat a) => Unfold m (a, a) a +enumerateFromStepRealFloat = Unfold Producer.enumerateFromStepRealFloat inject -{-# INLINE_NORMAL enumerateFromThenFractional #-} -enumerateFromThenFractional :: (Monad m, Fractional a) => Unfold m (a, a) a -enumerateFromThenFractional = enumerateFromThenNum + where --- | Same as 'enumerateFromStepNum' with a step of 1 and enumerating up to the --- specified upper limit rounded to the nearest integral value: + inject (!from, !stride) = pure (from, stride, 0) + +{-# INLINE enumerateFromRealFloat #-} +enumerateFromRealFloat :: (Applicative m, RealFloat a) => Unfold m a a +enumerateFromRealFloat = lmap (\from -> (from, 1)) enumerateFromStepRealFloat + +{-# INLINE enumerateFromThenRealFloat #-} +enumerateFromThenRealFloat :: (Applicative m, RealFloat a) => Unfold m (a, a) a +enumerateFromThenRealFloat = + lmap (\(from, next) -> (from, next - from)) enumerateFromStepRealFloat + +-- | Same as 'enumerateFromStepRealFloat' with a step of 1 and enumerating up +-- to the specified upper limit rounded to the nearest integral value: -- -- @ --- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromToFractional (0.1, 6.3) +-- >>> Stream.toList $ Stream.unfold Unfold.enumerateFromToRealFloat (0.1, 6.3) -- [0.1,1.1,2.1,3.1,4.1,5.1,6.1] -- -- @ -- -- /Internal/ -- +{-# INLINE enumerateFromToRealFloat #-} +enumerateFromToRealFloat :: (Monad m, RealFloat a) => Unfold m (a, a) a +enumerateFromToRealFloat = + takeWhileMWithInput (\(_, to) b -> return $ b <= to + 1 / 2) + $ lmap (\(from, _) -> (from, 1)) enumerateFromStepRealFloat + +{-# INLINE enumerateFromThenToRealFloat #-} +enumerateFromThenToRealFloat :: (Monad m, RealFloat a) => Unfold m (a, a, a) a +enumerateFromThenToRealFloat = + takeWhileMWithInput cond $ lmap toFromStep enumerateFromStepRealFloat + + where + + toFromStep (from, next, _) = (from, next - from) + + cond (from, next, to) b = + let stride = next - from + in return + $ if next >= from + then b <= to + stride / 2 + else b >= to + stride / 2 + +------------------------------------------------------------------------------ +-- Enumeration of Fractionals (Deprecated) +------------------------------------------------------------------------------ + +{-# INLINE enumerateFromStepFractional #-} +enumerateFromStepFractional :: (Monad m, Fractional a) => Unfold m (a, a) a +enumerateFromStepFractional = Unfold step inject + + where + + inject (!from, !stride) = return (from, stride, 0) + + step (from, stride, i) = + return $ Yield (from + i * stride) (from, stride, i + 1) + +{-# DEPRECATED enumerateFromFractional "Please use enumerateFromRealFloat instead." #-} +{-# INLINE_NORMAL enumerateFromFractional #-} +enumerateFromFractional :: (Monad m, Fractional a) => Unfold m a a +enumerateFromFractional = + lmap (\from -> (from, 1)) enumerateFromStepFractional + +{-# DEPRECATED enumerateFromThenFractional "Please use enumerateFromThenRealFloat instead." #-} +{-# INLINE_NORMAL enumerateFromThenFractional #-} +enumerateFromThenFractional :: (Monad m, Fractional a) => Unfold m (a, a) a +enumerateFromThenFractional = + lmap (\(from, next) -> (from, next - from)) enumerateFromStepFractional + +{-# DEPRECATED enumerateFromToFractional "Please use enumerateFromToRealFloat instead." #-} {-# INLINE_NORMAL enumerateFromToFractional #-} enumerateFromToFractional :: (Monad m, Fractional a, Ord a) => Unfold m (a, a) a enumerateFromToFractional = takeWhileMWithInput (\(_, to) b -> return $ b <= to + 1 / 2) - $ lmap (\(from, _) -> (from, 1)) enumerateFromStepNum + $ lmap (\(from, _) -> (from, 1)) enumerateFromStepFractional +{-# DEPRECATED enumerateFromThenToFractional "Please use enumerateFromThenToRealFloat instead." #-} {-# INLINE enumerateFromThenToFractional #-} enumerateFromThenToFractional :: (Monad m, Fractional a, Ord a) => Unfold m (a, a, a) a enumerateFromThenToFractional = - takeWhileMWithInput cond $ lmap toFromStep enumerateFromStepNum + takeWhileMWithInput cond $ lmap toFromStep enumerateFromStepFractional where @@ -333,7 +477,7 @@ enumerateFromThenToFractional = {-# INLINE enumerateFromToSmall #-} enumerateFromToSmall :: (Monad m, Enum a) => Unfold m (a, a) a enumerateFromToSmall = - fmap toEnum (lmap (bimap fromEnum fromEnum) enumerateFromToIntegral) + fmap toEnum (lmap (bimap fromEnum fromEnum) enumerateFromToNum) -- | Enumerate from given starting Enum value 'from' and then Enum value 'next' -- and to Enum value 'to' with stride of (fromEnum next - fromEnum from) @@ -342,10 +486,10 @@ enumerateFromToSmall = -- /Internal/ -- {-# INLINE enumerateFromThenToSmall #-} -enumerateFromThenToSmall :: (Monad m, Enum a) => Unfold m (a, a, a) a +enumerateFromThenToSmall :: (Applicative m, Enum a) => Unfold m (a, a, a) a enumerateFromThenToSmall = let toInts (x, y, z) = (fromEnum x, fromEnum y, fromEnum z) - in fmap toEnum (lmap toInts enumerateFromThenToIntegral) + in fmap toEnum (lmap toInts enumerateFromThenToNum) ------------------------------------------------------------------------------- -- Bounded Enumeration of Enum types not larger than Int @@ -370,7 +514,7 @@ RENAME(enumerateFromSmallBounded,enumerateFromSmall) -- {-# INLINE enumerateFromThenSmall #-} enumerateFromThenSmall, enumerateFromThenSmallBounded - :: forall m a. (Monad m, Enum a, Bounded a) => + :: forall m a. (Applicative m, Enum a, Bounded a) => Unfold m (a, a) a enumerateFromThenSmall = let adapt (from, next) = @@ -381,7 +525,7 @@ enumerateFromThenSmall = then fromEnum (maxBound :: a) else fromEnum (minBound :: a) in (frm, nxt, to) - in fmap toEnum (lmap adapt enumerateFromThenToIntegral) + in fmap toEnum (lmap adapt enumerateFromThenToNum) RENAME(enumerateFromThenSmallBounded,enumerateFromThenSmall) @@ -394,7 +538,7 @@ RENAME(enumerateFromThenSmallBounded,enumerateFromThenSmall) -- generate a stream instead of a list. Use the functions in -- "Streamly.Internal.Data.Unfold.Enumeration" module to define new instances. -- -class Enum a => Enumerable a where +class Enumerable a where -- | Unfolds @from@ generating a stream starting with the element -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or @@ -477,59 +621,63 @@ ENUMERABLE_BOUNDED_SMALL(Bool) ENUMERABLE_BOUNDED_SMALL(Ordering) ENUMERABLE_BOUNDED_SMALL(Char) --- For bounded Integral Enum types, may be larger than Int. -#define ENUMERABLE_BOUNDED_INTEGRAL(INTEGRAL_TYPE) \ -instance Enumerable INTEGRAL_TYPE where { \ +-- For bounded Integral Enum types, may be larger than Int. 'enumerateFrom' +-- and 'enumerateFromThen' use the bounded Num functions so that they stop at +-- 'maxBound'/'minBound' instead of overflowing and wrapping around. +#define ENUMERABLE_BOUNDED_NUM(TYPE_NAME) \ +instance Enumerable TYPE_NAME where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegral; \ + enumerateFrom = enumerateFromBoundedNum; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegral; \ + enumerateFromThen = enumerateFromThenBoundedNum; \ {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegral; \ + enumerateFromTo = enumerateFromToNum; \ {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegral } - -ENUMERABLE_BOUNDED_INTEGRAL(Int) -ENUMERABLE_BOUNDED_INTEGRAL(Int8) -ENUMERABLE_BOUNDED_INTEGRAL(Int16) -ENUMERABLE_BOUNDED_INTEGRAL(Int32) -ENUMERABLE_BOUNDED_INTEGRAL(Int64) -ENUMERABLE_BOUNDED_INTEGRAL(Word) -ENUMERABLE_BOUNDED_INTEGRAL(Word8) -ENUMERABLE_BOUNDED_INTEGRAL(Word16) -ENUMERABLE_BOUNDED_INTEGRAL(Word32) -ENUMERABLE_BOUNDED_INTEGRAL(Word64) - --- For unbounded Integral Enum types. -#define ENUMERABLE_UNBOUNDED_INTEGRAL(INTEGRAL_TYPE) \ -instance Enumerable INTEGRAL_TYPE where { \ - {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromIntegralUnbounded; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegralUnbounded; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegral; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegral } + enumerateFromThenTo = enumerateFromThenToNum } + +ENUMERABLE_BOUNDED_NUM(Int) +ENUMERABLE_BOUNDED_NUM(Int8) +ENUMERABLE_BOUNDED_NUM(Int16) +ENUMERABLE_BOUNDED_NUM(Int32) +ENUMERABLE_BOUNDED_NUM(Int64) +ENUMERABLE_BOUNDED_NUM(Word) +ENUMERABLE_BOUNDED_NUM(Word8) +ENUMERABLE_BOUNDED_NUM(Word16) +ENUMERABLE_BOUNDED_NUM(Word32) +ENUMERABLE_BOUNDED_NUM(Word64) + +-- For unbounded 'Num' types that are not 'RealFloat' (no numerical +-- stability concerns since these are either exact integrals or exact +-- rational/fixed-precision types). +#define ENUMERABLE_UNBOUNDED_NUM(TYPE_NAME,CONSTRAINT) \ +instance (CONSTRAINT) => Enumerable TYPE_NAME where { \ + {-# INLINE enumerateFrom #-}; \ + enumerateFrom = enumerateFromNum; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenNum; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToNum; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToNum } -ENUMERABLE_UNBOUNDED_INTEGRAL(Integer) -ENUMERABLE_UNBOUNDED_INTEGRAL(Natural) +ENUMERABLE_UNBOUNDED_NUM(Integer,) +ENUMERABLE_UNBOUNDED_NUM(Natural,) +ENUMERABLE_UNBOUNDED_NUM((Fixed a),HasResolution a) +ENUMERABLE_UNBOUNDED_NUM((Ratio a),Integral a) -#define ENUMERABLE_FRACTIONAL(FRACTIONAL_TYPE,CONSTRAINT) \ -instance (CONSTRAINT) => Enumerable FRACTIONAL_TYPE where { \ +#define ENUMERABLE_REAL_FLOAT(REALFLOAT_TYPE) \ +instance Enumerable REALFLOAT_TYPE where { \ {-# INLINE enumerateFrom #-}; \ - enumerateFrom = enumerateFromFractional; \ + enumerateFrom = enumerateFromRealFloat; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenFractional; \ - {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToFractional; \ - {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToFractional } - -ENUMERABLE_FRACTIONAL(Float,) -ENUMERABLE_FRACTIONAL(Double,) -ENUMERABLE_FRACTIONAL((Fixed a),HasResolution a) -ENUMERABLE_FRACTIONAL((Ratio a),Integral a) + enumerateFromThen = enumerateFromThenRealFloat; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToRealFloat; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToRealFloat } + +ENUMERABLE_REAL_FLOAT(Float) +ENUMERABLE_REAL_FLOAT(Double) instance Enumerable a => Enumerable (Identity a) where {-# INLINE enumerateFrom #-} diff --git a/docs/Developer/optimization-guidelines.md b/docs/Developer/optimization-guidelines.md index 3b38334bdc..0f7a051d62 100644 --- a/docs/Developer/optimization-guidelines.md +++ b/docs/Developer/optimization-guidelines.md @@ -153,26 +153,26 @@ Minimize the number of yield points and try to keep a single `Yield` point per state. In Scanl benchmarks we observed that the following state machine code does not fuse: ``` -enumerateFromThenUpToIntegral :: +enumerateUpFromThenToIntegral :: (Applicative m, Integral a) => Producer m (EnumStateUp a) a -enumerateFromThenUpToIntegral (EnumUpInit from next to) = +enumerateUpFromThenToIntegral (EnumUpInit from next to) = pure $ if to < next then if to < from then Stop else Yield from EnumUpStop else let stride = next - from in Skip $ EnumUpYield from stride (to - stride) -enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = +enumerateUpFromThenToIntegral (EnumUpYield x stride toMinus) = pure $ if x > toMinus then Yield x EnumUpStop else Yield x $ EnumUpYield (x + stride) stride toMinus -enumerateFromThenUpToIntegral EnumUpStop = pure Stop +enumerateUpFromThenToIntegral EnumUpStop = pure Stop ``` However the following variation fuses quickly: ``` -enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = +enumerateUpFromThenToIntegral (EnumUpYield x stride toMinus) = if x > toMinus then Yield x Stop else Yield x $ EnumUpYield (x + stride) stride toMinus @@ -182,23 +182,23 @@ If we use a single yield point to transfer control to the next state machine then the state machine is likely to fuse better. For example, this works absolutely fine: ``` -enumerateFromThenUpToIntegral :: +enumerateUpFromThenToIntegral :: (Applicative m, Integral a) => Producer m (EnumStateUp a) a -enumerateFromThenUpToIntegral (EnumUpInit from next to) = +enumerateUpFromThenToIntegral (EnumUpInit from next to) = pure $ if to < next then if to < from then Stop else Yield from EnumUpStop else -- from <= next <= to let stride = next - from in Skip $ EnumUpYield from stride (to - stride) -enumerateFromThenUpToIntegral (EnumUpYield x stride toMinus) = +enumerateUpFromThenToIntegral (EnumUpYield x stride toMinus) = pure $ Yield x (EnumUpNext x stride toMinus) -enumerateFromThenUpToIntegral (EnumUpNext x stride toMinus) = +enumerateUpFromThenToIntegral (EnumUpNext x stride toMinus) = pure $ if x > toMinus then Stop else Skip $ EnumUpYield (x + stride) stride toMinus -enumerateFromThenUpToIntegral EnumUpStop = pure Stop +enumerateUpFromThenToIntegral EnumUpStop = pure Stop ``` A Skip ties a loop into the current, local state machine, a Yield however diff --git a/test/Streamly/Test/Data/Stream/Generate.hs b/test/Streamly/Test/Data/Stream/Generate.hs index 84afa12959..bf4ec76e61 100644 --- a/test/Streamly/Test/Data/Stream/Generate.hs +++ b/test/Streamly/Test/Data/Stream/Generate.hs @@ -1,3 +1,4 @@ +{-# OPTIONS_GHC -Wno-deprecations #-} -- | -- Module : Streamly.Test.Data.Stream.Generate -- Copyright : (c) 2020 Composewell Technologies @@ -172,12 +173,6 @@ testEnumerateFromStepNum = toList (Stream.take 5 (Stream.enumerateFromStepNum (0 :: Int) 3)) `shouldReturn` [0, 3, 6, 9, 12] -testEnumerateFromStepIntegralUnbounded :: Expectation -testEnumerateFromStepIntegralUnbounded = - toList - (Stream.take 5 (Stream.enumerateFromStepIntegralUnbounded (0 :: Int) 2)) - `shouldReturn` [0, 2, 4, 6, 8] - testEnumerateFromThenNum :: Expectation testEnumerateFromThenNum = toList (Stream.take 5 (Stream.enumerateFromThenNum (0 :: Int) 3)) @@ -215,6 +210,24 @@ testEnumerateFromThenToFractional = toList (Stream.enumerateFromThenToFractional (0.1 :: Double) 2.0 6.0) `shouldReturn` [0.1, 2.0, 3.9, 5.799999999999999] +testEnumerateFromThenDownToNum :: Expectation +testEnumerateFromThenDownToNum = do + toList (Stream.enumerateDownFromThenToNum (6 :: Int) 4 0) + `shouldReturn` [6, 4, 2, 0] + -- stride does not evenly divide (from - to) + toList (Stream.enumerateDownFromThenToNum (7 :: Int) 5 0) + `shouldReturn` [7, 5, 3, 1] + -- then > from returns an empty stream + toList (Stream.enumerateDownFromThenToNum (0 :: Int) 4 (-6)) + `shouldReturn` [] + -- to is above then (but at or below from) returns just the single + -- "from" element + toList (Stream.enumerateDownFromThenToNum (6 :: Int) 0 3) + `shouldReturn` [6] + -- matches [from, then .. to] from the Prelude + toList (Stream.enumerateDownFromThenToNum (6 :: Int) 4 (-1)) + `shouldReturn` Prelude.takeWhile (>= (-1)) [6, 4 ..] + testEnumerateFromThenSmall :: Expectation testEnumerateFromThenSmall = toList (Stream.take 4 (Stream.enumerateFromThenSmall 'a' 'c')) @@ -422,16 +435,19 @@ main = hspec $ describe moduleName $ do it "fromW16CString#" testFromW16CString it "fromW16CString# empty" testFromW16CStringEmpty + -- TODO: Use the Down functor in the up direction versions and check if + -- that gives correct results in the down direction. If so update the + -- general documentation in the Stream/Unfold modules. describe "Enumeration Primitives" $ do it "enumerateFromIntegral" testEnumerateFromIntegral it "enumerateFromNum" testEnumerateFromNum it "enumerateFromStepNum" testEnumerateFromStepNum - it "enumerateFromStepIntegralUnbounded" testEnumerateFromStepIntegralUnbounded it "enumerateFromThenNum" testEnumerateFromThenNum it "enumerateFromThenIntegral" testEnumerateFromThenIntegral it "enumerateFromThenFractional" testEnumerateFromThenFractional it "enumerateFromThenToIntegral" testEnumerateFromThenToIntegral it "enumerateFromThenToFractional" testEnumerateFromThenToFractional + it "enumerateDownFromThenToNum" testEnumerateFromThenDownToNum it "enumerateFromThenSmall" testEnumerateFromThenSmall it "enumerateFromToSmall" testEnumerateFromToSmall it "enumerateFromThenToSmall" testEnumerateFromThenToSmall diff --git a/test/Streamly/Test/Data/Stream/Nesting.hs b/test/Streamly/Test/Data/Stream/Nesting.hs index cfa831bd66..f66fd6b964 100644 --- a/test/Streamly/Test/Data/Stream/Nesting.hs +++ b/test/Streamly/Test/Data/Stream/Nesting.hs @@ -301,7 +301,7 @@ testAltBfsUnfoldEach :: Expectation testAltBfsUnfoldEach = do result <- fmap sort $ Stream.toList $ Stream.altBfsUnfoldEach - (Unfold.lmap (\n -> (1, n)) Unfold.enumerateFromToIntegral) + (Unfold.lmap (\n -> (1, n)) Unfold.enumerateFromToNum) (Stream.fromList [2, 3 :: Int]) result `shouldBe` sort [1, 2, 1, 2, 3] diff --git a/test/Streamly/Test/Data/Stream/Serial.hs b/test/Streamly/Test/Data/Stream/Serial.hs index 79a2fb67bc..9ec00f24a1 100644 --- a/test/Streamly/Test/Data/Stream/Serial.hs +++ b/test/Streamly/Test/Data/Stream/Serial.hs @@ -432,7 +432,7 @@ unfold :: Property unfold = monadicIO $ do a <- pick $ choose (0, max_length `div` 2) b <- pick $ choose (0, max_length) - let unf = UF.supplySecond b UF.enumerateFromToIntegral + let unf = UF.supplySecond b UF.enumerateFromToNum ls <- toList $ S.unfold unf a return $ ls == [a..b] diff --git a/test/Streamly/Test/Data/Stream/Type.hs b/test/Streamly/Test/Data/Stream/Type.hs index a7723fe7ba..6c3d7c74ed 100644 --- a/test/Streamly/Test/Data/Stream/Type.hs +++ b/test/Streamly/Test/Data/Stream/Type.hs @@ -34,7 +34,7 @@ unfold :: Property unfold = monadicIO $ do a <- pick $ choose (0, max_length `div` 2) b <- pick $ choose (0, max_length) - let unf = Unfold.supplySecond b Unfold.enumerateFromToIntegral + let unf = Unfold.supplySecond b Unfold.enumerateFromToNum ls <- toList $ Stream.unfold unf a return $ ls == [a..b] diff --git a/test/Streamly/Test/Data/Unfold.hs b/test/Streamly/Test/Data/Unfold.hs index ef0c25a43a..2dedda5b44 100644 --- a/test/Streamly/Test/Data/Unfold.hs +++ b/test/Streamly/Test/Data/Unfold.hs @@ -259,22 +259,6 @@ enumerateFromThenNum = ------------------------------------------------------------------------------- -- Test for Integral type ------------------------------------------------------------------------------- -enumerateFromIntegralUnbounded :: Property -enumerateFromIntegralUnbounded = - property - $ \f -> - let unf = UF.take 50 UF.enumerateFromIntegralUnbounded - in testUnfold unf (f :: Integer) $ - Prelude.take 50 $ Prelude.enumFrom f - -enumerateFromThenIntegralUnbounded :: Property -enumerateFromThenIntegralUnbounded = - property - $ \f th -> - let unf = UF.take 50 UF.enumerateFromThenIntegralUnbounded - in testUnfold unf (f :: Integer, th) $ - Prelude.take 50 $ Prelude.enumFromThen f th - enumerateFromThenToIntegral :: Property enumerateFromThenToIntegral = property @@ -304,6 +288,43 @@ enumerateFromThenToIntegralLargeStride = [-7537527385297985025, 5092559113693760989] `shouldBe` True +-- Regression test: enumerateFromToNum used to pass a literal 1 instead of +-- (from + 1) as the "then" element to enumerateFromThenToNum, so whenever +-- from > 1 the "then < from" check took the downward branch and produced an +-- empty stream instead of [from..to]. +enumerateFromToIntegralNonZeroFrom :: Expectation +enumerateFromToIntegralNonZeroFrom = + testUnfold + UF.enumerateFromToIntegral + (5 :: Int, 10) + [5, 6, 7, 8, 9, 10] + `shouldBe` True + +enumerateDownFromThenToNum :: Expectation +enumerateDownFromThenToNum = do + testUnfold + UF.enumerateDownFromThenToNum (6 :: Int, 4, 0) [6, 4, 2, 0] + `shouldBe` True + -- stride does not evenly divide (from - to) + testUnfold + UF.enumerateDownFromThenToNum (7 :: Int, 5, 0) [7, 5, 3, 1] + `shouldBe` True + -- then > from returns an empty stream + testUnfold + UF.enumerateDownFromThenToNum (0 :: Int, 4, -6) [] + `shouldBe` True + -- to is above then (but at or below from) returns just the single + -- "from" element + testUnfold + UF.enumerateDownFromThenToNum (6 :: Int, 0, 3) [6] + `shouldBe` True + -- matches [from, then .. to] from the Prelude + testUnfold + UF.enumerateDownFromThenToNum + (6 :: Int, 4, -1) + (Prelude.takeWhile (>= (-1)) [6, 4 ..]) + `shouldBe` True + enumerateFromIntegral :: Property enumerateFromIntegral = property @@ -469,7 +490,7 @@ enumerateFromFractional :: Property enumerateFromFractional = property $ \f -> - let unf = UF.take 50 UF.enumerateFromFractional + let unf = UF.take 50 UF.enumerateFromRealFloat in testUnfold unf (f :: Double) $ Prelude.take 50 $ Prelude.enumFrom f @@ -477,7 +498,7 @@ enumerateFromThenFractional :: Property enumerateFromThenFractional = property $ \f th -> - let unf = UF.take 50 UF.enumerateFromThenFractional + let unf = UF.take 50 UF.enumerateFromThenRealFloat in testUnfold unf (f :: Double, th) $ Prelude.take 50 $ Prelude.enumFromThen f th @@ -485,7 +506,7 @@ enumerateFromThenToFractional :: Property enumerateFromThenToFractional = property $ \f th to -> - let unf = UF.take 50 UF.enumerateFromThenToFractional + let unf = UF.take 50 UF.enumerateFromThenToRealFloat in testUnfold unf (f :: Double, th, to) $ Prelude.take 50 $ Prelude.enumFromThenTo f th to @@ -493,7 +514,7 @@ enumerateFromToFractional :: Property enumerateFromToFractional = property $ \f t -> - let unf = UF.enumerateFromToFractional + let unf = UF.enumerateFromToRealFloat in testUnfold unf (f :: Double, t) [f..(t :: Double)] ------------------------------------------------------------------------------- @@ -629,18 +650,18 @@ enumerateFromThenToIntegralOverflowDn = [-124, -126, -128] `shouldBe` True -enumerateFromIntegralOverflow :: Expectation -enumerateFromIntegralOverflow = +enumerateFromIntegralBoundedOverflow :: Expectation +enumerateFromIntegralBoundedOverflow = testUnfold - (UF.take 10 UF.enumerateFromIntegral) + (UF.take 10 UF.enumerateFromIntegralBounded) (253 :: Word8) [253, 254, 255] `shouldBe` True -enumerateFromThenIntegralOverflow :: Expectation -enumerateFromThenIntegralOverflow = +enumerateFromThenIntegralBoundedOverflow :: Expectation +enumerateFromThenIntegralBoundedOverflow = testUnfold - (UF.take 10 UF.enumerateFromThenIntegral) + (UF.take 10 UF.enumerateFromThenIntegralBounded) (250 :: Word8, 252) [250, 252, 254] `shouldBe` True @@ -1220,12 +1241,13 @@ testGeneration = prop "enumerateFromNum" enumerateFromNum prop "enumerateFromThenNum" enumerateFromThenNum ----------- Enumerate from Integral ------------------------------- - prop "enumerateFromIntegralUnbounded" enumerateFromIntegralUnbounded - prop "enumerateFromThenIntegralUnbounded" enumerateFromThenIntegralUnbounded prop "enumerateFromToIntegral" enumerateFromToIntegral + it "enumerateFromToIntegral non-zero from" + enumerateFromToIntegralNonZeroFrom prop "enumerateFromThenToIntegral" enumerateFromThenToIntegral it "enumerateFromThenToIntegral large stride" enumerateFromThenToIntegralLargeStride + it "enumerateDownFromThenToNum" enumerateDownFromThenToNum prop "enumerateFromIntegral" enumerateFromIntegral prop "enumerateFromThenIntegral" enumerateFromThenIntegral @@ -1237,10 +1259,10 @@ testGeneration = enumerateFromThenToIntegralOverflowUp it "enumerateFromThenToIntegral overflow dn" enumerateFromThenToIntegralOverflowDn - it "enumerateFromIntegral overflow" - enumerateFromIntegralOverflow - it "enumerateFromThenIntegral overflow" - enumerateFromThenIntegralOverflow + it "enumerateFromIntegralBounded overflow" + enumerateFromIntegralBoundedOverflow + it "enumerateFromThenIntegralBounded overflow" + enumerateFromThenIntegralBoundedOverflow it "enumerateFromToIntegralBounded overflow" enumerateFromToIntegralBoundedOverflow it "enumerateFromThenToIntegralBounded overflow" diff --git a/test/Streamly/Test/Prelude/Serial.hs b/test/Streamly/Test/Prelude/Serial.hs index 8c09d7c448..91d7f39bf3 100644 --- a/test/Streamly/Test/Prelude/Serial.hs +++ b/test/Streamly/Test/Prelude/Serial.hs @@ -494,7 +494,7 @@ unfold :: Property unfold = monadicIO $ do a <- pick $ choose (0, max_length `div` 2) b <- pick $ choose (0, max_length) - let unf = UF.supplySecond b UF.enumerateFromToIntegral + let unf = UF.supplySecond b UF.enumerateFromToNum ls <- toList $ S.unfold unf a return $ ls == [a..b] From ff542207513c567ef892684fdb430ec20f83c5e6 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 8 Jul 2026 12:47:59 +0530 Subject: [PATCH 22/23] Do not use Producer for Stream generation It causes perf regressions in many benchmarks. --- .../Internal/Data/Stream/Enumeration.hs | 128 ++++++++++++++++-- 1 file changed, 119 insertions(+), 9 deletions(-) diff --git a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs index bfd1cdf4df..f0a42e80d4 100644 --- a/core/src/Streamly/Internal/Data/Stream/Enumeration.hs +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -95,14 +95,11 @@ import Data.Int import Data.Ord (Down(..)) import Data.Ratio import Data.Word +-- import Fusion.Plugin.Types (Fuse(..)) import Numeric.Natural import Streamly.Internal.Data.Stream.Type -#ifdef USE_UNFOLDS_EVERYWHERE -import qualified Streamly.Internal.Data.Unfold as Unfold -#else -import qualified Streamly.Internal.Data.Producer as Producer -#endif +-- import qualified Streamly.Internal.Data.Producer as Producer import Prelude hiding (takeWhile) #include "DocTestDataStream.hs" @@ -125,8 +122,16 @@ import Prelude hiding (takeWhile) -- CAUTION: This will overflow or underflow and wrap around for bounded types. {-# INLINE_NORMAL enumerateFromStepNum #-} enumerateFromStepNum :: (Applicative m, Num a) => a -> a -> Stream m a +-- NOTE: Moving this to Producer causes regressions in many Stream benchmarks +{- enumerateFromStepNum !from !stride = Stream (const Producer.enumerateFromStep) (from, stride) +-} +enumerateFromStepNum !from !stride = Stream step from + + where + + step _ x = pure $ Yield x $! (x + stride) -- | @enumerateFromThenNum from then@ generates a stream whose first element is -- @from@, the second element is @then@ and the successive elements are in @@ -158,6 +163,16 @@ enumerateFromNum from = enumerateFromStepNum from 1 enumerateDownFromNum :: (Applicative m, Num a) => a -> Stream m a enumerateDownFromNum from = enumerateFromStepNum from (-1) +-- {-# ANN type EnumState Fuse #-} +data EnumState a = + EnumInit + | EnumYieldUpward a a a + | EnumNextUpward a a a + | EnumYieldDownward a a a + | EnumNextDownward a a a + | EnumSingle a + | EnumStop + -- | @enumerateFromThenToNum from then to@ generates a finite stream whose -- first element is @from@, the second element is @then@ and the successive -- elements are in increments of @then - from@ up to @to@. @@ -172,10 +187,69 @@ enumerateDownFromNum from = enumerateFromStepNum from (-1) enumerateFromThenToNum :: (Applicative m, Num a, Ord a) => a -> a -> a -> Stream m a +{- +-- This blows up build time memory consumption and compilation time of +-- Stream.Type.Logic benchmarks. enumerateFromThenToNum from next to = Stream (const Producer.enumerateFromThenTo) (Producer.EnumInit from next to) +-} +enumerateFromThenToNum from next to = Stream step EnumInit + + where + + {-# INLINE_LATE step #-} + step _ EnumInit = + pure $ + if next >= from + then + if to < next + then + if to < from + then Stop + else Skip (EnumSingle from) + else -- from <= next <= to + let stride = next - from + in Skip $ EnumYieldUpward from stride (to - stride) + else + if to > next + then + if to > from + then Stop + else Skip (EnumSingle from) + else -- from >= next >= to + let stride = next - from + in Skip $ EnumYieldDownward from stride (to - stride) + + step _ (EnumYieldUpward x stride toMinus) = + pure $ Yield x (EnumNextUpward x stride toMinus) + + step _ (EnumNextUpward x stride toMinus) = + pure $ + if x > toMinus + then Stop + else Skip $ EnumYieldUpward (x + stride) stride toMinus + + step _ (EnumYieldDownward x stride toMinus) = + pure $ Yield x (EnumNextDownward x stride toMinus) + + step _ (EnumNextDownward x stride toMinus) = + pure $ + if x < toMinus + then Stop + else Skip $ EnumYieldDownward (x + stride) stride toMinus + + step _ (EnumSingle x) = pure $ Yield x EnumStop + + step _ EnumStop = pure Stop + +-- {-# ANN type EnumStateUp Fuse #-} +data EnumStateUp a = + EnumUpInit + | EnumUpYield a a a + | EnumUpNext a a a + | EnumUpStop -- | Like 'enumerateFromThenToNum' but a simplified version that only works in -- the upward direction. It returns an empty stream if @then < from@. @@ -186,10 +260,41 @@ enumerateFromThenToNum from next to = {-# INLINE_NORMAL enumerateUpFromThenToNum #-} enumerateUpFromThenToNum :: (Applicative m, Num a, Ord a) => a -> a -> a -> Stream m a +{- enumerateUpFromThenToNum from next to = Stream (const Producer.enumerateUpFromThenTo) (Producer.EnumUpInit from next to) +-} +enumerateUpFromThenToNum from next to = Stream step EnumUpInit + + where + + {-# INLINE_LATE step #-} + step _ EnumUpInit = + pure $ + if next < from + then Stop + else + if to < next + then + if to < from + then Stop + else Yield from EnumUpStop + else -- from <= next <= to + let stride = next - from + in Skip $ EnumUpYield from stride (to - stride) + + step _ (EnumUpYield x stride toMinus) = + pure $ Yield x (EnumUpNext x stride toMinus) + + step _ (EnumUpNext x stride toMinus) = + pure $ + if x > toMinus + then Stop + else Skip $ EnumUpYield (x + stride) stride toMinus + + step _ EnumUpStop = pure Stop -- | Like 'enumerateFromThenToNum' but a simplified version that only works in -- the downward direction. It returns an empty stream if @then > from@. @@ -199,11 +304,9 @@ enumerateUpFromThenToNum from next to = -- {-# INLINE_NORMAL enumerateDownFromThenToNum #-} enumerateDownFromThenToNum - :: (Applicative m, Num a, Ord a) => a -> a -> a -> Stream m a + :: (Monad m, Num a, Ord a) => a -> a -> a -> Stream m a enumerateDownFromThenToNum from next to = - Stream - (const Producer.enumerateDownFromThenTo) - (Producer.EnumUpInit (Down from) (Down next) (Down to)) + fmap getDown $ enumerateUpFromThenToNum (Down from) (Down next) (Down to) -- | @enumerateFromToNum from to@ generates a finite stream whose first element -- is @from@ and successive elements are in increments of @1@ up to @to@. @@ -326,8 +429,15 @@ enumerateFromIntegral = enumerateFromBoundedNum -- {-# INLINE_NORMAL enumerateFromStepRealFloat #-} enumerateFromStepRealFloat :: (Applicative m, RealFloat a) => a -> a -> Stream m a +{- enumerateFromStepRealFloat !from !stride = Stream (const Producer.enumerateFromStepRealFloat) (from, stride, 0) +-} +enumerateFromStepRealFloat !from !stride = Stream step 0 + + where + + step _ i = pure $ (Yield $! (from + i * stride)) $! (i + 1) -- | Numerically stable enumeration from a 'Fractional' number in steps of size -- @1@. @enumerateFromRealFloat from@ generates a stream whose first element From 6928d2ec104add383ffbaffa1196a5cbfed7f27a Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Wed, 8 Jul 2026 12:59:07 +0530 Subject: [PATCH 23/23] Fix deprecation warning in Unfold benchmarks --- benchmark/Streamly/Benchmark/Data/Unfold.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/Streamly/Benchmark/Data/Unfold.hs b/benchmark/Streamly/Benchmark/Data/Unfold.hs index bdd3d1e675..3189d6a8d4 100644 --- a/benchmark/Streamly/Benchmark/Data/Unfold.hs +++ b/benchmark/Streamly/Benchmark/Data/Unfold.hs @@ -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