packages feed

streamly-0.1.2: test/Main.hs

{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE RankNTypes          #-}

module Main (main) where

import Control.Concurrent (threadDelay)
import Control.Monad (replicateM)
import Data.Foldable (forM_)
import Data.List (sort)
import Data.Maybe (fromJust)
import Test.Hspec

import Streamly
import qualified Streamly.Prelude as A

toListSerial :: StreamT IO a -> IO [a]
toListSerial = A.toList . serially

toListInterleaved :: InterleavedT IO a -> IO [a]
toListInterleaved = A.toList . interleaving

toListAsync :: AsyncT IO a -> IO [a]
toListAsync = A.toList . asyncly

toListParallel :: Ord a => ParallelT IO a -> IO [a]
toListParallel = fmap sort . A.toList . parallely

main :: IO ()
main = hspec $ do
    describe "Runners" $ do
        it "simple serially" $
            (runStreaming . serially) (return (0 :: Int)) `shouldReturn` ()
        it "simple serially with IO" $
            (runStreaming . serially) (liftIO $ putStrLn "hello") `shouldReturn` ()
        it "Captures a return value using toList" $
            toListSerial (return 0) `shouldReturn` ([0] :: [Int])

    describe "Empty" $ do
        it "Monoid - mempty" $
            (toListSerial mempty) `shouldReturn` ([] :: [Int])
        it "Alternative - empty" $
            (toListSerial empty) `shouldReturn` ([] :: [Int])
        it "MonadPlus - mzero" $
            (toListSerial mzero) `shouldReturn` ([] :: [Int])

    ---------------------------------------------------------------------------
    -- Functor
    ---------------------------------------------------------------------------

    describe "Functor (fmap)" $ do
        it "Simple fmap" $
            (toListSerial $ fmap (+1) (return 1)) `shouldReturn` ([2] :: [Int])
        it "fmap on composed (<>)" $
            (toListSerial $ fmap (+1) (return 1 <> return 2))
                `shouldReturn` ([2,3] :: [Int])
        it "fmap on composed (<|>)" $
            (toListSerial $ fmap (+1) (return 1 <|> return 2))
                `shouldReturn` ([2,3] :: [Int])

    ---------------------------------------------------------------------------
    -- Applicative
    ---------------------------------------------------------------------------

    describe "Applicative" $ do
        it "Simple apply" $
            (toListSerial $ (,) <$> (return 1) <*> (return 2))
                `shouldReturn` ([(1,2)] :: [(Int, Int)])

        it "Apply - serial composed first argument" $
            (toListSerial $ (,) <$> (return 1 <> return 2) <*> (return 3))
                `shouldReturn` ([(1,3),(2,3)] :: [(Int, Int)])

        it "Apply - serial composed second argument" $
            (toListSerial $ (,) <$> (return 1) <*> (return 2 <> return 3))
                `shouldReturn` ([(1,2),(1,3)] :: [(Int, Int)])

        it "Apply - parallel composed first argument" $
            (toListSerial $ (,) <$> (return 1 <|> return 2) <*> (return 3))
                `shouldReturn` ([(1,3),(2,3)] :: [(Int, Int)])

        it "Apply - parallel composed second argument" $
            (toListSerial $ (,) <$> (return 1) <*> (return 2 <|> return 3))
                `shouldReturn` ([(1,2),(1,3)] :: [(Int, Int)])

    ---------------------------------------------------------------------------
    -- Binds
    ---------------------------------------------------------------------------

    describe "Bind then" thenBind
    describe "Pure bind serial" $ pureBind toListSerial
    describe "Pure bind serial interleaved" $ pureBind toListInterleaved
    describe "Pure bind parallel DFS" $ pureBind toListAsync
    describe "Pure bind parallel BFS" $ pureBind toListParallel

    describe "Bind (>>=) with empty" $ bindEmpty toListSerial
    describe "Bind (>->) with empty" $ bindEmpty toListInterleaved
    describe "Bind (>|>) with empty" $ bindEmpty toListAsync
    describe "Bind (>>|) with empty" $ bindEmpty toListParallel

    ---------------------------------------------------------------------------
    -- Monoidal Compositions
    ---------------------------------------------------------------------------

    describe "Serial Composition (<>)" $ compose (<>) id
    describe "Serial Composition (mappend)" $ compose mappend id
    describe "Interleaved Composition (<>)" $ compose (<=>) sort
    describe "Left biased parallel Composition (<|)" $ compose (<|) sort
    describe "Fair parallel Composition (<|>)" $ compose (<|>) sort
    describe "Fair parallel Composition (mplus)" $ compose mplus sort

    ---------------------------------------------------------------------------
    -- Monoidal Composition ordering checks
    ---------------------------------------------------------------------------

    describe "Serial interleaved ordering check (<=>)" $ interleaveCheck (<=>)
    describe "Parallel interleaved ordering check (<|>)" $ interleaveCheck (<|>)
    describe "Left biased parallel time order check" $ parallelCheck (<|)
    describe "Fair parallel time order check" $ parallelCheck (<|>)

    ---------------------------------------------------------------------------
    -- TBD Monoidal composition combinations
    ---------------------------------------------------------------------------

    -- TBD need more such combinations to be tested.
    describe "<> and <>" $ composeAndComposeSimple (<>) (<>) (cycle [[1 .. 9]])

    describe "<> and <=>" $ composeAndComposeSimple
      (<>)
      (<=>)
      ([ [1 .. 9]
       , [1 .. 9]
       , [1, 3, 2, 4, 6, 5, 7, 9, 8]
       , [1, 3, 2, 4, 6, 5, 7, 9, 8]
       ])

    describe "<=> and <=>" $ composeAndComposeSimple
      (<=>)
      (<=>)
      ([ [1, 4, 2, 7, 3, 5, 8, 6, 9]
       , [1, 7, 4, 8, 2, 9, 5, 3, 6]
       , [1, 4, 3, 7, 2, 6, 9, 5, 8]
       , [1, 7, 4, 9, 3, 8, 6, 2, 5]
       ])

    describe "<=> and <>" $ composeAndComposeSimple
      (<=>)
      (<>)
      ([ [1, 4, 2, 7, 3, 5, 8, 6, 9]
       , [1, 7, 4, 8, 2, 9, 5, 3, 6]
       , [1, 4, 2, 7, 3, 5, 8, 6, 9]
       , [1, 7, 4, 8, 2, 9, 5, 3, 6]
       ])

    describe "Nested parallel and serial compositions" $ do
        {-
        -- This is not correct, the result can also be [4,4,8,0,8,0,2,2]
        -- because of parallelism of [8,0] and [8,0].
        it "Nest <|>, <>, <|> (1)" $
            let t = timed
             in toListSerial (
                    ((t 8 <|> t 4) <> (t 2 <|> t 0))
                <|> ((t 8 <|> t 4) <> (t 2 <|> t 0)))
            `shouldReturn` ([4,4,8,8,0,0,2,2])
        -}
        it "Nest <|>, <>, <|> (2)" $
            let t = timed
             in toListSerial (
                    ((t 4 <|> t 8) <> (t 1 <|> t 2))
                <|> ((t 4 <|> t 8) <> (t 1 <|> t 2)))
            `shouldReturn` ([4,4,8,8,1,1,2,2])
        -- FIXME: These two keep failing intermittently on Mac OS X
        -- Need to examine and fix the tests.
        {-
        it "Nest <|>, <=>, <|> (1)" $
            let t = timed
             in toListSerial (
                    ((t 8 <|> t 4) <=> (t 2 <|> t 0))
                <|> ((t 9 <|> t 4) <=> (t 2 <|> t 0)))
            `shouldReturn` ([4,4,0,0,8,2,9,2])
        it "Nest <|>, <=>, <|> (2)" $
            let t = timed
             in toListSerial (
                    ((t 4 <|> t 8) <=> (t 1 <|> t 2))
                <|> ((t 4 <|> t 9) <=> (t 1 <|> t 2)))
            `shouldReturn` ([4,4,1,1,8,2,9,2])
        -}
        it "Nest <|>, <|>, <|>" $
            let t = timed
             in toListSerial (
                    ((t 4 <|> t 8) <|> (t 0 <|> t 2))
                <|> ((t 4 <|> t 8) <|> (t 0 <|> t 2)))
            `shouldReturn` ([0,0,2,2,4,4,8,8])

    ---------------------------------------------------------------------------
    -- Monoidal composition recursion loops
    ---------------------------------------------------------------------------

    describe "Serial loops (<>)" $ loops (<>) id reverse
    describe "Left biased parallel loops (<|)" $ loops (<|) sort sort
    describe "Fair parallel loops (<|>)" $ loops (<|>) sort sort

    ---------------------------------------------------------------------------
    -- Bind and monoidal composition combinations
    ---------------------------------------------------------------------------

    forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
        describe "Bind and compose" $ bindAndComposeSimple toListSerial g

    forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
        describe "Bind and compose" $ bindAndComposeSimple toListInterleaved g

    forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
        describe "Bind and compose" $ bindAndComposeSimple toListAsync g

    forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
        describe "Bind and compose" $ bindAndComposeSimple toListParallel g

    let fldr f = foldr f empty
        fldl f = foldl f empty
     in do
        forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
            forM_ [fldr, fldl] $ \k ->
                describe "Bind and compose" $
                    bindAndComposeHierarchy toListSerial (k g)
        forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
            forM_ [fldr, fldl] $ \k ->
                describe "Bind and compose" $
                    bindAndComposeHierarchy toListInterleaved (k g)
        forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
            forM_ [fldr, fldl] $ \k ->
                describe "Bind and compose" $
                    bindAndComposeHierarchy toListAsync (k g)
        forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
            forM_ [fldr, fldl] $ \k ->
                describe "Bind and compose" $
                    bindAndComposeHierarchy toListParallel (k g)

    -- Nest two lists using different styles of product compositions
    it "Nests two streams using monadic serial composition" nestTwoSerial
    it "Nests two streams using monadic interleaved composition" nestTwoInterleaved
    it "Nests two streams using monadic async composition" nestTwoAsync
    it "Nests two streams using monadic parallel composition" nestTwoParallel

    it "Nests two streams using applicative serial composition" nestTwoSerialApp
    it "Nests two streams using applicative interleaved composition" nestTwoInterleavedApp
    it "Nests two streams using applicative async composition" nestTwoAsyncApp
    it "Nests two streams using applicative parallel composition" nestTwoParallelApp

    it "Nests two streams using Num serial composition" nestTwoSerialNum
    it "Nests two streams using Num interleaved composition" nestTwoInterleavedNum
    it "Nests two streams using Num async composition" nestTwoAsyncNum
    -- This test fails intermittently, need to investigate
    -- it "Nests two streams using Num parallel composition" nestTwoParallelNum

    ---------------------------------------------------------------------------
    -- TBD Bind and Bind combinations
    ---------------------------------------------------------------------------

    -- TBD combine all binds and all compose in one example
    describe "Miscellaneous combined examples" mixedOps

    ---------------------------------------------------------------------------
    -- Stream operations
    ---------------------------------------------------------------------------

    -- XXX for streams other than StreamT
    describe "Stream Ops empty" $ streamOperations makeEmptyStream
    describe "Stream ops singleton constr" $ streamOperations makeSingletonStream1
    describe "Stream ops singleton folded" $ streamOperations makeSingletonStream2
    describe "Stream Ops constr" $ streamOperations makeStream1
    describe "Stream Ops folded" $ streamOperations $ makeStream2
          ((<>) :: StreamT IO Int -> StreamT IO Int -> StreamT IO Int)

    describe "Serial zipping" $
        zipOps A.zipWith A.zipWithM zipping
    describe "Async zipping" $
        zipOps A.zipAsyncWith A.zipAsyncWithM zippingAsync

makeEmptyStream :: (StreamT IO Int, [Int], Int)
makeEmptyStream = (A.nil, [], 0)

makeSingletonStream1 :: (StreamT IO Int, [Int], Int)
makeSingletonStream1 = (1 `A.cons` A.nil, [1], 1)

makeSingletonStream2 :: (StreamT IO Int, [Int], Int)
makeSingletonStream2 = (return 1, [1], 1)

-- Streams that indicate an end via the stop continuation
makeStream1 :: (StreamT IO Int, [Int], Int)
makeStream1 =
    let list = [1..10]
        stream = A.each list
    in (stream, list, 10)

-- Streams that indicate an end via the yield continuation
makeStream2 :: (Streaming t, Monad (t IO))
    => (t IO Int -> t IO Int -> t IO Int)
    -> (t IO Int, [Int], Int)
makeStream2 f =
    let list = [1..10]
        stream = foldMapWith f return list
    in (stream, list, 10)

nestTwoSerial :: Expectation
nestTwoSerial =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListSerial (do
        x <- s1
        y <- s2
        return (x + y)
        ) `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])

nestTwoSerialApp :: Expectation
nestTwoSerialApp =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListSerial ((+) <$> s1 <*> s2)
        `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])

