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/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/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/benchmark/Streamly/Benchmark/Data/Unfold.hs b/benchmark/Streamly/Benchmark/Data/Unfold.hs index 5ec12f3c64..3189d6a8d4 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 @@ -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 @@ -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 0ee0f6cd97..7c19b39e40 100644 --- a/core/docs/Changelog.md +++ b/core/docs/Changelog.md @@ -11,6 +11,10 @@ * Fixed `Stream.postscanl` to omit the output of a scan that terminates without consuming any input (e.g. `Scanl.take 0`). +* Fix overflow in enumerateFrom and enumerateFromTo variants in Stream and + Unfold modules. +* Fix `Unfold.enumerateFromTo` for the `Identity` type, it was incorrectly + calling `enumerateFromThen` instead of `enumerateFromTo`. ### Breaking changes @@ -38,6 +42,8 @@ filtering in scans. * Removed deprecated module `Streamly.Internal.Data.Stream.StreamD`. Use `Streamly.Internal.Data.Stream` instead. +* enumerateFrom*Num are not numerically stable any more, use + enumerateFrom*RealFloat for that. ## 0.3.1 (May 2026) diff --git a/core/src/Streamly/Internal/Data/Array/Generic/Type.hs b/core/src/Streamly/Internal/Data/Array/Generic/Type.hs index dd2f776f15..7b01f0113a 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 @@ -226,13 +227,13 @@ toList arr = loop 0 {-# INLINE_NORMAL read #-} read :: Monad m => Array a -> Stream m a read arr = - D.map (`unsafeGetIndex` arr) $ D.enumerateFromToIntegral 0 (length arr - 1) + D.map (`unsafeGetIndex` arr) $ D.enumerateFromToNum 0 (length arr - 1) {-# INLINE_NORMAL readRev #-} readRev :: Monad m => Array a -> Stream m a readRev arr = D.map (`unsafeGetIndex` arr) - $ D.enumerateFromThenToIntegral (arrLen - 1) (arrLen - 2) 0 + $ D.enumerateFromThenToNum (arrLen - 1) (arrLen - 2) 0 where arrLen = length arr diff --git a/core/src/Streamly/Internal/Data/MutArray/Generic.hs b/core/src/Streamly/Internal/Data/MutArray/Generic.hs index 3d382b8d08..f02a8682a1 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 @@ -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 cd2b99baf7..f4fef3ae57 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 @@ -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 ddb5623346..3d0b74d813 100644 --- a/core/src/Streamly/Internal/Data/Producer.hs +++ b/core/src/Streamly/Internal/Data/Producer.hs @@ -17,6 +17,8 @@ module Streamly.Internal.Data.Producer , CrossState(..) , FairCrossState(..) , TupleState(..) + , EnumState(..) + , EnumStateUp(..) , ConcatState(..) , InterleaveState(..) , InterleaveEachState(..) @@ -42,13 +44,21 @@ module Streamly.Internal.Data.Producer , mapM , mapMaybeM , takeWhileM + , takeEndByM , unfoldrM + , 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(..)) import Prelude hiding (mapM) @@ -540,6 +550,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'. @@ -549,3 +579,158 @@ 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. +-- +-- 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 + -- 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. +-- +-- 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' +-- 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 + | 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 +-- 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@. +-- +-- 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 + 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) +enumerateFromThenTo (EnumYieldUpward x stride toMinus) = + pure $ Yield x (EnumNextUpward x stride toMinus) +enumerateFromThenTo (EnumNextUpward x stride toMinus) = + pure $ + if x > toMinus + then Stop + else Skip $ EnumYieldUpward (x + stride) stride toMinus +enumerateFromThenTo (EnumYieldDownward x stride toMinus) = + pure $ Yield x (EnumNextDownward x stride toMinus) +enumerateFromThenTo (EnumNextDownward x stride toMinus) = + pure $ + if x < toMinus + then Stop + else Skip $ EnumYieldDownward (x + stride) stride toMinus +enumerateFromThenTo (EnumSingle x) = pure $ Yield x EnumStop +enumerateFromThenTo EnumStop = pure Stop + +-- | State for 'enumerateUpFromThenToIntegral'. 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 '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 enumerateUpFromThenTo #-} +enumerateUpFromThenTo :: + (Applicative m, Num a, Ord a) => Producer m (EnumStateUp a) a +enumerateUpFromThenTo (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) +enumerateUpFromThenTo (EnumUpYield x stride toMinus) = + pure $ Yield x (EnumUpNext x stride toMinus) +enumerateUpFromThenTo (EnumUpNext x stride toMinus) = + pure $ + if x > toMinus + then Stop + else Skip $ EnumUpYield (x + stride) stride toMinus +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.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..f0a42e80d4 --- /dev/null +++ b/core/src/Streamly/Internal/Data/Stream/Enumeration.hs @@ -0,0 +1,855 @@ +{-# 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 +-- +-- 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. + + -- ** 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 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 + + -- ** Bounded Num Types + , enumerateFromBoundedNum + , enumerateFromThenBoundedNum + , enumerateDownFromBoundedNum + + -- ** 'Enum' Types not larger than 'Int' + -- | These are implemented by converting Enum to Int and using integral + -- operations. Note small Enum types are always bounded though they may not + -- have a Bounded instance. + , enumerateFromSmall + , enumerateFromToSmall + , enumerateFromThenSmall + , enumerateFromThenToSmall + + -- ** '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 + , enumerateTo + + -- * Deprecated + , enumerateFromBounded + , enumerateFromThenSmallBounded + , enumerateFromIntegral + , enumerateFromThenIntegral + , enumerateFromToIntegral + , enumerateFromThenToIntegral + , enumerateFromStepIntegral + , enumerateFromFractional + , enumerateFromToFractional + , enumerateFromThenFractional + , enumerateFromThenToFractional + ) +where + +#include "inline.hs" + +import Data.Fixed +import Data.Functor.Identity (Identity(..)) +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 + +-- import qualified Streamly.Internal.Data.Producer as Producer +import Prelude hiding (takeWhile) + +#include "DocTestDataStream.hs" + +------------------------------------------------------------------------------ +-- Enumeration of Num +------------------------------------------------------------------------------ + +-- | @enumerateFromStepNum from step@ generates an infinite stream whose first +-- element is @from@ and the successive elements are in increments of @step@. +-- +-- >>> 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. +-- +-- 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 +-- 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 :: (Applicative m, Num a) => a -> a -> Stream m a +enumerateFromThenNum from next = enumerateFromStepNum from (next - from) + +-- | Same as: +-- +-- >> enumerateFromThenNum from (from + 1) +-- +{-# INLINE_NORMAL enumerateFromNum #-} +enumerateFromNum :: (Applicative m, Num a) => a -> Stream m a +enumerateFromNum from = enumerateFromStepNum from 1 + +{-# INLINE_NORMAL enumerateDownFromNum #-} +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@. +-- +-- >>> Stream.toList $ Stream.enumerateFromThenToNum 0 2 6 +-- [0,2,4,6] +-- +-- >>> Stream.toList $ Stream.enumerateFromThenToNum 0 (-2) (-6) +-- [0,-2,-4,-6] +-- +{-# INLINE_NORMAL enumerateFromThenToNum #-} +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@. +-- +-- >>> Stream.toList $ Stream.enumerateUpFromThenToNum 0 2 6 +-- [0,2,4,6] +-- +{-# 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@. +-- +-- >>> Stream.toList $ Stream.enumerateDownFromThenToNum 6 4 0 +-- [6,4,2,0] +-- +{-# INLINE_NORMAL enumerateDownFromThenToNum #-} +enumerateDownFromThenToNum + :: (Monad m, Num a, Ord a) => a -> a -> a -> Stream m a +enumerateDownFromThenToNum from next 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@. +-- +-- >>> 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 +enumerateFromThenBoundedNum from next = + enumerateFromThenToNum + from next (if next >= from then maxBound else minBound) + +-- | @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.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 = enumerateFromToNum + +{-# DEPRECATED enumerateFromIntegral "Please use enumerateFromBoundedNum instead." #-} +{-# INLINE enumerateFromIntegral #-} +enumerateFromIntegral :: (Monad m, Integral a, Bounded a) => a -> Stream m a +enumerateFromIntegral = enumerateFromBoundedNum + +------------------------------------------------------------------------------ +-- 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 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) +-} +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 +-- 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.enumerateFromRealFloat 1.1 +-- [1.1,2.1,3.1,4.1] +-- +{-# INLINE enumerateFromRealFloat #-} +enumerateFromRealFloat :: (Applicative m, RealFloat a) => a -> Stream m a +enumerateFromRealFloat from = enumerateFromStepRealFloat from 1 + +-- | 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 'RealFloat' types. For +-- example: +-- +-- >>> 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.enumerateFromThenRealFloat 1.1 (-2.1) +-- [1.1,-2.1,-5.300000000000001,-8.500000000000002] +-- +{-# INLINE enumerateFromThenRealFloat #-} +enumerateFromThenRealFloat + :: (Applicative m, RealFloat a) + => a -> a -> Stream m a +enumerateFromThenRealFloat from next = + enumerateFromStepRealFloat from (next - from) + +-- | 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 'RealFloat' types. For +-- example: +-- +-- >>> Stream.toList $ Stream.enumerateFromToRealFloat 1.1 4 +-- [1.1,2.1,3.1,4.1] +-- +-- >>> 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 enumerateFromToRealFloat #-} +enumerateFromToRealFloat + :: (Monad m, RealFloat a) + => a -> a -> Stream m a +enumerateFromToRealFloat from to = + takeWhile (<= to + 1 / 2) $ enumerateFromStepRealFloat from 1 + +-- | 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 'RealFloat' types. For +-- example: +-- +-- >>> Stream.toList $ Stream.enumerateFromThenToRealFloat 0.1 2 6 +-- [0.1,2.0,3.9,5.799999999999999] +-- +-- >>> 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) + => 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 + $ enumerateFromToNum (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 + $ enumerateFromThenToNum + (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 +-- 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 enumerateFromThenSmall #-} +enumerateFromThenSmall :: (Monad m, Enum a, Bounded a) + => a -> a -> Stream m a +enumerateFromThenSmall from next = + if fromEnum next >= fromEnum from + 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 +------------------------------------------------------------------------------- +-- +-- 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 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 + +-- | 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) => a -> Stream m a +enumerateFromBounded = enumerateFrom + +------------------------------------------------------------------------------- +-- 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 = enumerateFromSmall; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenSmall; \ + {-# 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. '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 = enumerateFromNum; \ + {-# INLINE enumerateFromThen #-}; \ + enumerateFromThen = enumerateFromThenNum; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToNum; \ + {-# INLINE enumerateFromThenTo #-}; \ + 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 #-} + 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 96bd133b9c..a48876f97b 100644 --- a/core/src/Streamly/Internal/Data/Stream/Generate.hs +++ b/core/src/Streamly/Internal/Data/Stream/Generate.hs @@ -28,43 +28,6 @@ module Streamly.Internal.Data.Stream.Generate , replicate , replicateM - -- * Enumeration - -- ** 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 - , EnumState (..) - , enumerateFromToIntegral - , enumerateFromThenToIntegral - - -- ** Enumerating unbounded 'Integral' Types - , enumerateFromStepIntegral - - -- ** Enumerating 'Fractional' Types - , enumerateFromFractional - , enumerateFromToFractional - , enumerateFromThenFractional - , enumerateFromThenToFractional - - -- ** Enumerable Type Class - , Enumerable(..) - -- * Time Enumeration , times , timesWith @@ -108,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)) @@ -124,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 @@ -319,560 +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 step 0 - where - {-# INLINE_LATE step #-} - step _ !i = return $ (Yield $! (from + i * stride)) $! (i + 1) -#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 ------------------------------------------------------------------------------- - -#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 -#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 --- 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 - | next >= from = enumerateFromThenToIntegralUp from next to - | otherwise = enumerateFromThenToIntegralDn 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 = - if next > from - then enumerateFromThenToIntegralUp from next maxBound - else enumerateFromThenToIntegralDn from next 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 step from - where - {-# INLINE_LATE step #-} - step _ !x = return $ Yield x $! (x + 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) $ 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/src/Streamly/Internal/Data/Stream/Type.hs b/core/src/Streamly/Internal/Data/Stream/Type.hs index 9788136e9d..27e318e212 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. @@ -1175,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 +takeEndByM f (Stream step1 state1) = Stream step (Just state1) + + where - step' _ Nothing = return Stop + {-# 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 @@ -1252,6 +1244,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 +1867,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 ()) diff --git a/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/core/src/Streamly/Internal/Data/Unfold/Enumeration.hs index 8e0e9f9ceb..dfa55cc948 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. @@ -19,37 +22,73 @@ -- 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 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 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 + + -- ** Bounded Num Types + , enumerateFromBoundedNum + , enumerateFromThenBoundedNum + , enumerateDownFromBoundedNum + + -- ** 'Enum' Types not larger than 'Int' + -- | These are implemented by converting Enum to Int and using integral + -- operations. Note small Enum types are always bounded though they may not + -- have a Bounded instance. + , enumerateFromSmall + , enumerateFromToSmall + , enumerateFromThenSmall + , enumerateFromThenToSmall - -- ** Enumerating unbounded 'Integral' Types + -- ** '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 - - -- ** Enumerating 'Bounded' 'Integral' Types , enumerateFromIntegralBounded , enumerateFromThenIntegralBounded , enumerateFromToIntegralBounded , enumerateFromThenToIntegralBounded - - -- ** Enumerating small 'Integral' Types - -- | Small types are always bounded. , enumerateFromSmallBounded , enumerateFromThenSmallBounded - , enumerateFromToSmall - , enumerateFromThenToSmall - - -- ** Enumerating 'Fractional' Types - -- | Enumeration of 'Num' specialized to 'Fractional' types. , enumerateFromFractional , enumerateFromThenFractional , enumerateFromToFractional @@ -58,17 +97,19 @@ module Streamly.Internal.Data.Unfold.Enumeration where #include "inline.hs" +#include "deprecation.h" + import Data.Fixed import Data.Bifunctor (bimap) import Data.Int +import Data.Ord (Down(..)) import Data.Ratio import Data.Word import Numeric.Natural import Data.Functor.Identity (Identity(..)) import Streamly.Internal.Data.Unfold.Type hiding (takeWhileMWithInput) -import Prelude - hiding (map, mapM, takeWhile, take, filter, const, zipWith - , drop, dropWhile) +import qualified Streamly.Internal.Data.Producer as Producer +import Prelude hiding (map, takeWhile, zipWith) -- $setup -- >>> :m @@ -92,28 +133,21 @@ import Prelude -- -- @ -- --- 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 'enumerateFromStepIntegral' 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 step inject +enumerateFromStepNum :: (Applicative m, Num a) => Unfold m (a, a) a +enumerateFromStepNum = Unfold Producer.enumerateFromStep 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) + inject (!from, !stride) = pure (from, stride) -- | Same as 'enumerateFromStepNum (from, next)' using a stride of @next - from@: -- @@ -129,21 +163,15 @@ enumerateFromStepNum = Unfold step inject -- -- @ -- --- The implementation is numerically stable for floating point values. --- --- Note that 'enumerateFromThenIntegral' 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 @@ -156,24 +184,121 @@ enumerateFromThenNum = -- -- @ -- --- Also, same as 'enumerateFromThenNum' using a stride of 1 but see the note in --- 'enumerateFromThenNum' about the loss of precision: +-- /Internal/ -- --- @ --- >>> 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] +{-# INLINE enumerateFromNum #-} +enumerateFromNum :: (Applicative m, Num a) => Unfold m a a +enumerateFromNum = lmap (\from -> (from, 1)) enumerateFromStepNum + +{-# INLINE enumerateDownFromNum #-} +enumerateDownFromNum :: (Applicative m, Num a) => Unfold m a a +enumerateDownFromNum = lmap (\from -> (from, -1)) enumerateFromStepNum + +-- | 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@. -- --- @ +-- 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, next, to) = pure (Producer.EnumInit from next to) + +-- | 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 + + 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) = + pure (Producer.EnumUpInit (Down from) (Down next) (Down to)) + +-- | 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 + +-- 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 enumerateFromNum #-} -enumerateFromNum :: (Monad m, Num a) => Unfold m a a -enumerateFromNum = lmap (\from -> (from, 1)) enumerateFromStepNum +{-# INLINE enumerateFromThenBoundedNum #-} +enumerateFromThenBoundedNum :: + (Applicative m, Num a, Ord a, Bounded a) => Unfold m (a, a) a +enumerateFromThenBoundedNum = lmap adapt enumerateFromThenToNum + + where + + adapt (from, next) = + (from, next, if next >= from then maxBound else minBound) + +-- | 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 enumerateDownFromBoundedNum #-} +enumerateDownFromBoundedNum :: + (Monad m, Num a, Ord a, Bounded a) => Unfold m a a +enumerateDownFromBoundedNum = supplySecond minBound enumerateDownFromToNum ------------------------------------------------------------------------------ --- Enumeration of Integrals +-- Enumeration of Integrals (Bounded) ------------------------------------------------------------------------------ {-# INLINE takeWhileMWithInput #-} @@ -181,132 +306,153 @@ takeWhileMWithInput :: Monad m => (a -> b -> m Bool) -> Unfold m a b -> Unfold m a b takeWhileMWithInput f = map snd . takeWhileM (uncurry f) . carryInput --- | Can be used to enumerate unbounded integrals. This does not check for --- overflow or underflow for bounded integrals. --- --- /Internal/ -{-# INLINE_NORMAL enumerateFromStepIntegral #-} +{-# DEPRECATED enumerateFromStepIntegral "Please use enumerateFromStepNum instead." #-} +{-# INLINE enumerateFromStepIntegral #-} enumerateFromStepIntegral :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromStepIntegral = Unfold step inject +enumerateFromStepIntegral = enumerateFromStepNum - where - - inject (from, stride) = from `seq` stride `seq` return (from, stride) +{-# DEPRECATED enumerateFromThenToIntegral "Please use enumerateFromThenToNum instead." #-} +{-# INLINE enumerateFromThenToIntegral #-} +enumerateFromThenToIntegral, enumerateFromThenToIntegralBounded :: + (Monad m, Integral a) => Unfold m (a, a, a) a +enumerateFromThenToIntegral = enumerateFromThenToNum - {-# 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 -enumerateFromIntegral = lmap (\from -> (from, 1)) enumerateFromStepIntegral +RENAME(enumerateFromThenToIntegralBounded,enumerateFromThenToIntegral) +{-# DEPRECATED enumerateFromThenIntegral "Please use enumerateFromThenNum instead." #-} {-# INLINE enumerateFromThenIntegral #-} -enumerateFromThenIntegral :: (Monad m, Integral a ) => Unfold m (a, a) a -enumerateFromThenIntegral = - lmap (\(from, next) -> (from, next - from)) enumerateFromStepIntegral +enumerateFromThenIntegral :: (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromThenIntegral = enumerateFromThenNum -{-# INLINE enumerateFromToIntegral #-} -enumerateFromToIntegral :: (Monad m, Integral a) => Unfold m (a, a) a -enumerateFromToIntegral = - takeWhileMWithInput (\(_, to) b -> return $ b <= to) - $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegral - -{-# INLINE enumerateFromThenToIntegral #-} -enumerateFromThenToIntegral :: (Monad m, Integral a) => Unfold m (a, a, a) a -enumerateFromThenToIntegral = - takeWhileMWithInput cond $ lmap toFromStep enumerateFromStepIntegral +{-# DEPRECATED enumerateFromThenIntegralBounded "Please use enumerateFromThenBoundedNum instead." #-} +{-# INLINE enumerateFromThenIntegralBounded #-} +enumerateFromThenIntegralBounded :: + (Monad m, Integral a, Bounded a) => Unfold m (a, a) a +enumerateFromThenIntegralBounded = enumerateFromThenBoundedNum - where +{-# DEPRECATED enumerateFromToIntegral "Please use enumerateFromToNum instead." #-} +{-# INLINE enumerateFromToIntegral #-} +enumerateFromToIntegral, enumerateFromToIntegralBounded :: + (Monad m, Integral a) => Unfold m (a, a) a +enumerateFromToIntegral = enumerateFromToNum - toFromStep (from, next, _) = (from, next - from) +RENAME(enumerateFromToIntegralBounded,enumerateFromToIntegral) - cond (from, next, to) b = - return - $ if next >= from - then b <= to - else b >= to +{-# DEPRECATED enumerateFromIntegral "Please use enumerateFromNum instead." #-} +{-# INLINE enumerateFromIntegral #-} +enumerateFromIntegral :: (Monad m, Integral a) => Unfold m a a +enumerateFromIntegral = enumerateFromNum --- Enumerate Bounded Integrals ------------------------------------------------ +{-# DEPRECATED enumerateFromIntegralBounded "Please use enumerateFromBoundedNum instead." #-} {-# INLINE enumerateFromIntegralBounded #-} -enumerateFromIntegralBounded :: (Monad m, Integral a, Bounded a) => - Unfold m a a -enumerateFromIntegralBounded = supplySecond maxBound enumerateFromToIntegral +enumerateFromIntegralBounded :: (Monad m, Integral a, Bounded a) => Unfold m a a +enumerateFromIntegralBounded = enumerateFromBoundedNum -{-# INLINE enumerateFromThenIntegralBounded #-} -enumerateFromThenIntegralBounded :: (Monad m, Integral a, Bounded a ) => - Unfold m (a, a) a -enumerateFromThenIntegralBounded = - takeWhileMWithInput cond $ lmap toFromStep enumerateFromStepIntegral +------------------------------------------------------------------------------ +-- Enumeration of RealFloat +------------------------------------------------------------------------------ + +-- | 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 where - toFromStep (from, next) = (from, next - from) + inject (!from, !stride) = pure (from, stride, 0) - cond (from, next) b = - return - $ if next >= from - then b <= maxBound - else b >= minBound +{-# INLINE enumerateFromRealFloat #-} +enumerateFromRealFloat :: (Applicative m, RealFloat a) => Unfold m a a +enumerateFromRealFloat = lmap (\from -> (from, 1)) enumerateFromStepRealFloat -{-# INLINE enumerateFromToIntegralBounded #-} -enumerateFromToIntegralBounded :: (Monad m, Integral a, Bounded a) => - Unfold m (a, a) a -enumerateFromToIntegralBounded = - takeWhileMWithInput (\(_, to) b -> return $ b <= to) - $ lmap fst enumerateFromIntegralBounded +{-# INLINE enumerateFromThenRealFloat #-} +enumerateFromThenRealFloat :: (Applicative m, RealFloat a) => Unfold m (a, a) a +enumerateFromThenRealFloat = + lmap (\(from, next) -> (from, next - from)) enumerateFromStepRealFloat -{-# INLINE enumerateFromThenToIntegralBounded #-} -enumerateFromThenToIntegralBounded :: (Monad m, Integral a, Bounded a) => - Unfold m (a, a, a) a -enumerateFromThenToIntegralBounded = - takeWhileMWithInput cond $ lmap toFromThen enumerateFromThenIntegralBounded +-- | 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.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 - toFromThen (from, next, _) = (from, next) + toFromStep (from, next, _) = (from, next - from) cond (from, next, to) b = - return - $ if next >= from - then b <= to - else b >= to + let stride = next - from + in return + $ if next >= from + then b <= to + stride / 2 + else b >= to + stride / 2 ------------------------------------------------------------------------------ --- Enumeration of Fractionals +-- 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 = enumerateFromNum +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 = enumerateFromThenNum +enumerateFromThenFractional = + lmap (\(from, next) -> (from, next - from)) enumerateFromStepFractional --- | Same as 'enumerateFromStepNum' 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) --- [0.1,1.1,2.1,3.1,4.1,5.1,6.1] --- --- @ --- --- /Internal/ --- +{-# 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 @@ -331,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) @@ -340,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 @@ -354,19 +500,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. (Applicative m, Enum a, Bounded a) => Unfold m (a, a) a -enumerateFromThenSmallBounded = +enumerateFromThenSmall = let adapt (from, next) = let frm = fromEnum from nxt = fromEnum next @@ -375,7 +525,9 @@ enumerateFromThenSmallBounded = 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) ------------------------------------------------------------------------------- -- Enumerable type class @@ -386,8 +538,7 @@ 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 +class Enumerable a where -- | Unfolds @from@ generating a stream starting with the element -- @from@, enumerating up to 'maxBound' when the type is 'Bounded' or @@ -402,8 +553,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 @@ -422,7 +571,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 @@ -437,7 +585,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 @@ -451,7 +598,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 ------------------------------------------------------------------------------- @@ -462,9 +608,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 #-}; \ @@ -475,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 = enumerateFromIntegralBounded; \ + enumerateFrom = enumerateFromBoundedNum; \ {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegralBounded; \ + enumerateFromThen = enumerateFromThenBoundedNum; \ {-# INLINE enumerateFromTo #-}; \ - enumerateFromTo = enumerateFromToIntegralBounded; \ + enumerateFromTo = enumerateFromToNum; \ {-# INLINE enumerateFromThenTo #-}; \ - enumerateFromThenTo = enumerateFromThenToIntegralBounded } - -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 = enumerateFromIntegral; \ - {-# INLINE enumerateFromThen #-}; \ - enumerateFromThen = enumerateFromThenIntegral; \ - {-# 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 } + enumerateFromThen = enumerateFromThenRealFloat; \ + {-# INLINE enumerateFromTo #-}; \ + enumerateFromTo = enumerateFromToRealFloat; \ + {-# INLINE enumerateFromThenTo #-}; \ + enumerateFromThenTo = enumerateFromThenToRealFloat } -ENUMERABLE_FRACTIONAL(Float,) -ENUMERABLE_FRACTIONAL(Double,) -ENUMERABLE_FRACTIONAL((Fixed a),HasResolution a) -ENUMERABLE_FRACTIONAL((Ratio a),Integral a) +ENUMERABLE_REAL_FLOAT(Float) +ENUMERABLE_REAL_FLOAT(Double) instance Enumerable a => Enumerable (Identity a) where {-# INLINE enumerateFrom #-} @@ -538,7 +688,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/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 ------------------------------------------------------------------------------ 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 diff --git a/docs/Developer/optimization-guidelines.md b/docs/Developer/optimization-guidelines.md index 45b23b928d..0f7a051d62 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: +``` +enumerateUpFromThenToIntegral :: + (Applicative m, Integral a) => Producer m (EnumStateUp a) a +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) +enumerateUpFromThenToIntegral (EnumUpYield x stride toMinus) = + pure $ + if x > toMinus + then Yield x EnumUpStop + else Yield x $ EnumUpYield (x + stride) stride toMinus +enumerateUpFromThenToIntegral EnumUpStop = pure Stop +``` + +However the following variation fuses quickly: +``` +enumerateUpFromThenToIntegral (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: +``` +enumerateUpFromThenToIntegral :: + (Applicative m, Integral a) => Producer m (EnumStateUp a) a +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) +enumerateUpFromThenToIntegral (EnumUpYield x stride toMinus) = + pure $ Yield x (EnumUpNext x stride toMinus) +enumerateUpFromThenToIntegral (EnumUpNext x stride toMinus) = + pure $ + if x > toMinus + then Stop + else Skip $ EnumUpYield (x + stride) stride toMinus +enumerateUpFromThenToIntegral 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 diff --git a/test/Streamly/Test/Data/Stream/Generate.hs b/test/Streamly/Test/Data/Stream/Generate.hs index 427c8c5e19..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 @@ -9,7 +10,9 @@ 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) import Foreign.Marshal.Alloc (alloca) import Foreign.Marshal.Array (withArray) @@ -155,11 +158,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))) @@ -175,11 +173,6 @@ 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)) - `shouldReturn` [0, 2, 4, 6, 8] - testEnumerateFromThenNum :: Expectation testEnumerateFromThenNum = toList (Stream.take 5 (Stream.enumerateFromThenNum (0 :: Int) 3)) @@ -203,15 +196,41 @@ 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 = 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')) +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')) `shouldReturn` "aceg" testEnumerateFromToSmall :: Expectation @@ -234,6 +253,117 @@ 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 +-- +-- 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) ------------------------------------------------------------------------------- @@ -305,23 +435,49 @@ 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 "enumerateFromBounded" testEnumerateFromBounded it "enumerateFromIntegral" testEnumerateFromIntegral it "enumerateFromNum" testEnumerateFromNum it "enumerateFromStepNum" testEnumerateFromStepNum - it "enumerateFromStepIntegral" testEnumerateFromStepIntegral it "enumerateFromThenNum" testEnumerateFromThenNum it "enumerateFromThenIntegral" testEnumerateFromThenIntegral it "enumerateFromThenFractional" testEnumerateFromThenFractional it "enumerateFromThenToIntegral" testEnumerateFromThenToIntegral it "enumerateFromThenToFractional" testEnumerateFromThenToFractional - it "enumerateFromThenSmallBounded" testEnumerateFromThenSmallBounded + it "enumerateDownFromThenToNum" testEnumerateFromThenDownToNum + it "enumerateFromThenSmall" testEnumerateFromThenSmall it "enumerateFromToSmall" testEnumerateFromToSmall it "enumerateFromThenToSmall" testEnumerateFromThenToSmall 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 + 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/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 0c4d171f60..2dedda5b44 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 @@ -257,22 +259,6 @@ enumerateFromThenNum = ------------------------------------------------------------------------------- -- Test for Integral type ------------------------------------------------------------------------------- -enumerateFromIntegral :: Property -enumerateFromIntegral = - property - $ \f -> - let unf = UF.take 50 UF.enumerateFromIntegral - in testUnfold unf (f :: Integer) $ - Prelude.take 50 $ Prelude.enumFrom f - -enumerateFromThenIntegral :: Property -enumerateFromThenIntegral = - property - $ \f th -> - let unf = UF.take 50 UF.enumerateFromThenIntegral - in testUnfold unf (f :: Integer, th) $ - Prelude.take 50 $ Prelude.enumFromThen f th - enumerateFromThenToIntegral :: Property enumerateFromThenToIntegral = property @@ -289,19 +275,69 @@ enumerateFromToIntegral = in testUnfold unf (f :: Integer, to) $ Prelude.take 50 $ Prelude.enumFromTo f to -enumerateFromIntegralBounded :: Property -enumerateFromIntegralBounded = +-- 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 + +-- 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 $ \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 @@ -321,19 +357,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 @@ -353,19 +389,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 @@ -386,19 +422,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 @@ -418,19 +454,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 @@ -454,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 @@ -462,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 @@ -470,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 @@ -478,9 +514,174 @@ enumerateFromToFractional :: Property enumerateFromToFractional = property $ \f t -> - let unf = UF.enumerateFromToFractional + let unf = UF.enumerateFromToRealFloat 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 +-- +-- 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 ------------------------------------------------------------------------------- @@ -1040,33 +1241,50 @@ testGeneration = prop "enumerateFromNum" enumerateFromNum prop "enumerateFromThenNum" enumerateFromThenNum ----------- Enumerate from Integral ------------------------------- - prop "enumerateFromIntegral" enumerateFromIntegral - prop "enumerateFromThenIntegral" enumerateFromThenIntegral prop "enumerateFromToIntegral" enumerateFromToIntegral + it "enumerateFromToIntegral non-zero from" + enumerateFromToIntegralNonZeroFrom prop "enumerateFromThenToIntegral" enumerateFromThenToIntegral + it "enumerateFromThenToIntegral large stride" + enumerateFromThenToIntegralLargeStride + it "enumerateDownFromThenToNum" enumerateDownFromThenToNum - 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 ---- + 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 + 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 @@ -1074,6 +1292,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 = 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]