#include "inline.hs"

-- |
-- Module      : Streamly.Data.Unfold
-- Copyright   : (c) 2019 Composewell Technologies
-- License     : BSD3
-- Maintainer  : streamly@composewell.com
-- Stability   : released
-- Portability : GHC
--
-- An 'Unfold' is a source or a producer of a stream of values.  It takes a
-- seed value as an input and unfolds it into a sequence of values.
--
-- >>> import qualified Streamly.Data.Fold as Fold
-- >>> import qualified Streamly.Data.Unfold as Unfold
-- >>> import qualified Streamly.Prelude as Stream
--
-- For example, the 'fromList' Unfold generates a stream of values from a
-- supplied list.  Unfolds can be converted to 'Streamly.Prelude.SerialT'
-- stream using the Stream.unfold operation.
--
-- >>> stream = Stream.unfold Unfold.fromList [1..100]
-- >>> Stream.sum stream
-- 5050
--
-- All the serial stream generation operations in "Streamly.Prelude"
-- can be expressed using unfolds:
--
-- > Stream.fromList = Stream.unfold Unfold.fromList [1..100]
--
-- Conceptually, an 'Unfold' is just like "Data.List.unfoldr". Let us write a
-- step function to unfold a list using "Data.List.unfoldr":
--
-- >>> :{
--  f [] = Nothing
--  f (x:xs) = Just (x, xs)
-- :}
--
-- >>> Data.List.unfoldr f [1,2,3]
-- [1,2,3]
--
-- Unfold.unfoldr is just the same, it uses the same step function:
--
-- >>> Stream.toList $ Stream.unfold (Unfold.unfoldr f) [1,2,3]
-- [1,2,3]
--
-- The input of an unfold can be transformed using 'lmap':
--
-- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList
-- >>> Stream.toList $ Stream.unfold u [1..5]
-- [2,3,4,5,6]
--
-- 'Unfold' streams can be transformed using transformation combinators. For
-- example, to retain only the first two elements of an unfold:
--
-- >>> u = Unfold.take 2 Unfold.fromList
-- >>> Stream.toList $ Stream.unfold u [1..100]
-- [1,2]
--
-- Multiple unfolds can be combined in several interesting ways. For example,
-- to generate nested looping as in imperative languages (also known as cross
-- product of the two streams):
--
-- >>> u1 = Unfold.lmap fst Unfold.fromList
-- >>> u2 = Unfold.lmap snd Unfold.fromList
-- >>> u = Unfold.crossWith (,) u1 u2
-- >>> Stream.toList $ Stream.unfold u ([1,2,3], [4,5,6])
-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
--
-- Nested loops using unfolds provide C like performance due to complete stream
-- fusion.
--
-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
-- functions.
--
-- = Unfolds vs. Streams
--
-- Unfolds' raison d'etre is their efficiency in nested stream operations due
-- to complete stream fusion.  'Streamly.Prelude.concatMap' or the 'Monad'
-- instance of streams use stream generation operations of the shape @a -> t m
-- b@ and then flatten the resulting stream.  This implementation is more
-- powerful but does not allow for complete stream fusion.  Unfolds provide
-- less powerful but more efficient 'Streamly.Prelude.unfoldMany', 'many' and
-- 'crossWith' operations as an alternative to a subset of use cases of
-- 'concatMap' and 'Applicative' stream operations.
--
-- "Streamly.Prelude" exports polymorphic stream generation operations that
-- provide the same functionality as unfolds in this module.  Since unfolds can
-- be easily converted to streams, several modules in streamly provide only
-- unfolds for serial stream generation.  We cannot use unfolds exclusively for
-- stream generation as they do not support concurrency.

module Streamly.Data.Unfold
    (
    -- * Unfold Type
      Unfold

    -- * Unfolds
    -- One to one correspondence with
    -- "Streamly.Internal.Data.Stream.IsStream.Generate"

    -- ** Basic Constructors
    , unfoldrM
    , unfoldr
    , function
    , functionM

    -- ** Generators
    -- | Generate a monadic stream from a seed.
    , repeatM
    , replicateM
    , iterateM

    -- ** From Containers
    , fromList
    , fromListM
    , fromStream

    -- * Combinators
    -- ** Mapping on Input
    , lmap
    , lmapM

    -- ** Mapping on Output
    , mapM

    -- ** Filtering
    , takeWhileM
    , takeWhile
    , take
    , filter
    , filterM
    , drop
    , dropWhile
    , dropWhileM

    -- ** Zipping
    , zipWith

    -- ** Cross Product
    , crossWith

    -- ** Nesting
    , many
    )
where

import Prelude hiding
    ( concat, map, mapM, takeWhile, take, filter, const, drop, dropWhile
    , zipWith
    )
import Streamly.Internal.Data.Stream.IsStream.Type (IsStream)
import Streamly.Internal.Data.Unfold hiding (fromStream)

import qualified Streamly.Internal.Data.Stream.IsStream.Type as IsStream
import qualified Streamly.Internal.Data.Unfold as Unfold

-- | Convert a stream into an 'Unfold'. Note that a stream converted to an
-- 'Unfold' may not be as efficient as an 'Unfold' in some situations.
--
-- /Since: 0.8.0/
--
{-# INLINE_NORMAL fromStream #-}
fromStream :: (IsStream t, Monad m) => Unfold m (t m a) a
fromStream :: forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(IsStream t, Monad m) =>
Unfold m (t m a) a
fromStream = forall a c (m :: * -> *) b.
(a -> c) -> Unfold m c b -> Unfold m a b
lmap forall (t1 :: (* -> *) -> * -> *) (t2 :: (* -> *) -> * -> *)
       (m :: * -> *) a.
(IsStream t1, IsStream t2) =>
t1 m a -> t2 m a
IsStream.adapt forall (m :: * -> *) a. Monad m => Unfold m (SerialT m a) a
Unfold.fromStream