{-# LANGUAGE CPP #-}

-- |
-- Module      : Streamly.Data.Unfold
-- Copyright   : (c) 2019 Composewell Technologies
-- License     : BSD3
-- Maintainer  : streamly@composewell.com
-- Stability   : released
-- Portability : GHC
--
-- Fast, composable stream producers with ability to terminate, supporting
-- nested stream fusion. Nested stream operations like
-- 'Streamly.Data.Stream.concatMap' in the "Streamly.Data.Stream" module do not
-- fuse, however, the 'Streamly.Data.Stream.unfoldMany' operation, using the
-- 'Unfold' type, is a fully fusible alternative to
-- 'Streamly.Data.Stream.concatMap'.
--
-- Please refer to "Streamly.Internal.Data.Unfold" for more functions that have
-- not yet been released.
--
-- Exception combinators are not exposed, we would like to encourage the use of
-- 'Stream' type instead whenever exception handling is required. We can
-- consider exposing the unfold exception functions if there is a compelling
-- use case to use unfolds instead of stream.

module Streamly.Data.Unfold
    (
    -- * Setup
    -- | To execute the code examples provided in this module in ghci, please
    -- run the following commands first.
    --
    -- $setup

    -- * Overview
    -- $overview

    -- * Unfold Type
      Unfold

    -- * Unfolds
    -- One to one correspondence with
    -- "Streamly.Internal.Data.Stream.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.Unfold

#include "DocTestDataUnfold.hs"

-- $overview
--
-- 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.
--
-- For example, the 'fromList' Unfold generates a stream of values from a
-- supplied list.  Unfolds can be converted to 'Streamly.Internal.Data.Stream'
-- using the 'Stream.unfold' operation.
--
-- >>> stream = Stream.unfold Unfold.fromList [1..100]
-- >>> Stream.fold Fold.sum stream
-- 5050
--
-- The input seed of an unfold can be transformed using 'lmap':
--
-- >>> u = Unfold.lmap (fmap (+1)) Unfold.fromList
-- >>> Stream.fold Fold.toList $ Stream.unfold u [1..5]
-- [2,3,4,5,6]
--
-- Output stream of an 'Unfold' 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.fold Fold.toList $ Stream.unfold u [1..100]
-- [1,2]
--
-- Unfolds can be nested efficiently. For example, to implement nested looping:
--
-- >>> u1 = Unfold.lmap fst Unfold.fromList
-- >>> u2 = Unfold.lmap snd Unfold.fromList
-- >>> u = Unfold.crossWith (,) u1 u2
-- >>> Stream.fold Fold.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)]
--
-- 'Unfold' @u1@ generates a stream from the first list in the input tuple,
-- @u2@ generates another stream from the second list. The combines 'Unfold'
-- @u@ nests the two streams i.e. for each element in first stream, for each
-- element in second stream apply the supplied function (i.e. @(,)@) to the
-- pair of elements.
--
-- This is the equivalent of the nested looping construct from imperative
-- languages, also known as the cross product of two streams in functional
-- parlance.
--
-- Please see "Streamly.Internal.Data.Unfold" for additional @Pre-release@
-- functions.
--
-- == Creating New Unfolds
--
-- There are many commonly used unfolds provided in this module. However, you
-- can always create your own as well.  An 'Unfold' is just a data
-- representation of a stream generator function. It consists of an @inject@
-- function which covnerts the supplied seed into an internal state of the
-- unfold, and a @step@ function which takes the state and generates the next
-- output in the stream. For those familiar with the list "Data.List.unfoldr"
-- function, this is a data representation of the same.
--
-- Smart constructor functions are provided in this module for constructing new
-- 'Unfolds'. For example, you can use the 'Unfold.unfoldr' constructor to
-- create an 'Unfold' from a pure step function, unfoldr uses 'id' as the
-- @inject@ function.
--
-- Let's define a simple pure step function:
--
-- >>> :{
--  f [] = Nothing
--  f (x:xs) = Just (x, xs)
-- :}
--
-- Create an 'Unfold' from the step function:
--
-- >>> u = Unfold.unfoldr f
--
-- Run the 'Unfold':
--
-- >>> Stream.fold Fold.toList $ Stream.unfold u [1,2,3]
-- [1,2,3]
--
-- The 'Unfold.unfoldr' smart constructor is essentially the same as the list
-- "Data.List.unfoldr" function. We can use the same step function in both::
--
-- >>> Data.List.unfoldr f [1,2,3]
-- [1,2,3]
--
-- == Unfolds vs. Streams
--
-- The 'Unfold' abstraction for representing streams was introduced in Streamly
-- to provide C like performance for nested looping of streams. 'Unfold' and
-- 'Stream' abstractions are similar with the following differences:
--
-- * 'Stream' is less efficient than 'Unfold' for nesting.
-- * 'Stream' is more powerful than 'Unfold'.
-- * 'Stream' API is more convenient for programming
--
-- Unfolds can be easily converted to streams using 'Stream.unfold', however,
-- vice versa is not possible. To provide a familiar analogy, 'Unfold' is to
-- 'Stream' as 'Applicative' is to 'Monad'.
--
-- To demonstrate the efficiency of unfolds, the nested loop example in the
-- previous section can be implemented with concatMap or Monad instance of
-- streams as follows:
--
-- @
--  do
--      x <- Stream.unfold Unfold.fromList [1,2,3]
--      y <- Stream.unfold Unfold.fromList [4,5,6]
--      return (x, y)
-- @
--
-- As you can see, this is more convenient to write than using the 'crossWith'
-- unfold combinator. However, this turns out to be many times slower than the
-- unfold implementation. The Unfold version is equivalent in performance to
-- the C implementation of the same nested loop. Similarly, unfolds can be
-- nested with streams using the 'unfoldMany' combinator which is a much more
-- efficient alternative to the 'concatMap' operation.
--
-- Streams use a hybrid implementation approach using direct style as well as
-- CPS. Unfolds do not use CPS, therefore, lack the power that is afforded to
-- streams by CPS. The CPS implementation allows infinitely scalable @cons@ and
-- @append@ operations in streams. It is also used to implement concurrency in
-- streams.
--
-- To summarize, unfolds are a high performance solution to the nesting
-- problem. Since streams provide a more palatable API for programming, work
-- with streams unless you need unfolds for better performance in nesting
-- situations. There is little difference in the way in which unfolds and
-- streams are written, it is easy to adapt a stream to an unfold. If you are
-- writing an unfold you can convert it to stream for free using
-- 'Stream.unfold'.