1{-# OPTIONS_GHC -fno-warn-unused-imports #-}
3-- Module : Streamly.Tutorial
4-- Copyright : (c) 2017 Harendra Kumar
7-- Maintainer : harendra.kumar@gmail.com
9-- Streamly, short for stream concurrently, combines the essence of
10-- non-determinism, streaming and concurrency in functional programming.
11-- Concurrent and non-concurrent applications are almost indistinguisable,
12-- concurrency capability does not at all impact the performance of
13-- non-concurrent case.
14-- Streaming enables writing modular, composable and scalable applications with
15-- ease and concurrency allows you to make them scale and perform well.
16-- Streamly enables writing concurrent applications without being aware of
17-- threads or synchronization. No explicit thread control is needed, where
18-- applicable the concurrency rate is automatically controlled based on the
19-- demand by the consumer. However, combinators are provided to fine tune the
20-- concurrency control.
21-- Streaming and concurrency together enable expressing reactive applications
22-- conveniently. See "Streamly.Examples" for a simple SDL based FRP example.
24-- Streamly streams are very much like the Haskell lists and most of the
25-- functions that work on lists have a counterpart that works on streams.
26-- However, streamly streams can be generated, consumed or combined
27-- concurrently. In this tutorial we will go over the basic concepts and how to
28-- use the library. The documentation of @Streamly@ module has more details on
29-- core APIs. For more APIs for constructing, folding, filtering, mapping and
30-- zipping etc. see the documentation of "Streamly.Prelude" module. For
31-- examples and other ways to use the library see the module
32-- "Streamly.Examples" as well.
34module Streamly.Tutorial
39 -- ** Generating Streams
42 -- ** Eliminating Streams
45 -- * Combining Streams
51 -- *** Serial composition ('<>')
54 -- *** Async composition ('<|')
57 -- *** Interleaved composition ('<=>')
60 -- *** Fair Concurrent composition ('<|>')
63 -- *** Custom composition
69 -- * Transforming Streams
75 -- *** Serial Composition ('SerialT')
78 -- *** Async Composition ('AsyncT')
81 -- *** Interleaved Composition ('InterleavedT')
82 -- $interleavedNesting
84 -- *** Fair Concurrent Composition ('ParallelT')
85 -- $fairlyConcurrentNesting
102 -- ** Parallel Zipping
105 -- * Summary of Compositions
106 -- $compositionSummary
108 -- * Concurrent Programming
111 -- * Reactive Programming
117 -- * Interoperation with Streaming Libraries
120 -- * Comparison with Existing Packages
126import Streamly.Prelude
128import Control.Applicative
130import Control.Monad.IO.Class (MonadIO(..))
131import Control.Monad.Trans.Class (MonadTrans (lift))
135-- Streamly provides many different stream types depending on the desired
136-- composition style. The simplest type is 'SerialT'. 'SerialT' is a monad
137-- transformer, the type @SerialT m a@ represents a stream of values of type
138-- 'a' in some underlying monad 'm'. For example, @SerialT IO Int@ is a stream
139-- of 'Int' in 'IO' monad.
143-- Pure values can be placed into the stream type using 'return' or 'pure'.
144-- Effects in the IO monad can be lifted to the stream type using the 'liftIO'
145-- combinator. In a transformer stack we can lift actions from the lower monad
146-- using the 'lift' combinator. Some examples of streams with a single element:
149-- return 1 :: 'SerialT' IO Int
152-- liftIO $ putStrLn "Hello world!" :: 'SerialT' IO ()
155-- We can combine streams using '<>' to create streams of many elements:
158-- return 1 <> return 2 <> return 3 :: 'SerialT' IO Int
161-- For more ways to construct or generate a stream see the module
162-- "Streamly.Prelude".
166-- 'runSerialT' runs a composed 'SerialT' computation, lowering the type into
167-- the underlying monad and discarding the result stream:
172-- main = 'runSerialT' $ liftIO $ putStrLn "Hello world!"
175-- 'toList' runs a stream computation and collects the result stream in a list
176-- in the underlying monad. 'toList' is a polymorphic function that works on
177-- multiple stream types belonging to the class 'IsStream'. Therefore, before
178-- you run a stream you need to tell how you want to interpret the stream by
179-- using one of the stream type combinators ('serially', 'asyncly', 'parallely'
180-- etc.). The combinator 'serially' is equivalent to annotating the type as @::
185-- import "Streamly.Prelude"
188-- xs \<- 'toList' $ 'serially' $ return 1 <> return 2
192-- For other ways to eliminate or fold a stream see the module
193-- "Streamly.Prelude".
196-- Streams of the same type can be combined into a composite stream in many
197-- different ways using one of the semigroup style binary composition operators
198-- i.e. '<>', '<=>', '<|', '<|>', 'mplus'. These operators work on all stream
199-- types ('SerialT', 'AsyncT' etc.) uniformly.
201-- To illustrate the concurrent aspects, we will use the following @delay@
202-- function to introduce a delay specified in seconds.
206-- import Control.Concurrent
208-- delay n = liftIO $ do
209-- threadDelay (n * 1000000)
211-- putStrLn (show tid ++ ": Delay " ++ show n)
216-- We have already seen, the '<>' operator. It composes two streams in series
217-- i.e. the first stream is completely exhausted and then the second stream is
218-- processed. The following example prints the sequence 3, 2, 1 and takes a
219-- total of 6 seconds because everything is serial:
222-- main = 'runSerialT' $ delay 3 <> delay 2 <> delay 1
225-- ThreadId 36: Delay 3
226-- ThreadId 36: Delay 2
227-- ThreadId 36: Delay 1
231-- The '<=>' operator is serial like '<>' but it interleaves the two streams
232-- i.e. it yields one element from the first stream and then one element from
233-- the second stream, and so on. The following example prints the sequence 1,
234-- 3, 2, 4 and takes a total of 10 seconds because everything is serial:
237-- main = 'runSerialT' $ (delay 1 <> delay 2) '<=>' (delay 3 <> delay 4)
240-- ThreadId 36: Delay 1
241-- ThreadId 36: Delay 3
242-- ThreadId 36: Delay 2
243-- ThreadId 36: Delay 4
246-- Note that this operator cannot be used to fold infinite containers since it
247-- requires preserving the state until a stream is finished. To be clear, it
248-- can combine infinite streams but not infinite number of streams.
252-- The '<|' operator can run both computations concurrently, /when needed/.
253-- In the following example since the first computation blocks we start the
254-- next one in a separate thread and so on:
257-- main = 'runSerialT' $ delay 3 '<|' delay 2 '<|' delay 1
260-- ThreadId 42: Delay 1
261-- ThreadId 41: Delay 2
262-- ThreadId 40: Delay 3
265-- This is the concurrent version of the '<>' operator. The computations are
266-- triggered in the same order as '<>' except that they are concurrent. When
267-- we have a tree of computations composed using this operator, the tree is
268-- traversed in DFS style just like '<>'.
271-- main = 'runSerialT' $ (p 1 '<|' p 2) '<|' (p 3 '<|' p 4)
272-- where p = liftIO . print
281-- Concurrency provided by this operator is demand driven. The second
282-- computation is run concurrently with the first only if the first computation
283-- is not producing enough output to keep the stream consumer busy otherwise
284-- the second computation is run serially after the previous one. The number of
285-- concurrent threads is adapted dynamically based on the pull rate of the
286-- consumer of the stream.
287-- As you can see, in the following example the computations are run in a
288-- single thread one after another, because none of them blocks. However, if
289-- the thread consuming the stream were faster than the producer then it would
290-- have started parallel threads for each computation to keep up even if none
294-- main = 'runSerialT' $ traced (sqrt 9) '<|' traced (sqrt 16) '<|' traced (sqrt 25)
295-- where traced m = liftIO (myThreadId >>= print) >> m
303-- Since the concurrency provided by this operator is demand driven it cannot
304-- be used when the composed computations have timers that are relative to each
305-- other because all computations may not be started at the same time and
306-- therefore timers in all of them may not start at the same time. When
307-- relative timing among all computations is important or when we need to start
308-- all computations at once for some reason '<|>' must be used instead.
309-- However, '<|' is useful in situations when we want to optimally utilize the
310-- resources and we know that the computations can run in parallel but we do
311-- not care if they actually run in parallel or not, that decision is left to
312-- the scheduler. Also, note that this operator can be used to fold infinite
313-- containers in contrast to '<|>', because it does not require us to run all
314-- of them at the same time.
316-- The left bias (or the DFS style) of the operator '<|' is suggested by its
317-- shape. You can also think of this as an unbalanced version of the fairly
318-- parallel operator '<|>'.
322-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
323-- computations concurrently. However, unlike '<|' it runs all of the
324-- computations in fairly parallel manner using a round robin scheduling
325-- mechanism. This can be considered as the concurrent version of the fairly
326-- interleaved serial operation '<=>'. Note that this cannot be used on
327-- infinite containers, as it will lead to an infinite sized scheduling queue.
329-- The following example sends a query to three search engines in parallel and
330-- prints the name of the search engine as a response arrives:
334-- import Network.HTTP.Simple
336-- main = 'runSerialT' $ google \<|> bing \<|> duckduckgo
338-- google = get "https://www.google.com/search?q=haskell"
339-- bing = get "https://www.bing.com/search?q=haskell"
340-- duckduckgo = get "https://www.duckduckgo.com/?q=haskell"
341-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
346-- The 'async' API can be used to create references to asynchronously running
347-- stream computations. We can then use 'uncons' to explore the streams
348-- arbitrarily and then recompose individual elements to create a new stream.
349-- This way we can dynamically decide which stream to explore at any given
350-- time. Take an example of a merge sort of two sorted streams. We need to
351-- keep consuming items from the stream which has the lowest item in the sort
352-- order. This can be achieved using async references to streams. See
353-- "Streamly.Examples.MergeSortedStreams".
357-- Each of the semigroup compositions described has an identity that can be
358-- used to fold a possibly empty container. An empty stream is represented by
359-- 'nil' which can be represented in various standard forms as 'mempty',
360-- 'empty' or 'mzero'.
361-- Some fold utilities are also provided by the library for convenience:
363-- * 'foldWith' folds a 'Foldable' container of stream computations using the
364-- given composition operator.
365-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
366-- * 'forEachWith' is like foldMapwith but the container argument comes before
367-- the function argument.
369-- All of the following are equivalent:
373-- import "Streamly.Prelude"
376-- 'toList' . 'serially' $ 'foldWith' (<>) (map return [1..10]) >>= print
377-- 'toList' . 'serially' $ 'foldMapWith' (<>) return [1..10] >>= print
378-- 'toList' . 'serially' $ 'forEachWith' (<>) [1..10] return >>= print
383-- The previous section discussed ways to merge the elements of two streams
384-- without doing any transformation on them. In this section we will explore
385-- how to transform streams using 'Functor', 'Applicative' or 'Monad' style
386-- compositions. The applicative and monad compositions of all 'IsStream' types
387-- behave exactly the same way as a list transformer. For simplicity of
388-- illustration we are using streams of pure values in the following examples.
389-- However, the real application of streams arises when these streams are
390-- generated using monadic actions.
394-- In functional programmer's parlance the 'Monad' instance of 'IsStream'
395-- types implement non-determinism, exploring all possible combination of
396-- choices from both the streams. From an imperative programmer's point of view
397-- it behaves like nested loops i.e. for each element in the first stream and
398-- for each element in the second stream apply the body of the loop. If you are
399-- familiar with list transformer this behavior is exactly the same as that of
400-- a list transformer.
402-- Just like we saw in sum style compositions earlier, monadic composition also
403-- has multiple variants each of which exactly corresponds to one of the sum
404-- style composition variant.
408-- When we interpret the monadic composition as 'SerialT' we get a standard
409-- list transformer like serial composition.
413-- import "Streamly.Prelude"
415-- main = 'runSerialT' $ do
416-- x <- 'fromFoldable' [3,2,1]
420-- ThreadId 30: Delay 3
421-- ThreadId 30: Delay 2
422-- ThreadId 30: Delay 1
425-- As you can see the code after the @fromFoldable@ statement is run three
426-- times, once for each value of @x@. All the three iterations are serial and
427-- run in the same thread one after another. When compared to imperative
428-- programming, this can also be viewed as a @for@ loop with three iterations.
430-- A console echo loop copying standard input to standard output can simply be
435-- import Data.Semigroup (cycle1)
437-- main = 'runSerialT' $ cycle1 (liftIO getLine) >>= liftIO . putStrLn
440-- When multiple streams are composed using this style they nest in a DFS
441-- manner i.e. nested iterations of an iteration are executed before we proceed
442-- to the next iteration at higher level. This behaves just like nested @for@
443-- loops in imperative programming.
447-- import "Streamly.Prelude"
449-- main = 'runSerialT' $ do
450-- x <- 'fromFoldable' [1,2]
451-- y <- 'fromFoldable' [3,4]
452-- liftIO $ putStrLn $ show (x, y)
461-- You will also notice that this is the monadic equivalent of the sum style
462-- composition using '<>'.
466-- When we interpret the monadic composition as 'AsyncT' we get a /concurrent/
467-- list-transformer like composition. Multiple monadic continuations (or loop
468-- iterations) may be started concurrently. Concurrency is demand driven
469-- i.e. more concurrent iterations are started only if the previous iterations
470-- are not able to produce enough output for the consumer of the output stream.
471-- This is the concurrent version of 'SerialT'.
475-- import "Streamly.Prelude"
477-- main = 'runAsyncT' $ do
478-- x <- 'fromFoldable' [3,2,1]
482-- ThreadId 40: Delay 1
483-- ThreadId 39: Delay 2
484-- ThreadId 38: Delay 3
487-- As you can see the code after the @fromFoldable'@ statement is run three
488-- times, once for each value of @x@. All the three iterations are concurrent
489-- and run in different threads. The iteration with least delay finishes first.
490-- When compared to imperative programming, this can be viewed as a @for@ loop
491-- with three concurrent iterations.
493-- Concurrency is demand driven just as in the case of '<|'. When multiple
494-- streams are composed using this style the iterations are triggered in a DFS
495-- manner just like 'SerialT' i.e. nested iterations are executed before we
496-- proceed to the next iteration at higher level. However, unlike 'SerialT'
497-- more than one iterations may be started concurrently, and based on the
498-- demand from the consumer.
502-- import "Streamly.Prelude"
504-- main = 'runAsyncT' $ do
505-- x <- 'fromFoldable' [1,2]
506-- y <- 'fromFoldable' [3,4]
507-- liftIO $ putStrLn $ show (x, y)
516-- You will notice that this is the monadic equivalent of the '<|' style
517-- sum composition. The same caveats apply to this as the '<|' operation.
519-- $interleavedNesting
521-- When we interpret the monadic composition as 'InterleavedT' we get a serial
522-- but fairly interleaved list-transformer like composition. The monadic
523-- continuations or iterations of the outer loop are fairly interleaved with
524-- the continuations or iterations of the inner loop.
528-- import "Streamly.Prelude"
530-- main = 'runInterleavedT' $ do
531-- x <- 'fromFoldable' [1,2]
532-- y <- 'fromFoldable' [3,4]
533-- liftIO $ putStrLn $ show (x, y)
542-- You will notice that this is the monadic equivalent of the '<=>' style
543-- sum composition. The same caveats apply to this as the '<=>' operation.
545-- $fairlyConcurrentNesting
547-- When we interpret the monadic composition as 'ParallelT' we get a
548-- /concurrent/ list-transformer like composition just like 'AsyncT'. The
549-- difference is that this is fully parallel with all iterations starting
550-- concurrently instead of the demand driven concurrency of 'AsyncT'.
554-- import "Streamly.Prelude"
556-- main = 'runParallelT' $ do
557-- x <- 'fromFoldable' [3,2,1]
561-- ThreadId 40: Delay 1
562-- ThreadId 39: Delay 2
563-- ThreadId 38: Delay 3
566-- You will notice that this is the monadic equivalent of the '<|>' style
567-- sum composition. The same caveats apply to this as the '<|>' operation.
571-- The streamly code is usually written in a way that is agnostic of the
572-- specific monadic composition type. We use a polymorphic type with a
573-- 'IsStream' type class constraint. When running the stream we can choose the
574-- specific mode of composition. For example look at the following code.
578-- import "Streamly.Prelude"
581-- composed :: 'IsStream' t => t m a
586-- liftIO $ putStrLn $ show (sz, cl, sh)
590-- sizes = 'fromFoldable' [1, 2, 3]
591-- colors = 'fromFoldable' ["red", "green", "blue"]
592-- shapes = 'fromFoldable' ["triangle", "square", "circle"]
595-- Now we can interpret this in whatever way we want:
598-- main = 'runSerialT' composed
599-- main = 'runAsyncT' composed
600-- main = 'runInterleavedT' composed
601-- main = 'runParallelT' composed
604-- Equivalently, we can also write it using the type adapter combinators, like
608-- main = 'runStream' $ 'serially' $ composed
609-- main = 'runStream' $ 'asyncly' $ composed
610-- main = 'runStream' $ 'interleaving' $ composed
611-- main = 'runStream' $ 'parallely' $ composed
614-- As an exercise try to figure out the output of this code for each mode of
619-- 'fmap' transforms a stream by mapping a function on all elements of the
620-- stream. The functor instance of each stream type defines 'fmap' to be
621-- precisely the same as 'liftM', and therefore 'fmap' is always serial
622-- irrespective of the type. For concurrent mapping, alternative versions of
623-- 'fmap', namely, 'asyncMap' and 'parMap' are provided.
628-- main = ('toList' $ 'serially' $ fmap show $ 'fromFoldable' [1..10]) >>= print
631-- Also see the 'mapM' and 'sequence' functions for mapping actions, in the
632-- "Streamly.Prelude" module.
636-- Applicative is precisely the same as the 'ap' operation of 'Monad'. For
637-- zipping and parallel applicatives separate types 'ZipSerial' and 'ZipAsync'
640-- The following example runs all iterations serially and takes a total 17
641-- seconds (1 + 3 + 4 + 2 + 3 + 4):
645-- import "Streamly.Prelude"
646-- import Control.Concurrent
650-- d n = delay n >> return n
652-- main = ('toList' . 'serially' $ (,) \<$> s1 \<*> s2) >>= print
655-- ThreadId 36: Delay 1
656-- ThreadId 36: Delay 3
657-- ThreadId 36: Delay 4
658-- ThreadId 36: Delay 2
659-- ThreadId 36: Delay 3
660-- ThreadId 36: Delay 4
661-- [(1,3),(1,4),(2,3),(2,4)]
664-- Similalrly interleaving runs the iterations in an interleaved order but
665-- since it is serial it takes a total of 17 seconds:
668-- main = ('toList' . 'interleaving' $ (,) \<$> s1 \<*> s2) >>= print
671-- ThreadId 36: Delay 1
672-- ThreadId 36: Delay 3
673-- ThreadId 36: Delay 2
674-- ThreadId 36: Delay 3
675-- ThreadId 36: Delay 4
676-- ThreadId 36: Delay 4
677-- [(1,3),(2,3),(1,4),(2,4)]
680-- 'AsyncT' can run the iterations concurrently and therefore takes a total
681-- of 10 seconds (1 + 2 + 3 + 4):
684-- main = ('toList' . 'asyncly' $ (,) \<$> s1 \<*> s2) >>= print
687-- ThreadId 34: Delay 1
688-- ThreadId 36: Delay 2
689-- ThreadId 35: Delay 3
690-- ThreadId 36: Delay 3
691-- ThreadId 35: Delay 4
692-- ThreadId 36: Delay 4
693-- [(1,3),(2,3),(1,4),(2,4)]
696-- Similalrly 'ParallelT' as well can run the iterations concurrently and
697-- therefore takes a total of 10 seconds (1 + 2 + 3 + 4):
700-- main = ('toList' . 'parallely' $ (,) \<$> s1 \<*> s2) >>= print
703-- ThreadId 34: Delay 1
704-- ThreadId 36: Delay 2
705-- ThreadId 35: Delay 3
706-- ThreadId 36: Delay 3
707-- ThreadId 35: Delay 4
708-- ThreadId 36: Delay 4
709-- [(1,3),(2,3),(1,4),(2,4)]
712-- $compositionSummary
714-- The following table summarizes the types for monadic compositions and the
715-- operators for sum style compositions. This table captures the essence of
719-- +-----+--------------+------------+
720-- | | Serial | Concurrent |
721-- +=====+==============+============+
722-- | DFS | 'SerialT' | 'AsyncT' |
723-- | +--------------+------------+
725-- +-----+--------------+------------+
726-- | BFS | 'InterleavedT' | 'ParallelT' |
727-- | +--------------+------------+
728-- | | '<=>' | '<|>' |
729-- +-----+--------------+------------+
734-- Zipping is a special transformation where the corresponding elements of two
735-- streams are combined together using a zip function producing a new stream of
736-- outputs. Two different types are provided for serial and concurrent zipping.
737-- These types provide an applicative instance that zips the argument streams.
738-- Also see the zipping function in the "Streamly.Prelude" module.
742-- 'ZipSerial' zips streams serially:
746-- import "Streamly.Prelude"
747-- import Control.Concurrent
749-- d n = delay n >> return n
750-- s1 = 'adapt' . 'serially' $ d 1 <> d 2
751-- s2 = 'adapt' . 'serially' $ d 3 <> d 4
753-- main = ('toList' . 'zipping' $ (,) \<$> s1 \<*> s2) >>= print
756-- This takes total 10 seconds to zip, which is (1 + 2 + 3 + 4) since
757-- everything runs serially:
760-- ThreadId 29: Delay 1
761-- ThreadId 29: Delay 3
762-- ThreadId 29: Delay 2
763-- ThreadId 29: Delay 4
769-- 'ZipAsync' zips streams concurrently:
773-- import "Streamly.Prelude"
774-- import Control.Concurrent
775-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
777-- d n = delay n >> return n
778-- s1 = 'adapt' . 'serially' $ d 1 <> d 2
779-- s2 = 'adapt' . 'serially' $ d 3 <> d 4
782-- liftIO $ hSetBuffering stdout LineBuffering
783-- ('toList' . 'zippingAsync' $ (,) \<$> s1 \<*> s2) >>= print
786-- This takes 7 seconds to zip, which is max (1,3) + max (2,4) because 1 and 3
787-- are produced concurrently, and 2 and 4 are produced concurrently:
790-- ThreadId 32: Delay 1
791-- ThreadId 32: Delay 2
792-- ThreadId 33: Delay 3
793-- ThreadId 33: Delay 4
799-- When writing concurrent programs there are two distinct places where the
800-- programmer chooses the type of concurrency. First, when /generating/ a
801-- stream by combining other streams we can use one of the sum style operators
802-- to combine them concurrently or serially. Second, when /processing/ a stream
803-- in a monadic composition we can choose one of the monad composition types to
804-- choose the desired type of concurrency.
806-- In the following example the squares of @x@ and @y@ are computed
807-- concurrently using the '<|' operator and the square roots of their sum are
808-- also computed concurrently by using the 'asyncly' combinator. We can choose
809-- different combinators e.g. '<>' and 'serially', to control the concurrency.
813-- import "Streamly.Prelude" (toList)
814-- import Data.List (sum)
818-- $ 'asyncly' -- Concurrent monadic processing (sqrt below)
820-- x2 \<- 'forEachWith' ('<|') [1..100] $ -- Concurrent @"for"@ loop
821-- \\x -> return $ x * x -- body of the loop
822-- y2 \<- 'forEachWith' ('<|') [1..100] $
823-- \\y -> return $ y * y
824-- return $ sqrt (x2 + y2)
828-- You can see how this directly maps to the imperative style
829-- <https://en.wikipedia.org/wiki/OpenMP OpenMP> model, we use combinators
830-- and operators instead of the ugly pragmas.
832-- For more concurrent programming examples see,
833-- "Streamly.Examples.ListDirRecursive", "Streamly.Examples.MergeSortedStreams"
834-- and "Streamly.Examples.SearchEngineQuery".
838-- Reactive programming is nothing but concurrent streaming which is what
839-- streamly is all about. With streamly we can generate streams of events,
840-- merge streams that are generated concurrently and process events
841-- concurrently. We can do all this without any knowledge about the specifics
842-- of the implementation of concurrency. In the following example you will see
843-- that the code is just regular Haskell code without much streamly APIs used
844-- (active hyperlinks are the streamly APIs) and yet it is a reactive
848-- This application has two independent and concurrent sources of event
849-- streams, @acidRain@ and @userAction@. @acidRain@ continuously generates
850-- events that deteriorate the health of the game character. @userAction@ can
851-- be "potion" or "quit". When the user types "potion" the health improves and
852-- the game continues.
855-- {-\# LANGUAGE FlexibleContexts #-}
858-- import Control.Concurrent (threadDelay)
859-- import Control.Monad (when)
860-- import Control.Monad.State
861-- import Data.Semigroup (cycle1)
863-- data Event = Harm Int | Heal Int | Quit deriving (Show)
865-- userAction :: MonadIO m => 'SerialT' m Event
866-- userAction = cycle1 $ liftIO askUser
871-- "potion" -> return (Heal 10)
872-- "quit" -> return Quit
873-- _ -> putStrLn "What?" >> askUser
875-- acidRain :: MonadIO m => 'SerialT' m Event
876-- acidRain = cycle1 $ liftIO (threadDelay 1000000) >> return (Harm 1)
878-- game :: ('MonadAsync' m, MonadState Int m) => 'SerialT' m ()
880-- event \<- userAction \<|> acidRain
882-- Harm n -> modify $ \\h -> h - n
883-- Heal n -> modify $ \\h -> h + n
884-- Quit -> fail "quit"
887-- when (h <= 0) $ fail "You die!"
888-- liftIO $ putStrLn $ "Health = " ++ show h
891-- putStrLn "Your health is deteriorating due to acid rain,\\
892-- \\ type \\"potion\\" or \\"quit\\""
893-- _ <- runStateT ('runSerialT' game) 60
897-- You can also find the source of this example in
898-- "Streamly.Examples.AcidRainGame". It has been adapted from Gabriel's
899-- <https://hackage.haskell.org/package/pipes-concurrency-2.0.8/docs/Pipes-Concurrent-Tutorial.html pipes-concurrency>
901-- This is much simpler compared to the pipes version because of the builtin
902-- concurrency in streamly. You can also find a SDL based reactive programming
903-- example adapted from Yampa in "Streamly.Examples.CirclingSquare".
907-- Streamly is highly optimized for performance, it is designed for serious
908-- high performing, concurrent and scalable applications. We have created the
909-- <https://hackage.haskell.org/package/streaming-benchmarks streaming-benchmarks>
910-- package which is specifically and carefully designed to measure the
911-- performance of Haskell streaming libraries fairly and squarely in the right
912-- way. Streamly performs at par or even better than most streaming libraries
913-- for common operations even though it needs to deal with the concurrency
918-- We can use @unfoldr@ and @uncons@ to convert one streaming type to another.
919-- We will assume the following common code to be available in the examples
920-- demonstrated below.
924-- import "Streamly.Prelude"
925-- import System.IO (stdin)
927-- -- Adapt uncons to return an Either instead of Maybe
928-- unconsE s = 'uncons' s >>= maybe (return $ Left ()) (return . Right)
929-- stdinLn = 'serially' $ 'fromHandle' stdin
932-- Interop with @pipes@:
935-- import qualified Pipes as P
936-- import qualified Pipes.Prelude as P
939-- -- streamly to pipe
940-- P.runEffect $ P.for (P.unfoldr unconsE stdinLn) (lift . putStrLn)
942-- -- pipe to streamly
943-- -- Adapt P.next to return a Maybe instead of Either
944-- let nextM p = P.next p >>= either (\\_ -> return Nothing) (return . Just)
945-- 'runSerialT' $ 'unfoldrM' nextM P.stdinLn >>= lift . putStrLn
948-- Interop with @streaming@:
951-- import qualified Streaming as S
952-- import qualified Streaming.Prelude as S
955-- -- streamly to streaming
956-- S.stdoutLn $ S.unfoldr unconsE stdinLn
958-- -- streaming to streamly
959-- 'runSerialT' $ unfoldrM S.uncons S.stdinLn >>= lift . putStrLn
963-- Interop with @conduit@:
966-- import qualified Data.Conduit as C
967-- import qualified Data.Conduit.List as C
968-- import qualified Data.Conduit.Combinators as C
970-- -- streamly to conduit
971-- main = (C.unfoldM 'uncons' stdinLn) C.$$ C.print
976-- Streamly unifies non-determinism, streaming, concurrency and FRP
977-- functionality that is otherwise covered by several disparate packages, and
978-- it does that with a surprisingly concise API. Here is a list of popular and
979-- well-known packages in all these areas:
982-- +-----------------+----------------+
983-- | Non-determinism | <https://hackage.haskell.org/package/list-t list-t> |
984-- | +----------------+
985-- | | <https://hackage.haskell.org/package/logict logict> |
986-- +-----------------+----------------+
987-- | Streaming | <https://hackage.haskell.org/package/streaming streaming> |
988-- | +----------------+
989-- | | <https://hackage.haskell.org/package/conduit conduit> |
990-- | +----------------+
991-- | | <https://hackage.haskell.org/package/pipes pipes> |
992-- | +----------------+
993-- | | <https://hackage.haskell.org/package/simple-conduit simple-conduit> |
994-- +-----------------+----------------+
995-- | Concurrency | <https://hackage.haskell.org/package/async async> |
996-- | +----------------+
997-- | | <https://hackage.haskell.org/package/transient transient> |
998-- +-----------------+----------------+
999-- | FRP | <https://hackage.haskell.org/package/Yampa Yampa> |
1000-- | +----------------+
1001-- | | <https://hackage.haskell.org/package/dunai dunai> |
1002-- | +----------------+
1003-- | | <https://hackage.haskell.org/package/reflex reflex> |
1004-- +-----------------+----------------+
1007-- Streamly covers all the functionality provided by both the non-determinism
1008-- packages listed above and provides better performance in comparison to
1009-- those. In fact, at the core streamly is a list transformer but it naturally
1010-- integrates the concurrency dimension to the basic list transformer
1013-- When it comes to streaming, in terms of core concepts, @simple-conduit@ is
1014-- the package that is closest to streamly if we set aside the concurrency
1015-- dimension, both are streaming packages with list transformer like monad
1016-- composition. However, in terms of API @streamly@ is more like the @streaming@
1017-- package. Streamly can be used to achieve more or less the functionality
1018-- provided by any of the streaming packages listed above. The types and API of
1019-- streamly are much simpler in comparison to conduit and pipes. It is more or
1020-- less like the standard Haskell list APIs.
1022-- When it comes to concurrency, streamly can do everything that the @async@
1023-- package can do and more. async provides applicative concurrency whereas
1024-- streamly provides both applicative and monadic concurrency. The 'ZipAsync'
1025-- type behaves like the applicative instance of async. This work was
1026-- originally inspired by the concurrency implementation in @transient@ though
1027-- it has no resemblence with that. Streamly provides concurrency as transient
1028-- does but in a sort of dual manner, it can lazily stream the output. In
1029-- comparison to transient streamly has a first class streaming interface and
1030-- is a monad transformer that can be used universally in any Haskell monad
1031-- transformer stack.
1033-- The non-determinism, concurrency and streaming combination make streamly a
1034-- strong FRP capable library as well. FRP is fundamentally stream of events
1035-- that can be processed concurrently. The example in this tutorial as well as
1036-- the "Streamly.Examples.CirclingSquare" example from Yampa demonstrate the
1037-- basic FRP capability of streamly. In core concepts streamly is strikingly
1038-- similar to @dunai@. dunai was designed from a FRP perspective and streamly
1039-- was originally designed from a concurrency perspective. However, both have
1040-- similarity at the core.
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