packages feed

streamly-0.9.0: docs/User/Tutorials/Tutorial.hs

{-# OPTIONS_GHC -fno-warn-unused-imports #-}
-- |
-- Module      : User.Tutorials.Tutorial
-- Copyright   : (c) 2017 Composewell Technologies
--
-- License     : BSD3
-- Maintainer  : streamly@composewell.com
--
-- THIS TUTORIAL IS OBSOLETE.
--
-- In this tutorial we will show how Streamly can be used for idiomatic
-- dataflow programming.  Before you go through this tutorial we recommend that
-- you take a look at the Streamly Getting Started guide so that you are ready
-- to try out the examples.

module User.Tutorials.Tutorial
    (
    -- * Imports
    -- $setup

    -- * Stream Types
    -- $streams

    -- * Imports and Supporting Code
    -- $imports

    -- * Generating Streams
    -- $generating

    -- * Eliminating Streams
    -- $eliminating

    -- * Transforming Streams
    -- $transformation

    -- * Merging Streams

    -- ** Semigroup Style
    -- $semigroup

    -- *** Deep Serial Composition ('Serial')
    -- $serial

    -- *** Wide Serial Composition ('WSerial')
    -- $interleaved

    -- ** Monoid Style
    -- $monoid

    -- * Nesting Streams
    -- $nesting

    -- ** Monad
    -- $monad

    -- *** Deep Serial Nesting ('Serial')
    -- $regularSerial

    -- *** Wide Serial Nesting ('WSerial')
    -- $interleavedNesting

    -- *** Exercise
    -- $monadExercise

    -- ** Applicative
    -- $applicative

    -- ** Functor
    -- $functor

    -- * Zipping Streams
    -- $zipping

    -- ** Serial Zipping
    -- $serialzip

    -- * Where to go next?
    -- $furtherReading
    )
where

import Streamly.Data.Stream
import Data.Semigroup
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class      (MonadIO(..))
import Control.Monad.Trans.Class   (MonadTrans (lift))

-- CAUTION: please keep setup and imports sections in sync

-- XXX This tutorial has to be re-written.

-- $setup
--
-- In most of example snippets we do not repeat the imports. Where
-- imports are not explicitly specified use the imports shown below.
--
-- >>> :m
-- >>> :set -fno-warn-deprecations
-- >>> import Data.Function ((&))
-- >>> import Streamly.Prelude ((|:), (|&))
-- >>> import Streamly.Data.Stream (Stream)
-- >>> import Streamly.Internal.Data.Stream.Cross (CrossStream(..))
-- >>> import qualified Streamly.Prelude as Stream
-- >>> import qualified Streamly.Data.Fold as Fold
--

-- $streams
--
-- The monadic stream API offered by Streamly is very close to the Haskell
-- "Prelude" pure lists' API, it can be considered as a natural extension of
-- lists to monadic actions. Streamly streams provide concurrent composition
-- and merging of streams. It can be considered as a concurrent list
-- transformer.
--
-- The 'Serial' type is almost a drop in replacement for pure lists,
-- pure lists are a special case of monadic streams. If you use 'nil' in place
-- of '[]' and '|:' in place ':' you can replace a list with a 'Serial' stream.
-- The only difference is that the elements must be monadic type and to operate
-- on the streams we must use the corresponding functions from
-- "Streamly.Prelude" instead of using the base "Prelude".

-- $generating
--
-- 'nil' represents an empty stream and 'consM' or its operator form '|:' adds
-- a monadic action at the head of the stream.
--
-- >>> Stream.toList Stream.nil
-- []
--
-- Stream.toList $ getLine |: getLine |: Stream.nil
-- hello
-- world
-- ["hello","world"]
--
-- To create a singleton stream from a pure value use 'fromPure' and to
-- create a singleton stream from a monadic action use 'fromEffect'.
--
-- >>> Stream.toList $ Stream.fromPure 1
-- [1]
--
-- Stream.toList $ Stream.fromEffect getLine
-- hello
-- ["hello"]
--
-- To create a stream from pure values in a 'Foldable' container use
-- 'fromFoldable' which is equivalent to a fold using 'cons' and 'nil':
--
-- >>> Stream.toList $ Stream.fromFoldable [1..3]
-- [1,2,3]
--
-- >>> Stream.toList $ Prelude.foldr Stream.cons Stream.nil [1..3]
-- [1,2,3]
--
-- To create a stream from monadic actions in a 'Foldable' container just use a
-- right fold using 'consM' and 'nil':
--
-- >>> Stream.drain $ Prelude.foldr (|:) Stream.nil [putStr "Hello ", putStrLn "world!"]
-- Hello world!
--
-- For more ways to construct a stream see the module "Streamly.Prelude".

-- $eliminating
--
-- We have already seen 'drain' and toList to eliminate a stream in the
-- examples above.  'drain' runs a stream discarding the results i.e. only
-- for effects.  'toList' runs the stream and collects the results in a list.
--
-- For other ways to eliminate a stream see the @Folding@ section in
-- "Streamly.Prelude" module.

-- $transformation
--
-- Transformation over a stream is the equivalent of a @for@ loop construct in
-- imperative paradigm. We iterate over every element in the stream and perform
-- certain transformations for each element.  Transformations may involve
-- mapping functions over the elements, filtering elements from the stream or
-- folding all the elements in the stream into a single value. Streamly streams
-- are exactly like lists and you can perform all the transformations in the
-- same way as you would on lists.
--
-- Here is a simple console echo program that just echoes every input line,
-- forever:
--
-- >>> :{
-- echo =
--       Stream.repeatM getLine
--     & Stream.mapM putStrLn
--     & Stream.drain
-- :}
--
-- The following code snippet reads lines from standard input, filters blank
-- lines, drops the first non-blank line, takes the next two, up cases them,
-- numbers them and prints them:
--
-- >>> import Data.Char (toUpper)
-- >>> :{
-- main =
--       Stream.repeatM getLine
--     & Stream.filter (not . null)
--     & Stream.drop 1
--     & Stream.take 2
--     & fmap (map toUpper)
--     & Stream.zipWith (\n s -> show n ++ " " ++ s) (Stream.fromFoldable [1..])
--     & Stream.mapM putStrLn
--     & Stream.drain
-- :}
--

-- $semigroup
--
-- We can combine two streams into a single stream using semigroup composition
-- operation '<>'.  Streams can be combined in many different ways as described
-- in the following sections, the '<>' operation behaves differently depending
-- on the stream type in effect. The stream type and therefore the composition
-- style can be changed at any point using one of the type combinators as
-- discussed earlier.

-- $serial
--
-- The 'Semigroup' operation '<>' of the 'Serial' type combines the two streams
-- in a /serial depth first/ manner. We use the 'fromSerial' type combinator to
-- effect 'Serial' style of composition. We can also use an explicit 'Serial'
-- type annotation for the stream to achieve the same effect.  However, since
-- 'Serial' is the default type unless explicitly specified by using a
-- combinator, we can omit using an explicit combinator or type annotation for
-- this style of composition.
--
-- When two streams with multiple elements are combined in this manner, the
-- monadic actions in the two streams are performed sequentially i.e. first all
-- actions in the first stream are performed sequentially and then all actions
-- in the second stream are performed sequentially. We call it
-- /serial depth first/ as the full depth of one stream is fully traversed
-- before we move to the next. The following example prints the sequence 1, 2,
-- 3, 4:
--
-- >>> stream1 = print 1 |: print 2 |: Stream.nil
-- >>> stream2 = print 3 |: print 4 |: Stream.nil
-- >>> Stream.drain $ stream1 <> stream2
-- 1
-- 2
-- 3
-- 4
--
-- All actions in both the streams are performed serially in the same thread.
--
-- The polymorphic version of the binary operation '<>' of the 'Serial' type is
-- 'serial'. We can use 'serial' to join streams in a sequential manner
-- irrespective of the type of stream:
--
-- >>> Stream.drain $ stream1 `Stream.serial` stream2
-- 1
-- 2
-- 3
-- 4
--

-- $interleaved
--
-- The 'Semigroup' operation '<>' of the 'WSerial' type combines the two
-- streams in a /serial breadth first/ manner. We use the fromWSerial type
-- combinator to effect 'WSerial' style of composition. We can also use the
-- 'WSerial' type annotation for the stream to achieve the same effect.
--
-- When two streams with multiple elements are combined in this manner, we
-- traverse all the streams in a breadth first manner i.e. one action from each
-- stream is performed and yielded to the resulting stream before we come back
-- to the first stream again and so on.
-- The following example prints the sequence 1, 3, 2, 4
--
-- >>> stream1 = print 1 |: print 2 |: Stream.nil
-- >>> stream2 = print 3 |: print 4 |: Stream.nil
-- >>> Stream.drain $ Stream.fromWSerial $ stream1 <> stream2
-- 1
-- 3
-- 2
-- 4
--
-- Even though the monadic actions of the two streams are performed in an
-- interleaved manner they are all performed serially in the same thread.
--
-- The polymorphic version of the 'WSerial' binary operation '<>' is called
-- 'wSerial'. We can use 'wSerial' to join streams in an interleaved manner
-- irrespective of the type, notice that we have not used the fromWSerial
-- combinator in the following example:
--
-- >>> Stream.drain $ stream1 `Stream.wSerial` stream2
-- 1
-- 3
-- 2
-- 4
--
-- Note that this composition cannot be used to fold infinite number of streams
-- since it requires preserving the state until a stream is finished.

-- $monoid
--
-- We can use 'Monoid' instances to fold a container of streams in the desired
-- style using 'fold' or 'foldMap'.  We have also provided some fold utilities
-- to fold streams using the polymorphic combine operations:
--
-- * 'concatFoldableWith' is like 'fold', it folds a 'Foldable' container of
-- streams using the given composition operator.
-- * 'concatMapFoldableWith' is like 'foldMap', it folds like
-- @concatFoldableWith@ but also maps a function before folding.
-- * 'concatForFoldableWith' is like @concatMapFoldableWith@ but the container
-- argument comes before the function argument.
--
-- All of the following are equivalent:
--
-- >>> :{
-- traced = Stream.fromEffect . print
-- main = do
--  Stream.drain $ foldMap traced [1..10]
--  Stream.drain $ Stream.concatFoldableWith Stream.serial (map traced [1..10])
--  Stream.drain $ Stream.concatMapFoldableWith Stream.serial traced [1..10]
--  Stream.drain $ Stream.concatForFoldableWith Stream.serial [1..10] traced
-- :}
--

-- $nesting
--
-- Till now we discussed ways to apply transformations on a stream or to merge
-- streams together to create another stream. We mentioned earlier that
-- transforming a stream is similar to a @for@ loop in the imperative paradigm.
-- We will now discuss the concept of a nested composition of streams which is
-- analogous to nested @for@ loops in the imperative paradigm. Functional
-- programmers call this style of composition a list transformer or @ListT@.
-- Logic programmers call it a logic monad or non-deterministic composition,
-- but for ordinary imperative minded people like me it is easier to think in
-- terms of good old nested @for@ loops.
--
-- $monad
--
-- In functional programmer's parlance the 'Monad' instances of different
-- 'IsStream' types implement non-determinism, exploring all possible
-- combination of choices from both the streams. From an imperative
-- programmer's point of view it behaves like nested loops i.e.  for each
-- element in the first stream and for each element in the second stream
-- execute the body of the loop.
--
-- The 'Monad' instances of 'Serial', 'WSerial', 'Async' and 'WAsync'
-- stream types support different flavors of nested looping.  In other words,
-- they are all variants of list transformer.  The nesting behavior of these
-- types correspond exactly to the way they merge streams as we discussed in
-- the previous section.
--

-- $regularSerial
--
-- The 'Monad' composition of the 'CrossStream' type behaves like a standard list
-- transformer.
--
-- Let's start with an example with a simple @for@ loop without any nesting.
-- For simplicity of illustration we are using streams of pure values in all
-- the examples.  However, the streams could also be made of monadic actions
-- instead.
--
-- >>> :{
-- Stream.drain $ unCrossStream $ do
--     x <- CrossStream (Stream.fromFoldable [3,2,1])
--     CrossStream (Stream.fromEffect $ print x)
-- :}
-- 3
-- 2
-- 1
--
-- As we can see, the code after the @fromFoldable@ statement is run three
-- times, once for each value of @x@ drawn from the stream. All the three
-- iterations are serial and run in the same thread one after another. In
-- imperative terms this is equivalent to a @for@ loop with three iterations.
--
-- We can write the console echo program that we wrote earlier using the monad
-- instance:
--
-- >>> :{
-- main =
--     Stream.drain $ unCrossStream $ do
--         x <- CrossStream (Stream.repeatM getLine)
--         CrossStream (Stream.fromEffect $ putStrLn x)
-- :}
--
-- When multiple streams are composed using this style they nest in a DFS
-- manner:
--
-- >>> :{
-- Stream.drain $ unCrossStream $ do
--   x <- CrossStream (Stream.fromFoldable [1,2])
--   y <- CrossStream (Stream.fromFoldable [3,4])
--   CrossStream (Stream.fromEffect $ print (x, y))
-- :}
-- (1,3)
-- (1,4)
-- (2,3)
-- (2,4)
--
-- i.e. inner loop iterations ((1,3), (1,4)) are executed before we proceed to
-- the next iteration of the outer loop ((2,3), (2,4)). This behaves just like
-- nested @for@ loops in imperative programming.
--
-- Notice that this is analogous to merging streams of type 'Serial' or merging
-- streams using 'serial'.

-- $interleavedNesting
--
-- TBD: generate the WSerial type using the TH utils.
--
-- The 'Monad' composition of 'WSerial' type interleaves the iterations of
-- outer and inner loops in a nested loop composition.
--
-- >> :{
-- Stream.drain $ getWSerial $ do
--      x <- WSerialStream (Stream.fromFoldable [1,2])
--      y <- WSerialStream (Stream.fromFoldable [3,4])
--      WSerialStream (Stream.fromEffect $ print (x, y))
-- :}
-- (1,3)
-- (2,3)
-- (1,4)
-- (2,4)
--
-- Note that (2,3) is preferred to (1,4).  This works exactly the same way as
-- the merging of two streams using 'wSerial' works.
--
-- The fromWSerial type combinator can be used to switch to this style of
-- composition. Alternatively, a type annotation can be used to specify the
-- type of the stream as 'WSerial'.
--

-- $monadExercise
--
-- >>> :{
-- composed :: Stream IO ()
-- composed = unCrossStream $ do
--     sz <- CrossStream sizes
--     cl <- CrossStream colors
--     sh <- CrossStream shapes
--     CrossStream (Stream.fromEffect $ print (sz, cl, sh))
--     where
--     sizes  = Stream.fromFoldable [1, 2, 3]
--     colors = Stream.fromFoldable ["red", "green", "blue"]
--     shapes = Stream.fromFoldable ["triangle", "square", "circle"]
-- :}
--

-- $functor
--
-- 'fmap' transforms a stream by mapping a function on all elements of the
-- stream. 'fmap' behaves in the same way for all stream types, it is always
-- serial.
--
-- >>> (Stream.toList $ fmap show $ Stream.fromFoldable [1..10]) >>= print
-- ["1","2","3","4","5","6","7","8","9","10"]
--
-- Also see functions 'mapM' and 'sequence' from "Streamly.Prelude" module
-- which can map actions concurrently depending on the type of the input stream.

-- $applicative
--
-- Applicative is precisely the same as the 'ap' operation of 'Monad'. For
-- zipping applicatives separate types 'ZipStream' and 'ZipAsync' are
-- provided.
--
-- The following is an example of 'CrossStream' applicative, it runs all iterations
-- serially:
--
-- >>> p n = Stream.fromEffect (print n >> pure n)
-- >>> s1 = p 1 <> p 2
-- >>> s2 = p 3 <> p 4
--
-- >>> (Stream.toList $ unCrossStream $ (,) <$> CrossStream s1 <*> CrossStream s2) >>= print
-- 1
-- 3
-- 4
-- 2
-- 3
-- 4
-- [(1,3),(1,4),(2,3),(2,4)]
--
-- Similarly, 'WSerial' applicative runs the iterations in an interleaved
-- order but being serial it too takes a total of 17 seconds:
--
-- >> (Stream.toList $ Stream.WSerialStream $ (,) <$> WSerialStream s1 <*> WSerialStream s2) >>= print
-- 1
-- 3
-- 2
-- 3
-- 4
-- 4
-- [(1,3),(2,3),(1,4),(2,4)]

-- $zipping
--
-- Zipping is a special transformation where the corresponding elements of two
-- streams are combined together using a zip function producing a new stream of
-- outputs. Two different types are provided for serial and concurrent zipping.
-- These types provide an applicative instance that can be used to lift
-- functions to zip the argument streams.
-- Also see the zipping functions in the "Streamly.Prelude" module.

-- $serialzip
--
-- The applicative instance of 'ZipSerial' type zips streams serially.
-- 'fromZipSerial' type combinator can be used to switch to serial applicative
-- zip composition:
--
-- >>> p n = Stream.fromEffect (print n >> pure n)
-- >>> s1 = Stream.fromSerial $ p 1 <> p 2
-- >>> s2 = Stream.fromSerial $ p 3 <> p 4
-- >>> (Stream.toList $ Stream.fromZipSerial $ (,) <$> s1 <*> s2) >>= print
-- 1
-- 3
-- 2
-- 4
-- [(1,3),(2,4)]
--

-- $furtherReading
--
-- * Read the concurrent streams tutorial
-- * See the examples in <https://github.com/composewell/streamly-examples streamly-examples> repo.