nestTwoSerialNum :: Expectation
nestTwoSerialNum =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListSerial (s1 + s2)
        `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])

nestTwoInterleaved :: Expectation
nestTwoInterleaved =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListInterleaved (do
        x <- s1
        y <- s2
        return (x + y)
        ) `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])

nestTwoInterleavedApp :: Expectation
nestTwoInterleavedApp =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListInterleaved ((+) <$> s1 <*> s2)
        `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])

nestTwoInterleavedNum :: Expectation
nestTwoInterleavedNum =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListInterleaved (s1 + s2)
        `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])

nestTwoAsync :: Expectation
nestTwoAsync =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListAsync (do
        x <- s1
        y <- s2
        return (x + y)
        ) `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])

nestTwoAsyncApp :: Expectation
nestTwoAsyncApp =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListAsync ((+) <$> s1 <*> s2)
        `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])

nestTwoAsyncNum :: Expectation
nestTwoAsyncNum =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListAsync (s1 + s2)
        `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])

nestTwoParallel :: Expectation
nestTwoParallel =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListParallel (do
        x <- s1
        y <- s2
        return (x + y)
        ) `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])

nestTwoParallelApp :: Expectation
nestTwoParallelApp =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListParallel ((+) <$> s1 <*> s2)
        `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])

{-
nestTwoParallelNum :: Expectation
nestTwoParallelNum =
    let s1 = foldMapWith (<>) return [1..4]
        s2 = foldMapWith (<>) return [5..8]
    in toListParallel (s1 + s2)
        `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])
