content: 
  -- |
  -- Module      : Streamly.Benchmark.Data.Stream.StreamDK
  -- Copyright   : (c) 2018 Composewell Technologies
  --
  -- License     : BSD3
  -- Maintainer  : streamly@composewell.com
  
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  module Main (main) where
  
  -- import Control.Monad (when)
  -- import Data.Maybe (isJust)
  import Prelude hiding ()
  -- import qualified Prelude as P
  -- import qualified Data.List as List
  
  import qualified Streamly.Internal.Data.StreamK.Alt as S
  -- import qualified Streamly.Internal.Data.Stream.Common as SP
  -- import qualified Streamly.Internal.Data.SVar as S
  
  import Streamly.Benchmark.Common
  import Test.Tasty.Bench (bgroup, Benchmark, defaultMain)
  
  value :: Int
  value = 100000
  {-
  value2, value3, value16, maxValue :: Int
  value2 = round (P.fromIntegral value**(1/2::P.Double)) -- double nested loop
  value3 = round (P.fromIntegral value**(1/3::P.Double)) -- triple nested loop
  value16 = round (P.fromIntegral value**(1/16::P.Double)) -- triple nested loop
  maxValue = value
  -}
  
  -------------------------------------------------------------------------------
  -- Benchmark ops
  -------------------------------------------------------------------------------
  
  -------------------------------------------------------------------------------
  -- Stream generation and elimination
  -------------------------------------------------------------------------------
  
  type Stream m a = S.Stream m a
  
  {-# INLINE sourceUnfoldr #-}
  sourceUnfoldr :: Monad m => Int -> Stream m Int
  sourceUnfoldr n = S.unfoldr step n
      where
      step cnt =
          if cnt > n + value
          then Nothing
          else Just (cnt, cnt + 1)
  
  {-
  {-# INLINE sourceUnfoldrN #-}
  sourceUnfoldrN :: Monad m => Int -> Int -> Stream m Int
  sourceUnfoldrN m n = S.unfoldr step n
      where
      step cnt =
          if cnt > n + m
          then Nothing
          else Just (cnt, cnt + 1)
  -}
  
  {-# INLINE sourceUnfoldrM #-}
  sourceUnfoldrM :: Monad m => Int -> Stream m Int
  sourceUnfoldrM n = S.unfoldrM step n
      where
      step cnt =
          if cnt > n + value
          then return Nothing
          else return (Just (cnt, cnt + 1))
  
  {-
  {-# INLINE sourceUnfoldrMN #-}
  sourceUnfoldrMN :: Monad m => Int -> Int -> Stream m Int
  sourceUnfoldrMN m n = S.unfoldrM step n
      where
      step cnt =
          if cnt > n + m
          then return Nothing
          else return (Just (cnt, cnt + 1))
  -}
  
  {-
  {-# INLINE sourceFromEnum #-}
  sourceFromEnum :: Monad m => Int -> Stream m Int
  sourceFromEnum n = S.enumFromStepN n 1 value
  -}
  
  {-
  {-# INLINE sourceFromFoldable #-}
  sourceFromFoldable :: Int -> Stream m Int
  sourceFromFoldable n = S.fromFoldable [n..n+value]
  -}
  
  {-
  {-# INLINE sourceFromFoldableM #-}
  sourceFromFoldableM :: S.MonadAsync m => Int -> Stream m Int
  sourceFromFoldableM n = S.fromFoldableM (Prelude.fmap return [n..n+value])
  -}
  
  {-
  {-# INLINE sourceFoldMapWith #-}
  sourceFoldMapWith :: Int -> Stream m Int
  sourceFoldMapWith n = SP.foldMapWith S.serial S.fromPure [n..n+value]
  
  {-# INLINE sourceFoldMapWithM #-}
  sourceFoldMapWithM :: Monad m => Int -> Stream m Int
  sourceFoldMapWithM n = SP.foldMapWith S.serial (S.fromEffect . return) [n..n+value]
  -}
  
  -------------------------------------------------------------------------------
  -- Elimination
  -------------------------------------------------------------------------------
  
  {-# INLINE runStream #-}
  runStream :: Monad m => Stream m a -> m ()
  runStream = S.drain
  -- runStream = S.mapM_ (\_ -> return ())
  
  {-
  {-# INLINE mapM_ #-}
  mapM_ :: Monad m => Stream m a -> m ()
  mapM_ = S.mapM_ (\_ -> return ())
  -}
  
  {-# INLINE toNull #-}
  toNull :: Monad m => Stream m Int -> m ()
  toNull = runStream
  
  {-# 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
  
  {-
  {-# INLINE init #-}
  init :: (Monad m, S.IsStream t) => t m a -> m ()
  init s = do
      t <- S.init s
      P.mapM_ S.drain t
  
  {-# INLINE tail #-}
  tail :: (Monad m, S.IsStream t) => t m a -> m ()
  tail s = S.tail s >>= P.mapM_ tail
  
  {-# INLINE nullTail #-}
  {-# INLINE headTail #-}
  {-# INLINE zip #-}
  nullTail, headTail, zip
      :: Monad m
      => Stream m Int -> m ()
  
  nullTail s = do
      r <- S.null s
      when (not r) $ S.tail s >>= P.mapM_ nullTail
  
  headTail s = do
      h <- S.head s
      when (isJust h) $ S.tail s >>= P.mapM_ headTail
  
  {-# INLINE toList #-}
  toList :: Monad m => Stream m Int -> m [Int]
  toList = S.toList
  
  {-# INLINE foldl #-}
  foldl :: Monad m => Stream m Int -> m Int
  foldl  = S.foldl' (+) 0
  
  {-# INLINE last #-}
  last :: Monad m => Stream m Int -> m (Maybe Int)
  last   = S.last
  -}
  
  -------------------------------------------------------------------------------
  -- Transformation
  -------------------------------------------------------------------------------
  
  {-
  {-# INLINE transform #-}
  transform :: Monad m => Stream m a -> m ()
  transform = runStream
  
  {-# INLINE composeN #-}
  composeN
      :: Monad m
      => Int -> (Stream m Int -> Stream m Int) -> Stream m Int -> m ()
  composeN n f =
      case n of
          1 -> transform . f
          2 -> transform . f . f
          3 -> transform . f . f . f
          4 -> transform . f . f . f . f
          _ -> undefined
  -}
  
  {-
  {-# INLINE scan #-}
  {-# INLINE map #-}
  {-# INLINE fmap #-}
  {-# INLINE filterEven #-}
  {-# INLINE filterAllOut #-}
  {-# INLINE filterAllIn #-}
  {-# INLINE takeOne #-}
  {-# INLINE takeAll #-}
  {-# INLINE takeWhileTrue #-}
  {-# INLINE dropOne #-}
  {-# INLINE dropAll #-}
  {-# INLINE dropWhileTrue #-}
  {-# INLINE dropWhileFalse #-}
  {-# INLINE foldlS #-}
  {-# INLINE concatMap #-}
  scan, map, fmap, filterEven, filterAllOut,
      filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropOne,
      dropWhileTrue, dropWhileFalse, foldlS, concatMap
      :: Monad m
      => Int -> Stream m Int -> m ()
  
  {-# INLINE mapM #-}
  {-# INLINE mapMSerial #-}
  {-# INLINE intersperse #-}
  mapM, mapMSerial, intersperse
      :: S.MonadAsync m => Int -> Stream m Int -> m ()
  
  scan           n = composeN n $ S.scanl' (+) 0
  map            n = composeN n $ P.fmap (+1)
  fmap           n = composeN n $ P.fmap (+1)
  mapM           n = composeN n $ S.mapM return
  mapMSerial     n = composeN n $ S.mapMSerial return
  filterEven     n = composeN n $ S.filter even
  filterAllOut   n = composeN n $ S.filter (> maxValue)
  filterAllIn    n = composeN n $ S.filter (<= maxValue)
  takeOne        n = composeN n $ S.take 1
  takeAll        n = composeN n $ S.take maxValue
  takeWhileTrue  n = composeN n $ S.takeWhile (<= maxValue)
  dropOne        n = composeN n $ S.drop 1
  dropAll        n = composeN n $ S.drop maxValue
  dropWhileTrue  n = composeN n $ S.dropWhile (<= maxValue)
  dropWhileFalse n = composeN n $ S.dropWhile (<= 1)
  foldlS         n = composeN n $ S.foldlS (flip S.cons) S.nil
  -- We use a (sqrt n) element stream as source and then concat the same stream
  -- for each element to produce an n element stream.
  concatMap      n = composeN n $ (\s -> S.concatMap (\_ -> s) s)
  intersperse    n = composeN n $ S.intersperse maxValue
  
  -------------------------------------------------------------------------------
  -- Iteration
  -------------------------------------------------------------------------------
  
  iterStreamLen, maxIters :: Int
  iterStreamLen = 10
  maxIters = 10000
  
  {-# INLINE iterateSource #-}
  iterateSource
      :: S.MonadAsync m
      => (Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int
  iterateSource g i n = f i (sourceUnfoldrMN iterStreamLen n)
      where
          f (0 :: Int) m = g m
          f x m = g (f (x P.- 1) m)
  
  {-# INLINE iterateMapM #-}
  {-# INLINE iterateScan #-}
  {-# INLINE iterateFilterEven #-}
  {-# INLINE iterateTakeAll #-}
  {-# INLINE iterateDropOne #-}
  {-# INLINE iterateDropWhileFalse #-}
  {-# INLINE iterateDropWhileTrue #-}
  iterateMapM, iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne,
      iterateDropWhileFalse, iterateDropWhileTrue
      :: S.MonadAsync m
      => Int -> Stream m Int
  
  -- this is quadratic
  iterateScan            = iterateSource (S.scanl' (+) 0) (maxIters `div` 10)
  iterateDropWhileFalse  = iterateSource (S.dropWhile (> maxValue))
                                         (maxIters `div` 10)
  
  iterateMapM            = iterateSource (S.mapM return) maxIters
  iterateFilterEven      = iterateSource (S.filter even) maxIters
  iterateTakeAll         = iterateSource (S.take maxValue) maxIters
  iterateDropOne         = iterateSource (S.drop 1) maxIters
  iterateDropWhileTrue   = iterateSource (S.dropWhile (<= maxValue)) maxIters
  
  -------------------------------------------------------------------------------
  -- Zipping and concat
  -------------------------------------------------------------------------------
  
  zip src       = transform $ S.zipWith (,) src src
  
  {-# INLINE concatMapRepl4xN #-}
  concatMapRepl4xN :: Monad m => Stream m Int -> m ()
  concatMapRepl4xN src = transform $ (S.concatMap (S.replicate 4) src)
  
  -------------------------------------------------------------------------------
  -- Mixed Composition
  -------------------------------------------------------------------------------
  
  {-# INLINE scanMap #-}
  {-# INLINE dropMap #-}
  {-# INLINE dropScan #-}
  {-# INLINE takeDrop #-}
  {-# INLINE takeScan #-}
  {-# INLINE takeMap #-}
  {-# INLINE filterDrop #-}
  {-# INLINE filterTake #-}
  {-# INLINE filterScan #-}
  {-# INLINE filterMap #-}
  scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop,
      filterTake, filterScan, filterMap
      :: Monad m => Int -> Stream m Int -> m ()
  
  scanMap    n = composeN n $ S.map (subtract 1) . S.scanl' (+) 0
  dropMap    n = composeN n $ S.map (subtract 1) . S.drop 1
  dropScan   n = composeN n $ S.scanl' (+) 0 . S.drop 1
  takeDrop   n = composeN n $ S.drop 1 . S.take maxValue
  takeScan   n = composeN n $ S.scanl' (+) 0 . S.take maxValue
  takeMap    n = composeN n $ S.map (subtract 1) . S.take maxValue
  filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue)
  filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue)
  filterScan n = composeN n $ S.scanl' (+) 0 . S.filter (<= maxBound)
  filterMap  n = composeN n $ S.map (subtract 1) . S.filter (<= maxValue)
  
  -------------------------------------------------------------------------------
  -- Nested Composition
  -------------------------------------------------------------------------------
  
  {-# INLINE toNullApNested #-}
  toNullApNested :: Monad m => Stream m Int -> m ()
  toNullApNested s = runStream $ do
      (+) <$> s <*> s
  
  {-# INLINE toNullNested #-}
  toNullNested :: Monad m => Stream m Int -> m ()
  toNullNested s = runStream $ do
      x <- s
      y <- s
      return $ x + y
  
  {-# INLINE toNullNested3 #-}
  toNullNested3 :: Monad m => Stream m Int -> m ()
  toNullNested3 s = runStream $ do
      x <- s
      y <- s
      z <- s
      return $ x + y + z
  
  {-# INLINE filterAllOutNested #-}
  filterAllOutNested
      :: Monad m
      => Stream m Int -> m ()
  filterAllOutNested str = runStream $ do
      x <- str
      y <- str
      let s = x + y
      if s < 0
      then return s
      else S.nil
  
  {-# INLINE filterAllInNested #-}
  filterAllInNested
      :: Monad m
      => Stream m Int -> m ()
  filterAllInNested str = runStream $ do
      x <- str
      y <- str
      let s = x + y
      if s > 0
      then return s
      else S.nil
  
  -------------------------------------------------------------------------------
  -- Nested Composition Pure lists
  -------------------------------------------------------------------------------
  
  {-# INLINE sourceUnfoldrList #-}
  sourceUnfoldrList :: Int -> Int -> [Int]
  sourceUnfoldrList maxval n = List.unfoldr step n
      where
      step cnt =
          if cnt > n + maxval
          then Nothing
          else Just (cnt, cnt + 1)
  
  {-# INLINE toNullApNestedList #-}
  toNullApNestedList :: [Int] -> [Int]
  toNullApNestedList s = (+) <$> s <*> s
  
  {-# INLINE toNullNestedList #-}
  toNullNestedList :: [Int] -> [Int]
  toNullNestedList s = do
      x <- s
      y <- s
      return $ x + y
  
  {-# INLINE toNullNestedList3 #-}
  toNullNestedList3 :: [Int] -> [Int]
  toNullNestedList3 s = do
      x <- s
      y <- s
      z <- s
      return $ x + y + z
  
  {-# INLINE filterAllOutNestedList #-}
  filterAllOutNestedList :: [Int] -> [Int]
  filterAllOutNestedList str = do
      x <- str
      y <- str
      let s = x + y
      if s < 0
      then return s
      else []
  
  {-# INLINE filterAllInNestedList #-}
  filterAllInNestedList :: [Int] -> [Int]
  filterAllInNestedList str = do
      x <- str
      y <- str
      let s = x + y
      if s > 0
      then return s
      else []
  -}
  
  -------------------------------------------------------------------------------
  -- Benchmarks
  -------------------------------------------------------------------------------
  
  moduleName :: String
  moduleName = "Data.Stream.StreamDK"
  
  o_1_space :: [Benchmark]
  o_1_space =
      [ bgroup (o_1_space_prefix moduleName)
        [ bgroup "generation"
          [ benchFold "unfoldr"       toNull sourceUnfoldr
          , benchFold "unfoldrM"      toNull sourceUnfoldrM
          ]
        , bgroup "elimination"
          [ benchFold "toNull"   toNull   sourceUnfoldrM
          , benchFold "uncons"   uncons   sourceUnfoldrM
          ]
        ]
      ]
  
  main :: IO ()
  main = defaultMain [o_1_space]
  