1{-# OPTIONS_GHC -fno-warn-unused-imports #-}
4-- Copyright : (c) 2017 Composewell Technologies
7-- Maintainer : streamly@composewell.com
9-- In this tutorial we will go over the basic concepts and how to use the
10-- Streamly library. Before you go through this tutorial we recommend that you
13-- * The quick overview of streamly in the <README.md README file>.
14-- * The overview of streams in "Streamly.Prelude" module.
21 -- * Concurrent Streams
24 -- * Combining Streams
27 -- * Imports and Supporting Code
30 -- * Generating Streams
33 -- * Generating Streams Concurrently
34 -- $generatingConcurrently
36 -- * Eliminating Streams
39 -- * Concurrent Pipeline Stages
40 -- $concurrentApplication
42 -- * Transforming Streams
45 -- * Mapping Concurrently
46 -- $concurrentTransformation
53 -- *** Deep Serial Composition ('Serial')
56 -- *** Wide Serial Composition ('WSerial')
59 -- *** Deep Speculative Composition ('Ahead')
62 -- *** Deep Asynchronous Composition ('Async')
65 -- *** Wide Asynchronous Composition ('WAsync')
68 -- *** Parallel Asynchronous Composition ('Parallel')
71 -- XXX we should deprecate and remove the mkAsync API
84 -- *** Deep Serial Nesting ('Serial')
87 -- *** Wide Serial Nesting ('WSerial')
88 -- $interleavedNesting
90 -- *** Deep Speculative Nesting ('Ahead')
93 -- *** Deep Asynchronous Nesting ('Async')
96 -- *** Wide Asynchronous Nesting ('WAsync')
99 -- *** Parallel Asynchronous Nesting ('Parallel')
117 -- ** Parallel Zipping
120 -- * Monad transformers
121 -- $monadtransformers
123 -- * Concurrent Programming
126 -- * Reactive Programming
129 -- * Writing Concurrent Programs
135 -- * Interoperation with Streaming Libraries
138 -- * Comparison with Existing Packages
141 -- * Where to go next?
146import Streamly.Prelude
148import Control.Applicative
150import Control.Monad.IO.Class (MonadIO(..))
151import Control.Monad.Trans.Class (MonadTrans (lift))
153-- CAUTION: please keep setup and imports sections in sync
157-- >>> import Data.Function ((&))
158-- >>> import Streamly.Prelude ((|:), (|&))
159-- >>> import qualified Streamly.Prelude as Stream
160-- >>> import qualified Streamly.Data.Fold as Fold
162-- >>> import Control.Concurrent (threadDelay, myThreadId)
164-- delay n = Stream.fromEffect $ do
165-- threadDelay (n * 1000000)
167-- putStrLn (show tid ++ ": Delay " ++ show n)
170-- >>> import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
171-- >>> hSetBuffering stdout LineBuffering
176-- In most of example snippets we do not repeat the imports. Where imports are
177-- not explicitly specified use the imports shown below.
180-- >>> import Data.Function ((&))
181-- >>> import Streamly.Prelude ((|:), (|&))
182-- >>> import qualified Streamly.Prelude as Stream
183-- >>> import qualified Streamly.Data.Fold as Fold
185-- To illustrate concurrent vs serial composition aspects, we will use the
186-- following @delay@ function to introduce a sleep or delay specified in
187-- seconds. After the delay it prints the number of seconds it slept.
189-- >>> import Control.Concurrent (threadDelay, myThreadId)
191-- delay n = Stream.fromEffect $ do
192-- threadDelay (n * 1000000)
194-- putStrLn (show tid ++ ": Delay " ++ show n)
197-- For concurrent examples, use line buffering, otherwise output from different
198-- threads may get mixed:
200-- >>> import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
201-- >>> hSetBuffering stdout LineBuffering
206-- The monadic stream API offered by Streamly is very close to the Haskell
207-- "Prelude" pure lists' API, it can be considered as a natural extension of
208-- lists to monadic actions. Streamly streams provide concurrent composition
209-- and merging of streams. It can be considered as a concurrent list
212-- The basic stream type is 'Serial', it represents a sequence of IO actions,
213-- and is a 'Monad'. The 'Serial' monad is almost a drop in replacement for
214-- the 'IO' monad, IO monad is a special case of the 'Serial' monad; IO monad
215-- represents a single IO action whereas the 'Serial' monad represents a series
216-- of IO actions. The only change you need to make to go from 'IO' to 'Serial'
217-- is to use 'drain' to run the monad and to prefix the IO actions with
218-- either 'fromEffect' or 'liftIO'. If you use liftIO you can switch from 'Serial'
219-- to IO monad by simply removing the 'drain' function; no other changes
220-- are needed unless you have used some stream specific composition or
223-- Similarly, the 'Serial' type is almost a drop in replacement for pure lists,
224-- pure lists are a special case of monadic streams. If you use 'nil' in place
225-- of '[]' and '|:' in place ':' you can replace a list with a 'Serial' stream.
226-- The only difference is that the elements must be monadic type and to operate
227-- on the streams we must use the corresponding functions from
228-- "Streamly.Prelude" instead of using the base "Prelude".
232-- Many stream operations can be done concurrently:
234-- * Streams can be generated concurrently.
236-- * Streams can be merged concurrently.
238-- * Multiple stages in a streaming pipeline can run concurrently.
240-- * Streams can be mapped and zipped concurrently.
242-- * In monadic composition they combine like a list transformer,
243-- providing concurrent non-determinism.
245-- There are three basic concurrent stream styles, 'Ahead', 'Async', and
246-- 'Parallel'. The 'Ahead' style streams are similar to 'Serial' except that
247-- they can speculatively execute multiple stream actions concurrently in
248-- advance. 'Ahead' would return exactly the same stream as 'Serial' except
249-- that it may execute the actions concurrently. The 'Async' style streams,
250-- like 'Ahead', speculatively execute multiple stream actions in advance but
251-- return the results in their finishing order rather than in the stream
252-- traversal order. 'Parallel' is like 'Async' except that it provides
253-- unbounded parallelism instead of controlled parallelism.
255-- For easy reference, we can classify the stream types based on /execution order/,
256-- /consumption order/, and /bounded or unbounded/ concurrency.
257-- Execution could be serial (i.e. synchronous) or asynchronous. In serial
258-- execution we execute the next action in the stream only after the previous
259-- one has finished executing. In asynchronous execution multiple actions in
260-- the stream can be executed asynchronously i.e. the next action can start
261-- executing even before the first one has finished. Consumption order
262-- determines the order in which the outputs generated by the composition are
263-- consumed. Consumption could be serial or asynchronous. In serial
264-- consumption, the outputs are consumed in the traversal order, in
265-- asynchronous consumption the outputs are consumed as they arrive i.e. first
266-- come first serve order.
268-- +------------+--------------+--------------+--------------+
269-- | Type | Execution | Consumption | Concurrency |
270-- +============+==============+==============+==============+
271-- | 'Serial' | Serial | Serial | None |
272-- +------------+--------------+--------------+--------------+
273-- | 'Ahead' | Asynchronous | Serial | bounded |
274-- +------------+--------------+--------------+--------------+
275-- | 'Async' | Asynchronous | Asynchronous | bounded |
276-- +------------+--------------+--------------+--------------+
277-- | 'Parallel' | Asynchronous | Asynchronous | unbounded |
278-- +------------+--------------+--------------+--------------+
280-- All these types can be freely inter-converted using type conversion
281-- combinators or type annotations, without any cost, to achieve the desired
282-- composition style. To force a particular type of composition, we coerce the
283-- stream type using the corresponding type adapting combinator from
284-- 'fromSerial', 'fromAhead', 'fromAsync', or 'fromParallel'. The default stream type
285-- is inferred as 'Serial' unless you change it by using one of the combinators
286-- or by using a type annotation.
290-- Streams can be combined using '<>' or 'mappend' to form a
291-- composite. Composite streams can be interpreted in a depth first or
292-- breadth first manner using an appropriate type conversion before
293-- consumption. Deep (e.g. 'Serial') stream type variants traverse a
294-- composite stream in a depth first manner, such that each stream is
295-- traversed fully before traversing the next stream. Wide
296-- (e.g. 'WSerial') stream types traverse it in a breadth first
297-- manner, such that one element from each stream is traversed before
298-- coming back to the first stream again.
300-- Each stream type has a wide traversal variant prefixed by 'W'. The wide
301-- variant differs only in the Semigroup\/Monoid, Applicative\/Monad
302-- compositions of the streams.
303-- The following table summarizes the basic types and the corresponding wide
307-- +------------+-----------+
309-- +============+===========+
310-- | 'Serial' | 'WSerial' |
311-- +------------+-----------+
312-- | 'Ahead' | 'WAhead' |
313-- +------------+-----------+
314-- | 'Async' | 'WAsync' |
315-- +------------+-----------+
318-- Other than these types there are also 'ZipSerial' and 'ZipAsync' types that
319-- zip streams serially or concurrently using 'Applicative' operation. These
320-- types are not monads they are only applicatives and they do not differ in
321-- 'Semigroup' composition.
326-- When writing concurrent programs it is advised to not use the concurrent
327-- style stream combinators blindly at the top level. That might create too
328-- much concurrency where it is not even required, and can even degrade
329-- performance in some cases. In some cases it can also lead to surprising
330-- behavior because of some code that is supposed to be serial becoming
331-- concurrent. Please be aware that all concurrency capable APIs that you may
332-- have used under the scope of a concurrent stream combinator will become
333-- concurrent. For example if you have a 'repeatM' somewhere in your program
334-- and you use 'fromParallel' on top, the 'repeatM' becomes fully parallel,
335-- resulting into an infinite parallel execution . Instead, use the
336-- /Keep It Serial and Stupid/ principle, start with the default serial
337-- composition and enable concurrent combinators only when and where necessary.
338-- When you use a concurrent combinator you can use an explicit 'fromSerial'
339-- combinator to suppress any unnecessary concurrency under the scope of that
344-- To represent streams in an arbitrary monad use the more general monad
345-- transformer types for example the monad transformer type corresponding to
346-- the 'Serial' type is 'SerialT'. @SerialT m a@ represents a stream of values
347-- of type 'a' in some underlying monad 'm'. For example, @SerialT IO Int@ is a
348-- stream of 'Int' in 'IO' monad. In fact, the type 'Serial' is a synonym for
351-- Similarly we have monad transformer types for other stream types as well viz.
352-- 'WSerialT', 'AsyncT', 'WAsyncT' and 'ParallelT'.
354-- To lift a value from an underlying monad in a monad transformer stack into a
355-- singleton stream use 'lift' and to lift from an IO action use 'liftIO'.
357-- >>> import Control.Monad.IO.Class (liftIO)
358-- >>> Stream.drain $ liftIO $ putStrLn "Hello world!"
361-- >>> import Control.Monad.Trans.Class (MonadTrans(lift))
362-- >>> Stream.drain $ lift $ putStrLn "Hello world!"
368-- 'nil' represents an empty stream and 'consM' or its operator form '|:' adds
369-- a monadic action at the head of the stream.
371-- >>> Stream.toList Stream.nil
374-- Stream.toList $ getLine |: getLine |: Stream.nil
379-- To create a singleton stream from a pure value use 'fromPure' or 'pure' and to
380-- create a singleton stream from a monadic action use 'fromEffect'. Note that in
381-- case of Zip applicative streams "pure" repeats the value to generate an
384-- >>> Stream.toList $ pure 1
387-- >>> Stream.toList $ Stream.fromPure 1
390-- Stream.toList $ Stream.fromEffect getLine
394-- To create a stream from pure values in a 'Foldable' container use
395-- 'fromFoldable' which is equivalent to a fold using 'cons' and 'nil':
397-- >>> Stream.toList $ Stream.fromFoldable [1..3]
400-- >>> Stream.toList $ Prelude.foldr Stream.cons Stream.nil [1..3]
403-- To create a stream from monadic actions in a 'Foldable' container just use a
404-- right fold using 'consM' and 'nil':
406-- >>> Stream.drain $ Prelude.foldr (|:) Stream.nil [putStr "Hello ", putStrLn "world!"]
409-- For more ways to construct a stream see the module "Streamly.Prelude".
411-- $generatingConcurrently
413-- Monadic construction and generation functions like 'consM', 'unfoldrM',
414-- 'replicateM', 'repeatM', 'iterateM' and 'fromFoldableM' work concurrently
415-- when used with appropriate stream type combinator. The pure versions of
416-- these APIs are not concurrent, however you can use the monadic versions even
417-- for pure computations by wrapping the pure value in a monad to get the
418-- concurrent generation capability where required.
420-- The following code finishes in 3 seconds (6 seconds when serial):
422-- >>> let p n = threadDelay (n * 1000000) >> return n
423-- >>> Stream.toList $ Stream.fromParallel $ p 3 |: p 2 |: p 1 |: Stream.nil
426-- >>> Stream.toList $ Stream.fromAhead $ p 3 |: p 2 |: p 1 |: Stream.nil
429-- The following finishes in 10 seconds (100 seconds when serial):
431-- >>> Stream.drain $ Stream.fromAsync $ Stream.replicateM 10 $ p 10
436-- We have already seen 'drain' and toList to eliminate a stream in the
437-- examples above. 'drain' runs a stream discarding the results i.e. only
438-- for effects. 'toList' runs the stream and collects the results in a list.
440-- For other ways to eliminate a stream see the @Folding@ section in
441-- "Streamly.Prelude" module.
445-- Transformation over a stream is the equivalent of a @for@ loop construct in
446-- imperative paradigm. We iterate over every element in the stream and perform
447-- certain transformations for each element. Transformations may involve
448-- mapping functions over the elements, filtering elements from the stream or
449-- folding all the elements in the stream into a single value. Streamly streams
450-- are exactly like lists and you can perform all the transformations in the
451-- same way as you would on lists.
453-- Here is a simple console echo program that just echoes every input line,
458-- Stream.repeatM getLine
459-- & Stream.mapM putStrLn
463-- The following code snippet reads lines from standard input, filters blank
464-- lines, drops the first non-blank line, takes the next two, up cases them,
465-- numbers them and prints them:
467-- >>> import Data.Char (toUpper)
470-- Stream.repeatM getLine
471-- & Stream.filter (not . null)
474-- & fmap (map toUpper)
475-- & Stream.zipWith (\n s -> show n ++ " " ++ s) (Stream.fromFoldable [1..])
476-- & Stream.mapM putStrLn
481-- $concurrentTransformation
483-- Monadic transformation functions 'mapM' and 'sequence' work concurrently
484-- when used with appropriate stream type combinators. The pure versions do not
485-- work concurrently, however you can use the monadic versions even for pure
486-- computations to get the concurrent transformation capability where required.
488-- This would print a value every second (2 seconds when serial):
490-- >>> let p n = threadDelay (n * 1000000) >> return n
493-- Stream.repeatM (p 1)
494-- & Stream.fromSerial -- repeatM is serial
495-- & Stream.mapM (\x -> p 1 >> print x)
496-- & Stream.fromAhead -- mapM is cocnurrent using Ahead style
501-- $concurrentApplication
503-- The concurrent function application operators '|$' and '|&' apply a stream
504-- argument to a stream function concurrently to compose a concurrent pipeline
505-- of stream processing functions:
507-- Because both the stages run concurrently, we would see a delay of only 1
508-- second instead of 2 seconds in the following:
510-- >>> let p n = threadDelay (n * 1000000) >> return n
513-- Stream.repeatM (p 1)
514-- |& Stream.mapM (\x -> p 1 >> print x)
520-- We can combine two streams into a single stream using semigroup composition
521-- operation '<>'. Streams can be combined in many different ways as described
522-- in the following sections, the '<>' operation behaves differently depending
523-- on the stream type in effect. The stream type and therefore the composition
524-- style can be changed at any point using one of the type combinators as
529-- The 'Semigroup' operation '<>' of the 'Serial' type combines the two streams
530-- in a /serial depth first/ manner. We use the 'fromSerial' type combinator to
531-- effect 'Serial' style of composition. We can also use an explicit 'Serial'
532-- type annotation for the stream to achieve the same effect. However, since
533-- 'Serial' is the default type unless explicitly specified by using a
534-- combinator, we can omit using an explicit combinator or type annotation for
535-- this style of composition.
537-- When two streams with multiple elements are combined in this manner, the
538-- monadic actions in the two streams are performed sequentially i.e. first all
539-- actions in the first stream are performed sequentially and then all actions
540-- in the second stream are performed sequentially. We call it
541-- /serial depth first/ as the full depth of one stream is fully traversed
542-- before we move to the next. The following example prints the sequence 1, 2,
545-- >>> stream1 = print 1 |: print 2 |: Stream.nil
546-- >>> stream2 = print 3 |: print 4 |: Stream.nil
547-- >>> Stream.drain $ stream1 <> stream2
553-- All actions in both the streams are performed serially in the same thread.
554-- In the following example we can see that all actions are performed in the
555-- same thread and take a combined total of @3 + 2 + 1 = 6@ seconds:
557-- >>> Stream.drain $ delay 3 <> delay 2 <> delay 1
558-- ThreadId ... Delay 3
559-- ThreadId ... Delay 2
560-- ThreadId ... Delay 1
562-- The polymorphic version of the binary operation '<>' of the 'Serial' type is
563-- 'serial'. We can use 'serial' to join streams in a sequential manner
564-- irrespective of the type of stream:
566-- >>> Stream.drain $ stream1 `Stream.serial` stream2
575-- The 'Semigroup' operation '<>' of the 'WSerial' type combines the two
576-- streams in a /serial breadth first/ manner. We use the fromWSerial type
577-- combinator to effect 'WSerial' style of composition. We can also use the
578-- 'WSerial' type annotation for the stream to achieve the same effect.
580-- When two streams with multiple elements are combined in this manner, we
581-- traverse all the streams in a breadth first manner i.e. one action from each
582-- stream is performed and yielded to the resulting stream before we come back
583-- to the first stream again and so on.
584-- The following example prints the sequence 1, 3, 2, 4
586-- >>> stream1 = print 1 |: print 2 |: Stream.nil
587-- >>> stream2 = print 3 |: print 4 |: Stream.nil
588-- >>> Stream.drain $ Stream.fromWSerial $ stream1 <> stream2
594-- Even though the monadic actions of the two streams are performed in an
595-- interleaved manner they are all performed serially in the same thread. In
596-- the following example we can see that all actions are performed in the same
597-- thread and take a combined total of @3 + 2 + 1 = 6@ seconds:
599-- >>> Stream.drain $ Stream.fromWSerial $ delay 3 <> delay 2 <> delay 1
600-- ThreadId ...: Delay 3
601-- ThreadId ...: Delay 2
602-- ThreadId ...: Delay 1
604-- The polymorphic version of the 'WSerial' binary operation '<>' is called
605-- 'wSerial'. We can use 'wSerial' to join streams in an interleaved manner
606-- irrespective of the type, notice that we have not used the fromWSerial
607-- combinator in the following example:
609-- >>> Stream.drain $ stream1 `Stream.wSerial` stream2
615-- Note that this composition cannot be used to fold infinite number of streams
616-- since it requires preserving the state until a stream is finished.
620-- The 'Semigroup' operation '<>' of the 'Ahead' type combines two streams in a
621-- /serial depth first/ manner with concurrent lookahead. We use the 'fromAhead'
622-- type combinator to effect 'Ahead' style of composition. We can also use an
623-- explicit 'Ahead' type annotation for the stream to achieve the same effect.
625-- When two streams are combined in this manner, the streams are traversed in
626-- depth first manner just like 'Serial', however it can execute the next
627-- stream concurrently and keep the results ready when its turn arrives.
628-- Concurrent execution of the next stream(s) is performed if the first stream
629-- blocks or if it cannot produce output at the rate that is enough to meet the
630-- consumer demand. Multiple streams can be executed concurrently to meet the
631-- demand. The following example would print the result in a second even
632-- though each action in each stream takes one second:
634-- >>> p n = threadDelay 1000000 >> return n
635-- >>> stream1 = p 1 |: p 2 |: Stream.nil
636-- >>> stream2 = p 3 |: p 4 |: Stream.nil
637-- >>> Stream.toList $ Stream.fromAhead $ stream1 <> stream2
640-- Each stream is constructed 'fromAhead' and then both the streams are merged
641-- 'fromAhead', therefore, all the actions can run concurrently but the result is
642-- presented in serial order.
644-- You can also use the polymorphic combinator 'ahead' in place of '<>' to
645-- compose any type of streams in this manner.
649-- The 'Semigroup' operation '<>' of the 'Async' type combines the two
650-- streams in a depth first manner with parallel look ahead. We use the
651-- 'fromAsync' type combinator to effect 'Async' style of composition. We
652-- can also use the 'Async' type annotation for the stream type to achieve
655-- When two streams with multiple elements are combined in this manner, the
656-- streams are traversed in depth first manner just like 'Serial', however it
657-- can execute the next stream concurrently and return the results from it
658-- as they arrive i.e. the results from the next stream may be yielded even
659-- before the results from the first stream. Concurrent execution of the next
660-- stream(s) is performed if the first stream blocks or if it cannot produce
661-- output at the rate that is enough to meet the consumer demand. Multiple
662-- streams can be executed concurrently to meet the demand.
663-- In the example below each element in the stream introduces a constant delay
664-- of 1 second, however, it takes just one second to produce all the results.
665-- The results are not guaranteed to be in any particular order:
667-- >>> p n = threadDelay 1000000 >> return n
668-- >>> stream1 = p 1 |: p 2 |: Stream.nil
669-- >>> stream2 = p 3 |: p 4 |: Stream.nil
670-- >>> Stream.toList $ Stream.fromAsync $ stream1 <> stream2
673-- The constituent streams are also composed in 'Async' manner and the
674-- composition of streams too. We can compose the constituent streams to run
675-- serially, in that case it would take 2 seconds to produce all the results.
676-- The elements in the serial streams would be in serial order in the results:
678-- >>> p n = threadDelay 1000000 >> return n
679-- >>> stream = (Stream.fromSerial stream1) <> (Stream.fromSerial stream2)
680-- >>> Stream.toList $ Stream.fromAsync stream
683-- In the following example we can see that new threads are started when a
684-- computation blocks. Notice that the output from the stream with the
685-- shortest delay is printed first. The whole computation takes @maximum of
686-- (3, 2, 1) = 3@ seconds:
688-- >>> Stream.drain $ Stream.fromAsync $ delay 3 <> delay 2 <> delay 1
689-- ThreadId ...: Delay 1
690-- ThreadId ...: Delay 2
691-- ThreadId ...: Delay 3
693-- When we have a tree of computations composed using this style, the tree is
694-- traversed in DFS style just like the 'Serial' style, the only difference is
695-- that here we can move on to executing the next stream if a stream blocks.
696-- However, we will not start new threads if we have sufficient output to
697-- saturate the consumer. This is why we call it left-biased demand driven or
698-- adaptive concurrency style, the concurrency tends to stay on the left side
699-- of the composition as long as possible. More threads are started based on
700-- the pull rate of the consumer. The following example prints an output every
701-- second as all of the actions are concurrent.
703-- >>> Stream.drain $ Stream.fromAsync $ (delay 1 <> delay 2) <> (delay 3 <> delay 4)
704-- ThreadId ...: Delay 1
705-- ThreadId ...: Delay 2
706-- ThreadId ...: Delay 3
707-- ThreadId ...: Delay 4
709-- All the computations may even run in a single thread when more threads are
710-- not needed. As you can see, in the following example the computations are
711-- run in a single thread one after another, because none of them blocks.
712-- However, if the thread consuming the stream were faster than the producer
713-- then it would have started parallel threads for each computation to keep up
714-- even if none of them blocks:
717-- traced m = Stream.fromEffect (myThreadId >>= print) >> return m
718-- stream = traced (sqrt 9) <> traced (sqrt 16) <> traced (sqrt 25)
719-- main = Stream.drain $ Stream.fromAsync stream
722-- Note that the order of printing in the above examples may change due to
723-- variations in scheduling latencies for concurrent threads.
725-- The polymorphic version of the 'Async' binary operation '<>' is called
726-- 'async'. We can use 'async' to join streams in a left biased
727-- adaptively concurrent manner irrespective of the type, notice that we have
728-- not used the 'fromAsync' combinator in the following example:
730-- >>> Stream.drain $ delay 3 `Stream.async` delay 2 `Stream.async` delay 1
731-- ThreadId ...: Delay 1
732-- ThreadId ...: Delay 2
733-- ThreadId ...: Delay 3
735-- Since the concurrency provided by this operator is demand driven it cannot
736-- be used when the composed computations start timers that are relative to
737-- each other because all computations may not be started at the same time and
738-- therefore timers in all of them may not start at the same time. When
739-- relative timing among all computations is important or when we need to start
740-- all computations at once for any reason 'Parallel' style must be used
743-- 'Async' style utilizes resources optimally and should be preferred over
744-- 'Parallel' or 'WAsync' unless you really need those. 'Async' should be used
745-- when we know that the computations can run in parallel but we do not care if
746-- they actually run in parallel or not, that decision can be left to the
747-- scheduler based on demand. Also, note that 'async' operator can be used to fold
748-- infinite number of streams in contrast to the 'Parallel' or 'WAsync' styles,
749-- because it does not require us to run all of them at the same time in a fair
754-- The 'Semigroup' operation '<>' of the 'WAsync' type combines two streams in
755-- a concurrent manner using /breadth first traversal/. We use the 'fromWAsync'
756-- type combinator to effect 'WAsync' style of composition. We can also use the
757-- 'WAsync' type annotation for the stream to achieve the same effect.
759-- When streams with multiple elements are combined in this manner, we traverse
760-- all the streams concurrently in a breadth first manner i.e. one action from
761-- each stream is performed and yielded to the resulting stream before we come
762-- back to the first stream again and so on. Even though we execute the actions
763-- in a breadth first order the outputs are consumed on a first come first
766-- In the following example we can see that outputs are produced in the breadth
767-- first traversal order but this is not guaranteed.
769-- >>> stream1 = print 1 |: print 2 |: Stream.nil
770-- >>> stream2 = print 3 |: print 4 |: Stream.nil
771-- >>> Stream.drain $ Stream.fromWAsync $ stream1 <> stream2
777-- The polymorphic version of the binary operation '<>' of the 'WAsync' type is
778-- 'wAsync'. We can use 'wAsync' to join streams using a breadth first
779-- concurrent traversal irrespective of the type, notice that we have not used
780-- the 'fromWAsync' combinator in the following example:
782-- >>> Stream.drain $ delay 3 `Stream.wAsync` delay 2 `Stream.wAsync` delay 1
783-- ThreadId ...: Delay 1
784-- ThreadId ...: Delay 2
785-- ThreadId ...: Delay 3
787-- Since the concurrency provided by this style is demand driven it may not
788-- be used when the composed computations start timers that are relative to
789-- each other because all computations may not be started at the same time and
790-- therefore timers in all of them may not start at the same time. When
791-- relative timing among all computations is important or when we need to start
792-- all computations at once for any reason 'Parallel' style must be used
798-- The 'Semigroup' operation '<>' of the 'Parallel' type combines the two
799-- streams in a fairly concurrent manner with round robin scheduling. We use
800-- the 'fromParallel' type combinator to effect 'Parallel' style of composition.
801-- We can also use the 'Parallel' type annotation for the stream type to
802-- achieve the same effect.
804-- When two streams with multiple elements are combined in this manner, the
805-- monadic actions in both the streams are performed concurrently with a fair
806-- round robin scheduling. The outputs are yielded in the order in which the
807-- actions complete. This is pretty similar to the 'WAsync' type, the
808-- difference is that 'WAsync' is adaptive to the consumer demand and may or
809-- may not execute all actions in parallel depending on the demand, whereas
810-- 'Parallel' runs all the streams in parallel irrespective of the demand.
812-- The polymorphic version of the binary operation '<>' of the 'Parallel' type
813-- is 'parallel'. We can use 'parallel' to join streams in a fairly concurrent
814-- manner irrespective of the type, notice that we have not used the
815-- 'fromParallel' combinator in the following example:
817-- >>> Stream.drain $ delay 3 `Stream.parallel` delay 2 `Stream.wAsync` delay 1
818-- ThreadId ...: Delay 1
819-- ThreadId ...: Delay 2
820-- ThreadId ...: Delay 3
822-- Note that this style of composition cannot be used to combine infinite
823-- number of streams, as it will lead to an infinite sized scheduling queue.
829-- The 'mkAsync' API can be used to create references to asynchronously running
830-- stream computations. We can then use 'uncons' to explore the streams
831-- arbitrarily and then recompose individual elements to create a new stream.
832-- This way we can dynamically decide which stream to explore at any given
833-- time. Take an example of a merge sort of two sorted streams. We need to
834-- keep consuming items from the stream which has the lowest item in the sort
835-- order. This can be achieved using async references to streams. See
836-- "MergeSort.hs" in <https://github.com/composewell/streamly-examples Streamly Examples>.
840-- We can use 'Monoid' instances to fold a container of streams in the desired
841-- style using 'fold' or 'foldMap'. We have also provided some fold utilities
842-- to fold streams using the polymorphic combine operations:
844-- * 'concatFoldableWith' is like 'fold', it folds a 'Foldable' container of
845-- streams using the given composition operator.
846-- * 'concatMapFoldableWith' is like 'foldMap', it folds like
847-- @concatFoldableWith@ but also maps a function before folding.
848-- * 'concatForFoldableWith' is like @concatMapFoldableWith@ but the container
849-- argument comes before the function argument.
851-- All of the following are equivalent and start ten concurrent tasks each with
852-- a delay from 1 to 10 seconds, resulting in the printing of each number every
857-- Stream.drain $ Stream.fromAsync $ foldMap delay [1..10]
858-- Stream.drain $ Stream.concatFoldableWith Stream.async (map delay [1..10])
859-- Stream.drain $ Stream.concatMapFoldableWith Stream.async delay [1..10]
860-- Stream.drain $ Stream.concatForFoldableWith Stream.async [1..10] delay
866-- Till now we discussed ways to apply transformations on a stream or to merge
867-- streams together to create another stream. We mentioned earlier that
868-- transforming a stream is similar to a @for@ loop in the imperative paradigm.
869-- We will now discuss the concept of a nested composition of streams which is
870-- analogous to nested @for@ loops in the imperative paradigm. Functional
871-- programmers call this style of composition a list transformer or @ListT@.
872-- Logic programmers call it a logic monad or non-deterministic composition,
873-- but for ordinary imperative minded people like me it is easier to think in
874-- terms of good old nested @for@ loops.
878-- In functional programmer's parlance the 'Monad' instances of different
879-- 'IsStream' types implement non-determinism, exploring all possible
880-- combination of choices from both the streams. From an imperative
881-- programmer's point of view it behaves like nested loops i.e. for each
882-- element in the first stream and for each element in the second stream
883-- execute the body of the loop.
885-- The 'Monad' instances of 'Serial', 'WSerial', 'Async' and 'WAsync'
886-- stream types support different flavors of nested looping. In other words,
887-- they are all variants of list transformer. The nesting behavior of these
888-- types correspond exactly to the way they merge streams as we discussed in
889-- the previous section.
894-- The 'Monad' composition of the 'Serial' type behaves like a standard list
895-- transformer. This is the default when we do not use an explicit type
896-- combinator. However, the 'fromSerial' type combinator can be used to switch to
897-- this style of composition. We will see how this style of composition works
898-- in the following examples.
900-- Let's start with an example with a simple @for@ loop without any nesting.
901-- For simplicity of illustration we are using streams of pure values in all
902-- the examples. However, the streams could also be made of monadic actions
907-- x <- Stream.fromFoldable [3,2,1]
910-- ThreadId ...: Delay 3
911-- ThreadId ...: Delay 2
912-- ThreadId ...: Delay 1
914-- As we can see, the code after the @fromFoldable@ statement is run three
915-- times, once for each value of @x@ drawn from the stream. All the three
916-- iterations are serial and run in the same thread one after another. In
917-- imperative terms this is equivalent to a @for@ loop with three iterations.
919-- We can write the console echo program that we wrote earlier using the monad
925-- x <- Stream.repeatM getLine
926-- Stream.fromEffect $ putStrLn x
929-- When multiple streams are composed using this style they nest in a DFS
934-- x <- Stream.fromFoldable [1,2]
935-- y <- Stream.fromFoldable [3,4]
936-- Stream.fromEffect $ putStrLn $ show (x, y)
943-- i.e. inner loop iterations ((1,3), (1,4)) are executed before we proceed to
944-- the next iteration of the outer loop ((2,3), (2,4)). This behaves just like
945-- nested @for@ loops in imperative programming.
947-- Notice that this is analogous to merging streams of type 'Serial' or merging
948-- streams using 'serial'.
952-- The 'Monad' composition of 'Ahead' type behaves just like 'Serial' except
953-- that it can speculatively perform a bounded number of next iterations of a
957-- Stream.toList $ Stream.fromAhead $ do
958-- x <- Stream.fromFoldable [3,2,1]
962-- ThreadId ...: Delay 1
963-- ThreadId ...: Delay 2
964-- ThreadId ...: Delay 3
967-- This code finishes in 3 seconds, 'Serial' would take 6 seconds. As we can
968-- see all the three iterations are concurrent and run in different threads,
969-- however, the results are returned in the serial order.
971-- Concurrency is demand driven, when multiple streams are composed using this
972-- style, the iterations are executed in a depth first manner just like
973-- 'Serial' i.e. nested iterations are executed before we proceed to the next
974-- outer iteration. The only difference is that we may execute multiple future
975-- iterations concurrently and keep the results ready.
977-- The 'fromAhead' type combinator can be used to switch to this style of
978-- composition. Alternatively, a type annotation can be used to specify the
979-- type of the stream as 'Ahead'.
984-- The 'Monad' composition of 'Async' type can perform the iterations of a
988-- Stream.drain $ Stream.fromAsync $ do
989-- x <- Stream.fromFoldable [3,2,1]
992-- ThreadId ...: Delay 1
993-- ThreadId ...: Delay 2
994-- ThreadId ...: Delay 3
996-- As we can see the code after the @fromFoldable@ statement is run three
997-- times, once for each value of @x@. All the three iterations are concurrent
998-- and run in different threads. The iteration with least delay finishes first.
999-- When compared to imperative programming, this can be viewed as a @for@ loop
1000-- with three concurrent iterations.
1002-- Concurrency is demand driven i.e. more concurrent iterations are started
1003-- only if the previous iterations are not able to saturate the consumer of the
1004-- output stream. This works exactly the same way as the merging of two
1005-- streams using 'async' works.
1007-- The 'fromAsync' type combinator can be used to switch to this style of
1008-- composition. Alternatively, a type annotation can be used to specify the
1009-- type of the stream as 'Async'.
1011-- When multiple streams are nested using this style, the iterations are
1012-- concurrently evaluated in a depth first manner:
1016-- Stream.drain $ Stream.fromAsync $ do
1017-- x <- Stream.fromFoldable [1,2]
1018-- y <- Stream.fromFoldable [3,4]
1019-- Stream.fromEffect $ putStrLn $ show (x, y)
1026-- Nested iterations are given preference for concurrent evaluation i.e.
1027-- (1,4) will be scheduled in preference to (2,3).
1029-- $interleavedNesting
1031-- The 'Monad' composition of 'WSerial' type interleaves the iterations of
1032-- outer and inner loops in a nested loop composition.
1035-- Stream.drain $ Stream.fromWSerial $ do
1036-- x <- Stream.fromFoldable [1,2]
1037-- y <- Stream.fromFoldable [3,4]
1038-- Stream.fromEffect $ putStrLn $ show (x, y)
1045-- Note that (2,3) is preferred to (1,4). This works exactly the same way as
1046-- the merging of two streams using 'wSerial' works.
1048-- The fromWSerial type combinator can be used to switch to this style of
1049-- composition. Alternatively, a type annotation can be used to specify the
1050-- type of the stream as 'WSerial'.
1055-- Like 'Async', the 'Monad' composition of 'WAsync' runs the iterations of a
1056-- loop concurrently. It differs from 'Async' in the nested loop behavior. Like
1057-- 'WSerial', the nested loops in this type are traversed and executed in a
1058-- breadth first manner rather than the depth first manner of 'Async' style.
1061-- Stream.drain $ Stream.fromWAsync $ do
1062-- x <- Stream.fromSerial $ Stream.fromFoldable [1,2]
1063-- y <- Stream.fromSerial $ Stream.fromFoldable [3,4]
1064-- Stream.fromEffect $ putStrLn $ show (x, y)
1071-- Note that (2,3) is preferred to (1,4) when evaluating the iterations
1072-- concurrently. This works exactly the same way as the merging of two streams
1073-- using 'wAsync' works.
1075-- The 'fromWAsync' type combinator can be used to switch to this style of
1076-- composition. Alternatively, a type annotation can be used to specify the
1077-- type of the stream as 'WAsync'.
1082-- Just like 'Async' or 'WAsync' the 'Monad' composition of 'Parallel' runs the
1083-- iterations of a loop concurrently.
1086-- Stream.drain $ Stream.fromParallel $ do
1087-- x <- Stream.fromFoldable [3,2,1]
1090-- ThreadId ...: Delay 1
1091-- ThreadId ...: Delay 2
1092-- ThreadId ...: Delay 3
1094-- It differs from 'Async' and 'WAsync' in the nested loop behavior. All
1095-- iterations of the loop are run fully concurrently irrespective of the
1096-- demand. This works exactly the same way as the merging of streams using
1099-- The 'fromParallel' type combinator can be used to switch to this style of
1100-- composition. Alternatively, a type annotation can be used to specify the
1101-- type of the stream as 'Parallel'.
1106-- Streamly code is usually written in a way that is agnostic of the
1107-- specific monadic composition type. We use a polymorphic type with a
1108-- 'IsStream' type class constraint. When running the stream we can choose the
1109-- specific mode of composition. For example take a look at the following code.
1112-- composed :: (Stream.IsStream t, Monad (t IO)) => t IO ()
1117-- Stream.fromEffect $ putStrLn $ show (sz, cl, sh)
1119-- sizes = Stream.fromFoldable [1, 2, 3]
1120-- colors = Stream.fromFoldable ["red", "green", "blue"]
1121-- shapes = Stream.fromFoldable ["triangle", "square", "circle"]
1124-- Now we can interpret this in whatever way we want:
1127-- main = Stream.'drain' $ Stream.'fromSerial' $ composed
1128-- main = Stream.'drain' $ Stream.'fromWSerial' $ composed
1129-- main = Stream.'drain' $ Stream.'fromAsync' $ composed
1130-- main = Stream.'drain' $ Stream.'fromWAsync' $ composed
1131-- main = Stream.'drain' $ Stream.'fromParallel' $ composed
1134-- As an exercise try to figure out the output of this code for each mode of
1139-- 'fmap' transforms a stream by mapping a function on all elements of the
1140-- stream. 'fmap' behaves in the same way for all stream types, it is always
1143-- >>> (Stream.toList $ fmap show $ Stream.fromFoldable [1..10]) >>= print
1144-- ["1","2","3","4","5","6","7","8","9","10"]
1146-- Also see functions 'mapM' and 'sequence' from "Streamly.Prelude" module
1147-- which can map actions concurrently depending on the type of the input stream.
1151-- Applicative is precisely the same as the 'ap' operation of 'Monad'. For
1152-- zipping applicatives separate types 'ZipSerial' and 'ZipAsync' are
1155-- The following is an example of 'Serial' applicative, it runs all iterations
1156-- serially and takes a total 17 seconds (1 + 3 + 4 + 2 + 3 + 4):
1158-- >>> d n = delay n >> return n
1159-- >>> s1 = d 1 <> d 2
1160-- >>> s2 = d 3 <> d 4
1162-- >>> (Stream.toList $ Stream.fromSerial $ (,) <$> s1 <*> s2) >>= print
1163-- ThreadId ...: Delay 1
1164-- ThreadId ...: Delay 3
1165-- ThreadId ...: Delay 4
1166-- ThreadId ...: Delay 2
1167-- ThreadId ...: Delay 3
1168-- ThreadId ...: Delay 4
1169-- [(1,3),(1,4),(2,3),(2,4)]
1171-- Similarly, 'WSerial' applicative runs the iterations in an interleaved
1172-- order but being serial it too takes a total of 17 seconds:
1174-- >>> (Stream.toList $ Stream.fromWSerial $ (,) <$> s1 <*> s2) >>= print
1175-- ThreadId ...: Delay 1
1176-- ThreadId ...: Delay 3
1177-- ThreadId ...: Delay 2
1178-- ThreadId ...: Delay 3
1179-- ThreadId ...: Delay 4
1180-- ThreadId ...: Delay 4
1181-- [(1,3),(2,3),(1,4),(2,4)]
1183-- 'Async' can run the iterations concurrently, therefore, it takes a total
1184-- of 6 seconds which is max (1, 2) + max (3, 4):
1186-- >>> (Stream.toList $ Stream.fromAsync $ (,) <$> s1 <*> s2) >>= print
1190-- ThreadId 34: Delay 1
1191-- ThreadId 36: Delay 2
1192-- ThreadId 35: Delay 3
1193-- ThreadId 36: Delay 3
1194-- ThreadId 35: Delay 4
1195-- ThreadId 36: Delay 4
1196-- [(1,3),(2,3),(1,4),(2,4)]
1199-- Similarly, 'WAsync' as well can run the iterations concurrently, but with a
1200-- different style of scheduling than 'Async' as explained in the Monad
1201-- section, therefore, it too takes a total of 6 seconds (2 + 4):
1203-- >>> (Stream.toList $ Stream.fromWAsync $ (,) <$> s1 <*> s2) >>= print
1207-- ThreadId 34: Delay 1
1208-- ThreadId 36: Delay 2
1209-- ThreadId 35: Delay 3
1210-- ThreadId 36: Delay 3
1211-- ThreadId 35: Delay 4
1212-- ThreadId 36: Delay 4
1213-- [(1,3),(2,3),(1,4),(2,4)]
1218-- Zipping is a special transformation where the corresponding elements of two
1219-- streams are combined together using a zip function producing a new stream of
1220-- outputs. Two different types are provided for serial and concurrent zipping.
1221-- These types provide an applicative instance that can be used to lift
1222-- functions to zip the argument streams.
1223-- Also see the zipping functions in the "Streamly.Prelude" module.
1227-- The applicative instance of 'ZipSerial' type zips streams serially.
1228-- 'fromZipSerial' type combinator can be used to switch to serial applicative
1231-- This takes total 10 seconds to zip, which is (1 + 2 + 3 + 4) since
1232-- everything runs serially:
1234-- >>> d n = delay n >> return n
1235-- >>> s1 = Stream.fromSerial $ d 1 <> d 2
1236-- >>> s2 = Stream.fromSerial $ d 3 <> d 4
1237-- >>> (Stream.toList $ Stream.fromZipSerial $ (,) <$> s1 <*> s2) >>= print
1238-- ThreadId ...: Delay 1
1239-- ThreadId ...: Delay 3
1240-- ThreadId ...: Delay 2
1241-- ThreadId ...: Delay 4
1247-- The applicative instance of 'ZipAsync' type zips streams concurrently.
1248-- 'fromZipAsync' type combinator can be used to switch to parallel applicative
1251-- This takes 7 seconds to zip, which is max (1,3) + max (2,4) because 1 and 3
1252-- are produced concurrently, and 2 and 4 are produced concurrently:
1254-- >>> d n = delay n >> return n
1255-- >>> s1 = Stream.fromSerial $ d 1 <> d 2
1256-- >>> s2 = Stream.fromSerial $ d 3 <> d 4
1257-- >>> (Stream.toList $ Stream.fromZipAsync $ (,) <$> s1 <*> s2) >>= print
1258-- ThreadId ...: Delay 1
1259-- ThreadId ...: Delay 2
1260-- ThreadId ...: Delay 3
1261-- ThreadId ...: Delay 4
1267-- When writing concurrent programs there are two distinct places where the
1268-- programmer can control the concurrency. First, when /composing/ a stream by
1269-- merging multiple streams we can choose an appropriate sum style operators to
1270-- combine them concurrently or serially. Second, when /processing/ a stream in
1271-- a monadic composition we can choose one of the monad composition types to
1272-- choose the desired type of concurrency.
1274-- In the following example the squares of @x@ and @y@ are computed
1275-- concurrently using the 'async' operation and the square roots of their
1276-- sum are computed serially because of the 'fromSerial' combinator. We can
1277-- choose different combinators for the monadic processing and the stream
1278-- generation, to control the concurrency. We can also use the 'fromAsync'
1279-- combinator instead of explicitly folding with 'async'.
1281-- >>> import Data.List (sum)
1284-- z <- Stream.toList
1285-- $ Stream.fromSerial -- Serial monadic processing (sqrt below)
1287-- x2 <- Stream.concatForFoldableWith Stream.async [1..100] $ -- Concurrent @"for"@ loop
1288-- \x -> return $ x * x -- body of the loop
1289-- y2 <- Stream.concatForFoldableWith Stream.async [1..100] $
1290-- \y -> return $ y * y
1291-- return $ sqrt (x2 + y2)
1295-- We can see how this directly maps to the imperative style
1296-- <https://en.wikipedia.org/wiki/OpenMP OpenMP> model, we use combinators
1297-- and operators instead of the ugly pragmas.
1299-- For more concurrent programming examples see,
1300-- <https://github.com/composewell/streamly-examples>.
1304-- Reactive programming is nothing but concurrent streaming which is what
1305-- streamly is all about. With streamly we can generate streams of events,
1306-- merge streams that are generated concurrently and process events
1307-- concurrently. We can do all this without any knowledge about the specifics
1308-- of the implementation of concurrency. In the following example you will see
1309-- that the code is just regular Haskell code without much streamly APIs used
1310-- (active hyperlinks are the streamly APIs) and yet it is a reactive
1313-- This application has two independent and concurrent sources of event
1314-- streams, @acidRain@ and @userAction@. @acidRain@ continuously generates
1315-- events that deteriorate the health of the character in the game.
1316-- @userAction@ can be "potion" or "quit". When the user types "potion" the
1317-- health improves and the game continues.
1320-- {-\# LANGUAGE FlexibleContexts \#-}
1322-- import "Streamly.Prelude" (MonadAsync, SerialT)
1323-- import "Streamly.Prelude" as Stream
1324-- import Control.Monad (void)
1325-- import Control.Monad.IO.Class (MonadIO(liftIO))
1326-- import Control.Monad.State (MonadState, get, modify, runStateT)
1328-- data Event = Quit | Harm Int | Heal Int deriving (Show)
1330-- userAction :: MonadAsync m => 'SerialT' m Event
1331-- userAction = Stream.'repeatM' $ liftIO askUser
1334-- command <- getLine
1336-- "potion" -> return (Heal 10)
1337-- "harm" -> return (Harm 10)
1338-- "quit" -> return Quit
1339-- _ -> putStrLn "Type potion or harm or quit" >> askUser
1341-- acidRain :: MonadAsync m => 'SerialT' m Event
1342-- acidRain = Stream.'fromAsync' $ Stream.'constRate' 1 $ Stream.'repeatM' $ liftIO $ return $ Harm 1
1344-- data Result = Check | Done
1346-- runEvents :: (MonadAsync m, MonadState Int m) => 'SerialT' m Result
1348-- event \<- userAction \`Stream.'parallel'` acidRain
1350-- Harm n -> modify (\\h -> h - n) >> return Check
1351-- Heal n -> modify (\\h -> h + n) >> return Check
1352-- Quit -> return Done
1354-- data Status = Alive | GameOver deriving Eq
1356-- getStatus :: (MonadAsync m, MonadState Int m) => Result -> m Status
1357-- getStatus result =
1359-- Done -> liftIO $ putStrLn "You quit!" >> return GameOver
1362-- liftIO $ if (h <= 0)
1363-- then putStrLn "You die!" >> return GameOver
1364-- else putStrLn ("Health = " <> show h) >> return Alive
1368-- putStrLn "Your health is deteriorating due to acid rain, type \\\"potion\\\" or \\\"quit\\\""
1369-- let runGame = Stream.'drainWhile' (== Alive) $ Stream.'mapM' getStatus runEvents
1370-- void $ runStateT runGame 60
1373-- You can also find the source of this example in the streamly-examples repo
1374-- as <https://github.com/composewell/streamly-examples/tree/master/AcidRain.hs AcidRain.hs>.
1375-- It has been adapted from Gabriel's
1376-- <https://hackage.haskell.org/package/pipes-concurrency-2.0.8/docs/Pipes-Concurrent-Tutorial.html pipes-concurrency>
1378-- This is much simpler compared to the pipes version because of the builtin
1379-- concurrency in streamly. You can also find a SDL based reactive programming
1380-- example adapted from Yampa in
1381-- <https://github.com/composewell/streamly-examples/tree/master/CirclingSquare.hs CirclingSquare.hs>.
1385-- Streamly is highly optimized for performance, it is designed for serious
1386-- high performing, concurrent and scalable applications. We have created the
1387-- <https://hackage.haskell.org/package/streaming-benchmarks streaming-benchmarks>
1388-- package which is specifically and carefully designed to measure the
1389-- performance of Haskell streaming libraries fairly and squarely in the right
1390-- way. Streamly performs at par or even better than most streaming libraries
1391-- for serial operations even though it needs to deal with the concurrency
1396-- * Read the documentation of "Streamly" module
1397-- * Read the documentation of "Streamly.Prelude" module
1398-- * See the examples in <https://github.com/composewell/streamly-examples streamly-examples> repo.
1399-- * See the tests in the "test" directory of the package
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list