-}

zipOps :: (Streaming t, Applicative (t IO))
    => (forall a b c. (a -> b -> c)
        -> StreamT IO a -> StreamT IO b -> StreamT IO c)
    -> (forall a b c. (a -> b -> StreamT IO c)
        -> StreamT IO a -> StreamT IO b -> StreamT IO c)
    -> (forall a. t IO a -> t IO a)
    -> Spec
zipOps z zM app = do
    it "zipWith" $
        let s1 = foldMapWith (<>) return [1..10]
            s2 = foldMapWith (<>) return [1..]
         in toListSerial (z (+) s1 s2)
        `shouldReturn` ([2,4..20] :: [Int])

    it "zipWithM" $
        let s1 = foldMapWith (<>) return [1..10]
            s2 = foldMapWith (<>) return [1..]
         in toListSerial (zM (\a b -> return (a + b)) s1 s2)
        `shouldReturn` ([2,4..20] :: [Int])

    it "Applicative zip" $
        let s1 = adapt $ serially $ foldMapWith (<>) return [1..10]
            s2 = adapt $ serially $ foldMapWith (<>) return [1..]
            f = A.toList . app
            functorial = f $ (+) <$> s1 <*> s2
            applicative = f $ pure (+) <*> s1 <*> s2
            expected = ([2,4..20] :: [Int])
         in (,) <$> functorial <*> applicative
        `shouldReturn` (expected, expected)

