content: -- |
  -- Module      : Stream.Eliminate
  -- Copyright   : (c) 2018 Composewell Technologies
  -- License     : BSD-3-Clause
  -- Maintainer  : streamly@composewell.com
  
  {-# LANGUAGE CPP #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE RankNTypes #-}
  
  {-# OPTIONS_GHC -Wno-orphans #-}
  #ifdef USE_PRELUDE
  #endif
  
  #ifdef __HADDOCK_VERSION__
  #undef INSPECTION
  #endif
  
  #ifdef INSPECTION
  {-# LANGUAGE TemplateHaskell #-}
  {-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
  #endif
  
  module Stream.Eliminate (benchmarks) where
  
  import Control.Monad (when)
  import Control.DeepSeq (NFData(..))
  import Data.Functor.Identity (Identity, runIdentity)
  import System.Random (randomRIO)
  
  import qualified Data.Foldable as F
  import qualified GHC.Exts as GHC
  import qualified Streamly.Internal.Data.Fold as Fold
  
  #ifdef INSPECTION
  import GHC.Types (SPEC(..))
  import Test.Inspection
  import qualified Streamly.Internal.Data.Stream as D
  #endif
  
  #ifdef USE_PRELUDE
  import Streamly.Prelude (fromSerial)
  import Streamly.Benchmark.Prelude
  import qualified Streamly.Internal.Data.Stream.IsStream as S
  #else
  import Stream.Common
  import Streamly.Internal.Data.Stream (Stream)
  import qualified Streamly.Internal.Data.Stream as S
  #endif
  
  import Test.Tasty.Bench
  import Streamly.Benchmark.Common
  import Prelude hiding (length, sum, or, and, any, all, notElem, elem, (!!),
      lookup, repeat, minimum, maximum, product, last, mapM_, init)
  import qualified Prelude
  
  #ifdef USE_PRELUDE
  type Stream = S.SerialT
  fromStream = id
  
  {-# INLINE repeat #-}
  repeat :: (Monad m, S.IsStream t) => Int -> Int -> t m Int
  repeat count = S.take count . S.repeat
  #endif
  
  -------------------------------------------------------------------------------
  -- Elimination
  -------------------------------------------------------------------------------
  
  -------------------------------------------------------------------------------
  -- Foldable Instance
  -------------------------------------------------------------------------------
  
  {-# INLINE foldableFoldl' #-}
  foldableFoldl' :: Int -> Int -> Int
  foldableFoldl' value n =
      F.foldl' (+) 0 (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableFoldrElem #-}
  foldableFoldrElem :: Int -> Int -> Bool
  foldableFoldrElem value n =
      F.foldr (\x xs -> x == value || xs)
              False
              (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableSum #-}
  foldableSum :: Int -> Int -> Int
  foldableSum value n =
      Prelude.sum (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableProduct #-}
  foldableProduct :: Int -> Int -> Int
  foldableProduct value n =
      Prelude.product (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE _foldableNull #-}
  _foldableNull :: Int -> Int -> Bool
  _foldableNull value n =
      Prelude.null (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableElem #-}
  foldableElem :: Int -> Int -> Bool
  foldableElem value n =
      value `Prelude.elem` (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableNotElem #-}
  foldableNotElem :: Int -> Int -> Bool
  foldableNotElem value n =
      value `Prelude.notElem` (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableFind #-}
  foldableFind :: Int -> Int -> Maybe Int
  foldableFind value n =
      F.find (== (value + 1)) (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableAll #-}
  foldableAll :: Int -> Int -> Bool
  foldableAll value n =
      Prelude.all (<= (value + 1)) (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableAny #-}
  foldableAny :: Int -> Int -> Bool
  foldableAny value n =
      Prelude.any (> (value + 1)) (sourceUnfoldr value n :: Stream Identity Int)
  
  {- HLINT ignore "Use all"-}
  {-# INLINE foldableAnd #-}
  foldableAnd :: Int -> Int -> Bool
  foldableAnd value n =
      Prelude.and $ fmap
          (<= (value + 1)) (sourceUnfoldr value n :: Stream Identity Int)
  
  {- HLINT ignore "Use any"-}
  {-# INLINE foldableOr #-}
  foldableOr :: Int -> Int -> Bool
  foldableOr value n =
      Prelude.or $ fmap
          (> (value + 1)) (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableLength #-}
  foldableLength :: Int -> Int -> Int
  foldableLength value n =
      Prelude.length (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableMin #-}
  foldableMin :: Int -> Int -> Int
  foldableMin value n =
      Prelude.minimum (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE ordInstanceMin #-}
  ordInstanceMin :: Stream Identity Int -> Stream Identity Int
  ordInstanceMin src = min src src
  
  {-# INLINE foldableMax #-}
  foldableMax :: Int -> Int -> Int
  foldableMax value n =
      Prelude.maximum (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableMinBy #-}
  foldableMinBy :: Int -> Int -> Int
  foldableMinBy value n =
      F.minimumBy compare (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableListMinBy #-}
  foldableListMinBy :: Int -> Int -> Int
  foldableListMinBy value n = F.minimumBy compare [1..value+n]
  
  {-# INLINE foldableMaxBy #-}
  foldableMaxBy :: Int -> Int -> Int
  foldableMaxBy value n =
      F.maximumBy compare (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableToList #-}
  foldableToList :: Int -> Int -> [Int]
  foldableToList value n =
      F.toList (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableMapM_ #-}
  foldableMapM_ :: Monad m => Int -> Int -> m ()
  foldableMapM_ value n =
      F.mapM_ (\_ -> return ()) (sourceUnfoldr value n :: Stream Identity Int)
  
  {-# INLINE foldableSequence_ #-}
  foldableSequence_ :: Int -> Int -> IO ()
  foldableSequence_ value n =
      F.sequence_ (sourceUnfoldrAction value n :: Stream Identity (IO Int))
  
  {-# INLINE _foldableMsum #-}
  _foldableMsum :: Int -> Int -> IO Int
  _foldableMsum value n =
      F.msum (sourceUnfoldrAction value n :: Stream Identity (IO Int))
  
  {-# INLINE showInstance #-}
  showInstance :: Stream Identity Int -> String
  showInstance = show
  
  o_1_space_elimination_foldable :: Int -> [Benchmark]
  o_1_space_elimination_foldable value =
      [ bgroup "foldable"
            -- Foldable instance
          [ bench "foldl'" $ nf (foldableFoldl' value) 1
          , bench "foldrElem" $ nf (foldableFoldrElem value) 1
       -- , bench "null" $ nf (_foldableNull value) 1
          , bench "elem" $ nf (foldableElem value) 1
          , bench "length" $ nf (foldableLength value) 1
          , bench "sum" $ nf (foldableSum value) 1
          , bench "product" $ nf (foldableProduct value) 1
          , bench "minimum" $ nf (foldableMin value) 1
          , benchPureSink value "min (ord)" ordInstanceMin
          , bench "maximum" $ nf (foldableMax value) 1
          , bench "minimumBy" $ nf (`foldableMinBy` 1) value
          , bench "maximumBy" $ nf (`foldableMaxBy` 1) value
          , bench "minimumByList" $ nf (`foldableListMinBy` 1) value
          , bench "length . toList"
              $ nf (Prelude.length . foldableToList value) 1
          , bench "notElem" $ nf (foldableNotElem value) 1
          , bench "find" $ nf (foldableFind value) 1
          , bench "all" $ nf (foldableAll value) 1
          , bench "any" $ nf (foldableAny value) 1
          , bench "and" $ nf (foldableAnd value) 1
          , bench "or" $ nf (foldableOr value) 1
  
          -- Applicative and Traversable operations
          -- TBD: traverse_
          , benchIOSink1 "mapM_" (foldableMapM_ value)
          -- TBD: for_
          -- TBD: forM_
          , benchIOSink1 "sequence_" (foldableSequence_ value)
          -- TBD: sequenceA_
          -- TBD: asum
          -- XXX needs to be fixed, results are in ns
          -- , benchIOSink1 "msum" (foldableMsum value)
          ]
      ]
  
  -------------------------------------------------------------------------------
  -- Stream folds
  -------------------------------------------------------------------------------
  
  #ifndef USE_PRELUDE
  instance NFData a => NFData (Stream Identity a) where
      {-# INLINE rnf #-}
      rnf xs = runIdentity $ S.fold (Fold.foldl' (\_ x -> rnf x) ()) xs
  #endif
  
  {-# INLINE benchPureSink #-}
  benchPureSink :: NFData b
      => Int -> String -> (Stream Identity Int -> b) -> Benchmark
  benchPureSink value name = benchPure name (sourceUnfoldr value)
  
  {-# INLINE benchHoistSink #-}
  benchHoistSink
      :: (NFData b)
      => Int -> String -> (Stream Identity Int -> IO b) -> Benchmark
  benchHoistSink value name f =
      bench name $ nfIO $ randomRIO (1,1) >>= f .  sourceUnfoldr value
  
  -- XXX We should be using sourceUnfoldrM for fair comparison with IO monad, but
  -- we can't use it as it requires MonadAsync constraint.
  {-# INLINE benchIdentitySink #-}
  benchIdentitySink
      :: (NFData b)
      => Int -> String -> (Stream Identity Int -> Identity b) -> Benchmark
  benchIdentitySink value name f = bench name $ nf (f . sourceUnfoldr value) 1
  
  -------------------------------------------------------------------------------
  -- Reductions
  -------------------------------------------------------------------------------
  
  {-# INLINE uncons #-}
  uncons :: Monad m => Stream m Int -> m ()
  uncons s = do
      r <- S.uncons s
      case r of
          Nothing -> return ()
          Just (_, t) -> uncons t
  
  #ifndef USE_PRELUDE
  {-# INLINE toNull #-}
  toNull :: Monad m => Stream m Int -> m ()
  toNull = S.drain
  #endif
  
  #ifdef USE_PRELUDE
  {-# INLINE init #-}
  init :: Monad m => Stream m a -> m ()
  init s = S.init s >>= Prelude.mapM_ S.drain
  #endif
  
  {-# INLINE mapM_ #-}
  mapM_ :: Monad m => Stream m Int -> m ()
  mapM_ = S.mapM_ (\_ -> return ())
  
  {-# INLINE foldBreak #-}
  foldBreak :: Monad m => Stream m Int -> m ()
  foldBreak s = do
      (r, s1) <- S.foldBreak (Fold.take 1 Fold.length) s
      when (r /= 0) $ foldBreak s1
  
  {-# INLINE foldrMElem #-}
  foldrMElem :: Monad m => Int -> Stream m Int -> m Bool
  foldrMElem e =
      S.foldrM
          (\x xs ->
               if x == e
                   then return True
                   else xs)
          (return False)
  
  -- {-# INLINE foldrToStream #-}
  -- foldrToStream :: Monad m => Stream m Int -> m (Stream Identity Int)
  -- foldrToStream = S.foldr S.cons S.nil
  
  {-# INLINE foldrMBuild #-}
  foldrMBuild :: Monad m => Stream m Int -> m [Int]
  foldrMBuild = S.foldrM (\x xs -> (x :) <$> xs) (return [])
  
  {-# INLINE foldl'Reduce #-}
  foldl'Reduce :: Monad m => Stream m Int -> m Int
  foldl'Reduce = S.foldl' (+) 0
  
  {-# INLINE last #-}
  last :: Monad m => Stream m Int -> m (Maybe Int)
  last = S.last
  
  #ifdef USE_PRELUDE
  {-# INLINE foldl1'Reduce #-}
  foldl1'Reduce :: Monad m => Stream m Int -> m (Maybe Int)
  foldl1'Reduce = S.foldl1' (+)
  
  {-# INLINE foldlM'Reduce #-}
  foldlM'Reduce :: Monad m => Stream m Int -> m Int
  foldlM'Reduce = S.foldlM' (\xs a -> return $ a + xs) (return 0)
  
  {-# INLINE _head #-}
  _head :: Monad m => Stream m Int -> m (Maybe Int)
  _head = S.head
  
  {-# INLINE elem #-}
  elem :: Monad m => Int -> Stream m Int -> m Bool
  elem value = S.elem (value + 1)
  
  {-# INLINE notElem #-}
  notElem :: Monad m => Int -> Stream m Int -> m Bool
  notElem value = S.notElem (value + 1)
  
  {-# INLINE length #-}
  length :: Monad m => Stream m Int -> m Int
  length = S.length
  
  {-# INLINE all #-}
  all :: Monad m => Int -> Stream m Int -> m Bool
  all value = S.all (<= (value + 1))
  
  {-# INLINE any #-}
  any :: Monad m => Int -> Stream m Int -> m Bool
  any value = S.any (> (value + 1))
  
  {-# INLINE and #-}
  and :: Monad m => Int -> Stream m Int -> m Bool
  and value = S.and . S.map (<= (value + 1))
  
  {-# INLINE or #-}
  or :: Monad m => Int -> Stream m Int -> m Bool
  or value = S.or . S.map (> (value + 1))
  
  {-# INLINE find #-}
  find :: Monad m => Int -> Stream m Int -> m (Maybe Int)
  find value = S.find (== (value + 1))
  
  {-# INLINE findM #-}
  findM :: Monad m => Int -> Stream m Int -> m (Maybe Int)
  findM value = S.findM (\z -> return $ z == (value + 1))
  
  {-# INLINE findIndex #-}
  findIndex :: Monad m => Int -> Stream m Int -> m (Maybe Int)
  findIndex value = S.findIndex (== (value + 1))
  
  {-# INLINE elemIndex #-}
  elemIndex :: Monad m => Int -> Stream m Int -> m (Maybe Int)
  elemIndex value = S.elemIndex (value + 1)
  
  {-# INLINE maximum #-}
  maximum :: Monad m => Stream m Int -> m (Maybe Int)
  maximum = S.maximum
  
  {-# INLINE minimum #-}
  minimum :: Monad m => Stream m Int -> m (Maybe Int)
  minimum = S.minimum
  
  {-# INLINE sum #-}
  sum :: Monad m => Stream m Int -> m Int
  sum = S.sum
  
  {-# INLINE product #-}
  product :: Monad m => Stream m Int -> m Int
  product = S.product
  
  {-# INLINE minimumBy #-}
  minimumBy :: Monad m => Stream m Int -> m (Maybe Int)
  minimumBy = S.minimumBy compare
  
  {-# INLINE maximumBy #-}
  maximumBy :: Monad m => Stream m Int -> m (Maybe Int)
  maximumBy = S.maximumBy compare
  
  {-# INLINE the #-}
  the :: Monad m => Stream m Int -> m (Maybe Int)
  the = S.the
  
  {-# INLINE drainN #-}
  drainN :: Monad m => Int -> Stream m Int -> m ()
  drainN = S.drainN
  
  {-# INLINE drainWhile #-}
  drainWhile :: Monad m => Stream m Int -> m ()
  drainWhile = S.drainWhile (const True)
  
  {-# INLINE (!!) #-}
  (!!) :: Monad m => Int -> Stream m Int -> m (Maybe Int)
  (!!) = flip (S.!!)
  
  {-# INLINE lookup #-}
  lookup :: Monad m => Int -> Stream m Int -> m (Maybe Int)
  lookup val = S.lookup val . S.map (\x -> (x, x))
  #endif
  
  o_1_space_elimination_folds :: Int -> [Benchmark]
  o_1_space_elimination_folds value =
      [ bgroup "elimination"
          -- Basic folds
          [
  #ifdef USE_PRELUDE
              bgroup "reduce"
              [ bgroup
                    "IO"
                    [ benchIOSink value "foldl'" foldl'Reduce
                    , benchIOSink value "foldl1'" foldl1'Reduce
                    , benchIOSink value "foldlM'" foldlM'Reduce
                    ]
  
              , bgroup
                    "Identity"
                    [ benchIdentitySink value "foldl'" foldl'Reduce
                    , benchIdentitySink value "foldl1'" foldl1'Reduce
                    , benchIdentitySink value "foldlM'" foldlM'Reduce
                    ]
              ] ,
  #endif
           bgroup "build"
              [ bgroup "IO"
                    [ benchIOSink value "foldrMElem" (foldrMElem value)
                    ]
              , bgroup "Identity"
                    [ benchIdentitySink value "foldrMElem" (foldrMElem value)
                    {-
                    , benchIdentitySink 16 "foldrToStreamLength (16)"
                          (S.fold Fold.length . runIdentity . foldrToStream)
                    -}
                    {-
                    , benchPureSink 16 "foldrMToListLength (16)"
                          (Prelude.length . runIdentity . foldrMBuild)
                    -}
                    , benchPureSink value "foldrMToListLength"
                          (Prelude.length . runIdentity . foldrMBuild)
                    ]
              ]
  
          -- deconstruction
          , benchIOSink value "uncons" uncons
          , benchIOSink value "mapM_" mapM_
          , benchIOSink value "last" last
  #ifndef USE_PRELUDE
          , benchHoistSink value "length . generalizeInner"
                (S.fold Fold.length . S.generalizeInner)
          , benchIOSink value "toNull" toNull
          , benchIOSink value "foldBreak" foldBreak
          , benchIOSink value "foldl" foldl'Reduce
  #endif
  #ifdef USE_PRELUDE
          , benchIOSink value "init" init
  
          -- draining
          , benchIOSink value "drain" $ toNull fromSerial
          , benchIOSink value "drainN" $ drainN value
          , benchIOSink value "drainWhile" drainWhile
          , benchPureSink value "drain (pure)" id
  
          -- this is too fast, causes all benchmarks reported in ns
      -- , benchIOSink value "head" head
          , benchIOSink value "length" length
          , benchIOSink value "sum" sum
          , benchIOSink value "product" product
          , benchIOSink value "maximumBy" maximumBy
          , benchIOSink value "maximum" maximum
          , benchIOSink value "minimumBy" minimumBy
          , benchIOSink value "minimum" minimum
  
          , bench "the" $ nfIO $ randomRIO (1,1) >>= the . repeat value
          , benchIOSink value "find" (find value)
          , benchIOSink value "findM" (findM value)
          -- , benchIOSink value "lookupFirst" (lookup 1)
          , benchIOSink value "lookupNever" (lookup (value + 1))
          , benchIOSink value "(!!)" (value !!)
          , benchIOSink value "findIndex" (findIndex value)
          , benchIOSink value "elemIndex" (elemIndex value)
          -- this is too fast, causes all benchmarks reported in ns
      -- , benchIOSink value "null" S.null
          , benchIOSink value "elem" (elem value)
          , benchIOSink value "notElem" (notElem value)
          , benchIOSink value "all" (all value)
          , benchIOSink value "any" (any value)
          , benchIOSink value "and" (and value)
          , benchIOSink value "or" (or value)
  #endif
  
          -- length is used to check for foldr/build fusion
          , benchPureSink value "length . IsList.toList" (Prelude.length . GHC.toList)
          ]
      ]
  
  -------------------------------------------------------------------------------
  -- Buffered Transformations by fold
  -------------------------------------------------------------------------------
  
  #ifdef USE_PRELUDE
  {-# INLINE foldl'Build #-}
  foldl'Build :: Monad m => Stream m Int -> m [Int]
  foldl'Build = S.foldl' (flip (:)) []
  
  {-# INLINE foldlM'Build #-}
  foldlM'Build :: Monad m => Stream m Int -> m [Int]
  foldlM'Build = S.foldlM' (\xs x -> return $ x : xs) (return [])
  
  o_n_heap_elimination_foldl :: Int -> [Benchmark]
  o_n_heap_elimination_foldl value =
      [ bgroup "foldl"
          -- Left folds for building a structure are inherently non-streaming
          -- as the structure cannot be lazily consumed until fully built.
          [ benchIOSink value "foldl'/build/IO" foldl'Build
          , benchIdentitySink value "foldl'/build/Identity" foldl'Build
          , benchIOSink value "foldlM'/build/IO" foldlM'Build
          , benchIdentitySink value "foldlM'/build/Identity" foldlM'Build
          ]
      ]
  #endif
  
  -- For comparisons
  {-# INLINE showInstanceList #-}
  showInstanceList :: [Int] -> String
  showInstanceList = show
  
  o_n_heap_elimination_buffered :: Int -> [Benchmark]
  o_n_heap_elimination_buffered value =
      [ bgroup "buffered"
          -- Buffers the output of show/read.
          -- XXX can the outputs be streaming? Can we have special read/show
          -- style type classes, readM/showM supporting streaming effects?
          [ bench "showPrec Haskell lists" $ nf showInstanceList (mkList value)
          -- XXX This is not o-1-space for GHC-8.10
          , benchPureSink value "showsPrec pure streams" showInstance
          ]
      ]
  
  {-# INLINE foldrMReduce #-}
  foldrMReduce :: Monad m => Stream m Int -> m Int
  foldrMReduce = S.foldrM (\x xs -> (x +) <$> xs) (return 0)
  
  o_n_space_elimination_foldr :: Int -> [Benchmark]
  o_n_space_elimination_foldr value =
      -- Head recursive strict right folds.
      [ bgroup "foldr"
          -- accumulation due to strictness of IO monad
          [ benchIOSink value "foldrM/build/IO (toList)" foldrMBuild
          -- Right folds for reducing are inherently non-streaming as the
          -- expression needs to be fully built before it can be reduced.
          , benchIdentitySink value "foldrM/reduce/Identity (sum)" foldrMReduce
          , benchIOSink value "foldrM/reduce/IO (sum)" foldrMReduce
  
          -- This is horribly slow, never finishes
          -- let foldlS = composeN n $ S.foldlS (flip S.cons) S.nil
          --  in benchFold "foldlS"  (foldlS    1) sourceUnfoldrM
          ]
      ]
  
  #ifdef USE_PRELUDE
  o_n_heap_elimination_toList :: Int -> [Benchmark]
  o_n_heap_elimination_toList value =
      [ bgroup "toList"
          -- Converting the stream to a list or pure stream in a strict monad
          [ benchIOSink value "toListRev" S.toListRev
          , benchIOSink value "toStreamRev"
              (S.toStreamRev :: (Stream IO Int -> IO (Stream Identity Int)))
          ]
      ]
  
  o_n_space_elimination_toList :: Int -> [Benchmark]
  o_n_space_elimination_toList value =
      [ bgroup "toList"
          -- Converting the stream to a list or pure stream in a strict monad
          [ benchIOSink value "toList" S.toList
  #ifndef USE_PRELUDE
          , benchIOSink value "toStream"
              (S.toStream :: (Stream IO Int -> IO (Stream Identity Int)))
  #endif
          ]
      ]
  #endif
  
  -------------------------------------------------------------------------------
  -- Multi-stream folds
  -------------------------------------------------------------------------------
  
  -------------------------------------------------------------------------------
  -- Multi-stream pure
  -------------------------------------------------------------------------------
  
  {-# INLINE eqBy' #-}
  eqBy' :: (Monad m, Eq a) => Stream m a -> m Bool
  eqBy' src = S.eqBy (==) src src
  
  {-# INLINE eqByPure #-}
  eqByPure :: Int -> Int -> Identity Bool
  eqByPure value n = eqBy' (sourceUnfoldr value n)
  
  #ifdef INSPECTION
  inspect $ hasNoTypeClasses 'eqByPure
  inspect $ 'eqByPure `hasNoType` ''SPEC
  inspect $ 'eqByPure `hasNoType` ''D.Step
  #endif
  
  {-# INLINE eqInstance #-}
  eqInstance :: Stream Identity Int -> Bool
  eqInstance src = src == src
  
  {-# INLINE eqInstanceNotEq #-}
  eqInstanceNotEq :: Stream Identity Int -> Bool
  eqInstanceNotEq src = src /= src
  
  {-# INLINE cmpBy' #-}
  cmpBy' :: (Monad m, Ord a) => Stream m a -> m Ordering
  cmpBy' src = S.cmpBy compare src src
  
  {-# INLINE cmpByPure #-}
  cmpByPure :: Int -> Int -> Identity Ordering
  cmpByPure value n = cmpBy' (sourceUnfoldr value n)
  
  #ifdef INSPECTION
  inspect $ hasNoTypeClasses 'cmpByPure
  inspect $ 'cmpByPure `hasNoType` ''SPEC
  inspect $ 'cmpByPure `hasNoType` ''D.Step
  #endif
  
  {-# INLINE ordInstance #-}
  ordInstance :: Stream Identity Int -> Bool
  ordInstance src = src < src
  
  o_1_space_elimination_multi_stream_pure :: Int -> [Benchmark]
  o_1_space_elimination_multi_stream_pure value =
      [ bgroup "multi-stream-pure"
          [ benchPureSink1 "eqBy" (eqByPure value)
          , benchPureSink value "==" eqInstance
          , benchPureSink value "/=" eqInstanceNotEq
          , benchPureSink1 "cmpBy" (cmpByPure value)
          , benchPureSink value "<" ordInstance
          ]
      ]
  
  {-# INLINE isPrefixOf #-}
  isPrefixOf :: Monad m => Stream m Int -> m Bool
  isPrefixOf src = S.isPrefixOf src src
  
  {-# INLINE isSubsequenceOf #-}
  isSubsequenceOf :: Monad m => Stream m Int -> m Bool
  isSubsequenceOf src = S.isSubsequenceOf src src
  
  {-# INLINE stripPrefix #-}
  stripPrefix :: Monad m => Stream m Int -> m ()
  stripPrefix src = do
      _ <- S.stripPrefix src src
      return ()
  
  {-# INLINE eqBy #-}
  eqBy :: Int -> Int -> IO Bool
  eqBy value n = eqBy' (sourceUnfoldrM value n)
  
  #ifdef INSPECTION
  inspect $ hasNoTypeClasses 'eqBy
  inspect $ 'eqBy `hasNoType` ''SPEC
  inspect $ 'eqBy `hasNoType` ''D.Step
  #endif
  
  {-# INLINE cmpBy #-}
  cmpBy :: Int -> Int -> IO Ordering
  cmpBy value n = cmpBy' (sourceUnfoldrM value n)
  
  #ifdef INSPECTION
  inspect $ hasNoTypeClasses 'cmpBy
  inspect $ 'cmpBy `hasNoType` ''SPEC
  inspect $ 'cmpBy `hasNoType` ''D.Step
  #endif
  
  o_1_space_elimination_multi_stream :: Int -> [Benchmark]
  o_1_space_elimination_multi_stream value =
      [ bgroup "multi-stream"
          [ benchIOSink1 "eqBy" (eqBy value)
          , benchIOSink1 "cmpBy" (cmpBy value)
          , benchIOSink value "isPrefixOf" isPrefixOf
          , benchIOSink value "isSubsequenceOf" isSubsequenceOf
          , benchIOSink value "stripPrefix" stripPrefix
          ]
      ]
  
  -------------------------------------------------------------------------------
  -- Main
  -------------------------------------------------------------------------------
  
  -- In addition to gauge options, the number of elements in the stream can be
  -- passed using the --stream-size option.
  --
  benchmarks :: String -> Int -> [Benchmark]
  benchmarks moduleName size =
          [ bgroup (o_1_space_prefix moduleName) $ concat
              [ o_1_space_elimination_foldable size
              , o_1_space_elimination_folds size
              , o_1_space_elimination_multi_stream_pure size
              , o_1_space_elimination_multi_stream size
              ]
  
          , bgroup (o_n_heap_prefix moduleName) $
                 o_n_heap_elimination_buffered size
  #ifdef USE_PRELUDE
              ++ o_n_heap_elimination_foldl size
              ++ o_n_heap_elimination_toList size
              ++ o_n_space_elimination_toList size
  #endif
          , bgroup (o_n_space_prefix moduleName) $
              o_n_space_elimination_foldr size
          ]
  