timed :: Int -> StreamT IO Int
timed x = liftIO (threadDelay (x * 100000)) >> return x

thenBind :: Spec
thenBind = do
    it "Simple runStreaming and 'then' with IO" $
        (runStreaming . serially) (liftIO (putStrLn "hello") >> liftIO (putStrLn "world"))
            `shouldReturn` ()
    it "Then and toList" $
        toListSerial (return (1 :: Int) >> return 2) `shouldReturn` ([2] :: [Int])

type ToListType s = (forall a. Ord a => s IO a -> IO [a])
pureBind :: Monad (s IO) => ToListType s -> Spec
pureBind l = do
    it "Bind and toList" $
        l (return 1 `f` \x -> return 2 `f` \y -> return (x + y))
            `shouldReturn` ([3] :: [Int])
    where f = (>>=)

bindEmpty :: (Monad (s IO), Alternative (s IO)) => ToListType s -> Spec
bindEmpty l = it "Binds with empty" $
    (l (return (1 :: Int) `f` \_ -> empty `f` \_ -> return 2))
        `shouldReturn` ([] :: [Int])
    where f = (>>=)

interleaveCheck
    :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
    -> Spec
interleaveCheck f =
    it "Interleave four" $
        toListSerial ((return 0 <> return 1) `f` (return 100 <> return 101))
            `shouldReturn` ([0, 100, 1, 101])

parallelCheck :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int) -> Spec
parallelCheck f = do
    it "Parallel ordering left associated" $
        toListSerial (((event 4 `f` event 3) `f` event 2) `f` event 1)
            `shouldReturn` ([1..4])

    it "Parallel ordering right associated" $
        toListSerial (event 4 `f` (event 3 `f` (event 2 `f` event 1)))
            `shouldReturn` ([1..4])

    where event n = (liftIO $ threadDelay (n * 100000)) >> (return n)

compose
    :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
    -> ([Int] -> [Int])
    -> Spec
compose f srt = do
    it "Compose mempty, mempty" $
        (tl (mempty `f` mempty)) `shouldReturn` []
    it "Compose empty, empty" $
        (tl (empty `f` empty)) `shouldReturn` []
    it "Compose empty at the beginning" $
        (tl $ (empty `f` return 1)) `shouldReturn` [1]
    it "Compose empty at the end" $
        (tl $ (return 1 `f` empty)) `shouldReturn` [1]
    it "Compose two" $
        (tl (return 0 `f` return 1) >>= return . srt)
            `shouldReturn` [0, 1]
    it "Compose three - empty in the middle" $
        ((tl $ (return 0 `f` empty `f` return 1)) >>= return . srt)
            `shouldReturn` [0, 1]
    it "Compose left associated" $
        ((tl $ (((return 0 `f` return 1) `f` return 2) `f` return 3))
            >>= return . srt) `shouldReturn` [0, 1, 2, 3]
    it "Compose right associated" $
        ((tl $ (return 0 `f` (return 1 `f` (return 2 `f` return 3))))
            >>= return . srt) `shouldReturn` [0, 1, 2, 3]
    it "Compose many" $
        ((tl $ forEachWith f [1..100] return) >>= return . srt)
            `shouldReturn` [1..100]
    it "Compose hierarchical (multiple levels)" $
        ((tl $ (((return 0 `f` return 1) `f` (return 2 `f` return 3))
                `f` ((return 4 `f` return 5) `f` (return 6 `f` return 7)))
            ) >>= return . srt) `shouldReturn` [0..7]
    where tl = toListSerial

composeAndComposeSimple
    :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
    -> (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
    -> [[Int]]
    -> Spec
composeAndComposeSimple f g answer = do
    it "Compose right associated outer expr, right folded inner" $
        let fold = foldMapWith g return
         in (toListSerial (fold [1,2,3] `f` (fold [4,5,6] `f` fold [7,8,9])))
            `shouldReturn` (answer !! 0)

    it "Compose left associated outer expr, right folded inner" $
        let fold = foldMapWith g return
         in (toListSerial ((fold [1,2,3] `f` fold [4,5,6]) `f` fold [7,8,9]))
            `shouldReturn` (answer !! 1)

    it "Compose right associated outer expr, left folded inner" $
        let fold xs = foldl g empty $ map return xs
         in (toListSerial (fold [1,2,3] `f` (fold [4,5,6] `f` fold [7,8,9])))
            `shouldReturn` (answer !! 2)

    it "Compose left associated outer expr, left folded inner" $
        let fold xs = foldl g empty $ map return xs
         in (toListSerial ((fold [1,2,3] `f` fold [4,5,6]) `f` fold [7,8,9]))
            `shouldReturn` (answer !! 3)


loops
    :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
    -> ([Int] -> [Int])
    -> ([Int] -> [Int])
    -> Spec
loops f tsrt hsrt = do
    it "Tail recursive loop" $ (toListSerial (loopTail 0) >>= return . tsrt)
            `shouldReturn` [0..3]

    it "Head recursive loop" $ (toListSerial (loopHead 0) >>= return . hsrt)
            `shouldReturn` [0..3]

    where
        loopHead x = do
            -- this print line is important for the test (causes a bind)
            liftIO $ putStrLn "LoopHead..."
            (if x < 3 then loopHead (x + 1) else empty) `f` return x

        loopTail x = do
            -- this print line is important for the test (causes a bind)
            liftIO $ putStrLn "LoopTail..."
            return x `f` (if x < 3 then loopTail (x + 1) else empty)

bindAndComposeSimple
    :: (Streaming t, Alternative (t IO), Monad (t IO))
    => (forall a. Ord a => t IO a -> IO [a])
    -> (t IO Int -> t IO Int -> t IO Int)
    -> Spec
bindAndComposeSimple tl g = do
    it "Compose many (right fold) with bind" $
        (tl (forEachWith g [1..10 :: Int] $ \x -> return x `f` (return .  id))
            >>= return . sort) `shouldReturn` [1..10]

    it "Compose many (left fold) with bind" $
        let forL xs k = foldl g empty $ map k xs
         in (tl (forL [1..10 :: Int] $ \x -> return x `f` (return . id))
                >>= return . sort) `shouldReturn` [1..10]
    where f = (>>=)

bindAndComposeHierarchy
    :: Monad (s IO) => (forall a. Ord a => s IO a -> IO [a])
    -> ([s IO Int] -> s IO Int)
    -> Spec
bindAndComposeHierarchy tl g = do
    it "Bind and compose nested" $
        (tl bindComposeNested >>= return . sort)
            `shouldReturn` (sort (
                   [12, 18]
                ++ replicate 3 13
                ++ replicate 3 17
                ++ replicate 6 14
                ++ replicate 6 16
                ++ replicate 7 15) :: [Int])

    where

    -- bindComposeNested :: AsyncT IO Int
    bindComposeNested =
        let c1 = tripleCompose (return 1) (return 2) (return 3)
            c2 = tripleCompose (return 4) (return 5) (return 6)
            c3 = tripleCompose (return 7) (return 8) (return 9)
            b = tripleBind c1 c2 c3
-- it seems to be causing a huge space leak in hspec so disabling this for now
--            c = tripleCompose b b b
--            m = tripleBind c c c
--         in m
         in b

    tripleCompose a b c = g [a, b, c]
    tripleBind mx my mz =
        mx `f` \x -> my
           `f` \y -> mz
           `f` \z -> return (x + y + z)
    f = (>>=)

mixedOps :: Spec
mixedOps = do
    it "Compose many ops" $
        (toListSerial composeMixed >>= return . sort)
            `shouldReturn` ([8,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,11,11
                            ,11,11,11,11,11,11,11,11,12,12,12,12,12,13
                            ] :: [Int])
    where

    composeMixed :: StreamT IO Int
    composeMixed = do
        liftIO $ return ()
        liftIO $ putStr ""
        x <- return 1
        y <- return 2
        z <- do
                x1 <- return 1 <|> return 2
                liftIO $ return ()
                liftIO $ putStr ""
                y1 <- return 1 <| return 2
                z1 <- do
                    x11 <- return 1 <> return 2
                    y11 <- return 1 <| return 2
                    z11 <- return 1 <=> return 2
                    liftIO $ return ()
                    liftIO $ putStr ""
                    return (x11 + y11 + z11)
                return (x1 + y1 + z1)
        return (x + y + z)

streamOperations :: Streaming t => (t IO Int, [Int], Int) -> Spec
streamOperations (stream, list, len) = do

    -- Generation
    it "replicateM" $ do
            let x = return (1 :: Int)
            str <- A.toList . serially $ A.replicateM len x
            lst <- replicateM len x
            return $ str == lst
        `shouldReturn` True

    it "iterate" $
            (A.toList . serially . (A.take len) $ (A.iterate (+ 1) (0 :: Int)))
            `shouldReturn` (take len $ iterate (+ 1) 0)

    it "iterateM" $ do
              let addM = (\ y -> return (y + 1))
              A.toList . serially . (A.take len) $ A.iterateM addM (0 :: Int)
              `shouldReturn` (take len $ iterate (+ 1) 0)


    -- Filtering
    it "filter all out" $ transform (A.filter (> len)) (filter (> len))
    it "filter all in"  $ transform (A.filter (<= len)) (filter (<= len))
    it "filter even"    $ transform (A.filter even)  (filter even)

    it "take all"  $ transform (A.take len) (take len)
    it "take none" $ transform (A.take 0) (take 0)
    it "take some" $ transform (A.take $ len - 1) (take $ len - 1)
    it "take one" $ transform (A.take 1) (take 1)

    it "takeWhile true"  $ transform (A.takeWhile (const True))
                                     (takeWhile (const True))
    it "takeWhile false" $ transform (A.takeWhile (const False))
                                     (takeWhile (const False))
    it "takeWhile < some" $ transform (A.takeWhile (< (len `div` 2)))
                                      (takeWhile (< (len `div` 2)))

    it "drop all"  $ transform (A.drop len) (drop len)
    it "drop none" $ transform (A.drop 0)  (drop 0)
    it "drop some" $ transform (A.drop $ len - 1)  (drop $ len - 1)
    it "drop one"  $ transform (A.drop 1)  (drop 1)

    it "dropWhile true"  $ transform (A.dropWhile (const True))
                                     (dropWhile (const True))
    it "dropWhile false" $ transform (A.dropWhile (const False))
                                     (dropWhile (const False))
    it "dropWhile < some" $ transform (A.dropWhile (< (len `div` 2)))
                                      (dropWhile (< (len `div` 2)))

    -- Transformations
    it "scan left"  $ transform (A.scan (+) 0 id) (scanl (+) 0)
    it "reverse" $ transform A.reverse reverse

    -- Elimination
    it "foldl" $ elimination (A.foldl (+) 0 id) (foldl (+) 0)
    it "all" $ elimination (A.all even) (all even)
    it "any" $ elimination (A.any even) (any even)
    it "length" $ elimination A.length length
    it "elem" $ elimination (A.elem (len - 1)) (elem (len - 1))
    it "elem" $ elimination (A.elem (len + 1)) (elem (len + 1))
    it "notElem" $ elimination (A.notElem (len - 1)) (notElem (len - 1))
    it "notElem" $ elimination (A.notElem (len + 1)) (notElem (len + 1))
    it "sum" $ elimination A.sum sum
    it "product" $ elimination A.product product

    if list == []
    then do
        it "head empty" $ A.head stream `shouldReturn` Nothing
        it "last empty" $ A.last stream `shouldReturn` Nothing
        it "maximum empty" $ A.maximum stream `shouldReturn` Nothing
        it "minimum empty" $ A.minimum stream `shouldReturn` Nothing
        it "null empty" $ A.null stream `shouldReturn` True
        it "tail empty" $ (A.tail stream >>= return . maybe True (const False))
            `shouldReturn` True
    else do
        it "head nonEmpty" $ A.head stream `shouldReturn` Just (head list)
        it "last nonEmpty" $ A.last stream `shouldReturn` Just (last list)
        it "maximum nonEmpty" $ A.maximum stream
            `shouldReturn` Just (maximum list)
        it "minimum nonEmpty" $ A.minimum stream
            `shouldReturn` Just (minimum list)
        it "null nonEmpty" $ A.null stream `shouldReturn` False
        it "tail nonEmpty" $ (A.tail stream >>= A.toList . fromJust)
            `shouldReturn` tail list

    where
    -- XXX run on empty stream as well
    transform streamOp listOp =
        (A.toList $ streamOp stream) `shouldReturn` listOp list

    elimination streamOp listOp = (streamOp stream) `shouldReturn` listOp list