on master help sign in
update comments on stream types
commit874a2017b1a9165e5b2dff6d1159438dfbd532b7 authorHarendra Kumar <harendra.kumar@gmail.com> dateWed 4 Sep 2019, 12:40 (7 years ago) parentcafa593
2 files changed+102−44
src/Streamly/Streams/StreamDK/Type.hs+75−44 src/Streamly/Unfold.hs+27
src/Streamly/Streams/StreamDK/Type.hs+75−44
@@ -13,57 +13,88 @@
1313-- A CPS style stream using a constructor based representation instead of a
1414-- function based representation.
1515--
16+-- Streamly internally uses two fundamental stream representations, (1) streams
17+-- with an open or arbitrary control flow (we call it StreamK), (2) streams
18+-- with a structured or closed loop control flow (we call it StreamD). The
19+-- higher level stream types can use any of these representations under the
20+-- hood and can interconvert between the two.
21+--
22+-- StreamD:
23+--
1624-- StreamD is a non-recursive data type in which the state of the stream and
1725-- the step function are separate. When the step function is called, a stream
1826-- element and the new stream state is yielded. The generated element and the
1927-- state are passed to the next consumer in the loop. The state is threaded
2028-- around in the loop until control returns back to the original step function
21−-- to run the next step. This creates a closed loop representation with state
22−-- of each step being hidden/abstracted or existential within that step. This
23−-- creates a loop reprentation identical to the for an while loop constructs in
24−-- imperative languages, the states of the steps combined together constitutes
25−-- the state of the loop iteration.
26−--
27−-- Because of a closed representation consing too many elements in this type of
28−-- stream does not scale. Each cons creates a layer that needs to return the
29−-- control back to the caller. However, this representation can be optimized
30−-- very efficiently by the compiler because the state is explicitly separated
31−-- from the functions, represented using pure data constructors and visible to
32−-- the compiler, the stream steps can be fused using case-of-case
33−-- transformations and the state can be specialized using spec-constructor
34−-- optimization, yielding a C like tight loop/state machine with no
35−-- constructors and therefore no unnecessary allocation. Because of the closed
36−-- nature of the stream, exception handling is disciplined in this model
37−-- because control flow is structured in the loop and cannot be arbitrary.
38−-- Also, because of the closed nature of the loop, it is not possible to uncons
39−-- an item from the stream.
40−--
41−-- This type is amenable to stream fusion.
29+-- to run the next step. This creates a structured closed loop representation
30+-- (like "for" loops in C) with state of each step being hidden/abstracted or
31+-- existential within that step. This creates a loop representation identical
32+-- to the "for" or "while" loop constructs in imperative languages, the states
33+-- of the steps combined together constitute the state of the loop iteration.
34+--
35+-- Internally most combinators use a closed loop representation because it
36+-- provides very high efficiency due to stream fusion. The performance of this
37+-- representation is competitive to the C language implementations.
38+--
39+-- Pros and Cons of StreamD:
40+--
41+-- 1) stream-fusion: This representation can be optimized very efficiently by
42+-- the compiler because the state is explicitly separated from step functions,
43+-- represented using pure data constructors and visible to the compiler, the
44+-- stream steps can be fused using case-of-case transformations and the state
45+-- can be specialized using spec-constructor optimization, yielding a C like
46+-- tight loop/state machine with no constructors, the state is used unboxed and
47+-- therefore no unnecessary allocation.
48+--
49+-- 2) Because of a closed representation consing too many elements in this type
50+-- of stream does not scale, it will have quadratic performance slowdown. Each
51+-- cons creates a layer that needs to return the control back to the caller.
52+-- Another implementation of cons is possible but that will have to box/unbox
53+-- the state and will not fuse. So effectively cons breaks fusion.
54+--
55+-- 3) unconsing an item from the stream breaks fusion, we have to "pause" the
56+-- loop, rebox and save the state.
57+--
58+-- 3) Exception handling is easy to implement in this model because control
59+-- flow is structured in the loop and cannot be arbitrary. Therefore,
60+-- implementing "bracket" is natural.
61+--
62+-- 4) Round-robin scheduling for co-operative multitasking is easy to implement.
63+--
64+-- 5) It fuses well with the direct style Fold implementation.
65+--
66+-- StreamK/StreamDK:
4267--
4368-- StreamDK i.e. the stream defined in this module, like StreamK, is a
44−-- recursive data type which has no explicit state, each step yields an element
45−-- and a computation representing the rest of the stream. Stream state is part
46−-- of the function representing the rest of the stream. This creates an open
47−-- computation representation, or essentially a continuation passing style
48−-- computation. After the stream step is executed, the caller is free to
49−-- consume the produced element and then send the control wherever it wants,
50−-- there is no restriction on the control to return back somewhere, the control
51−-- is free to go anywhere. The caller may decide not to consume the rest of the
52−-- stream.
53−--
54−-- This model allows generating arbitrarily large streams lazily using the cons
55−-- operation. Addition of more elements does not add any cost. Similarly uncons
56−-- operation is possible in this stream representation. The control flow can be
57−-- manipulated arbitrarily, there is no structure dictated by the stream. This
58−-- allows interoperation/interconversion with other monads, and other stream
59−-- types. Scalable cons operation and the possibility of uncons operation makes
60−-- this representation very similar to standard lists. Also, this type
61−-- facilitates a scalable monadic composition.
62−--
63−-- This model has a cost because the open representation requires a constructor
64−-- which may not always be eliminated.
65−--
66−-- This type is amenable to foldr/build fusion.
69+-- recursive data type which has no explicit state defined using constructors,
70+-- each step yields an element and a computation representing the rest of the
71+-- stream. Stream state is part of the function representing the rest of the
72+-- stream. This creates an open computation representation, or essentially a
73+-- continuation passing style computation. After the stream step is executed,
74+-- the caller is free to consume the produced element and then send the control
75+-- wherever it wants, there is no restriction on the control to return back
76+-- somewhere, the control is free to go anywhere. The caller may decide not to
77+-- consume the rest of the stream. This representation is more like a "goto"
78+-- based implementation in imperative languages.
79+--
80+-- Pros and Cons of StreamK:
81+--
82+-- 1) The way StreamD can be optimized using stream-fusion, this type can be
83+-- optimized using foldr/build fusion. However, foldr/build has not yet been
84+-- fully implemented for StreamK/StreamDK.
85+--
86+-- 2) Using cons is natural in this representation, unlike in StreamD it does
87+-- not have a quadratic slowdown. Currently, we in fact wrap StreamD in StreamK
88+-- to support a better cons operation.
89+--
90+-- 3) Similarly, uncons is natural in this representation.
91+--
92+-- 4) Exception handling is not easy to implement because of the "goto" nature
93+-- of CPS.
94+--
95+-- 5) Composable folds are not implemented/proven, however, intuition says that
96+-- a push style CPS representation should be able to be used along with StreamK
97+-- to efficiently implement composable folds.
6798
6899module Streamly.Streams.StreamDK.Type
69100 ( Step(..)
src/Streamly/Unfold.hs+27
@@ -17,7 +17,34 @@
1717-- Stability : experimental
1818-- Portability : GHC
1919--
20+-- Streams forcing a closed control flow loop can be categorized under
21+-- two types, unfolds and folds, both of these are duals of each other.
22+--
23+-- Unfold streams are really generators of a sequence of elements, we can also
24+-- call them pull style streams. These are lazy producers of streams. On each
25+-- evaluation the producer generates the next element. A consumer can
26+-- therefore pull elements from the stream whenever it wants to. A stream
27+-- consumer can multiplex pull streams by pulling elements from the chosen
28+-- streams, therefore, pull streams allow merging or multiplexing. On the
29+-- other hand, with this representation we cannot split or demultiplex a
30+-- stream. So really these are stream sources that can be generated from a
31+-- seed and can be merged or zipped into a single stream.
32+--
33+-- The dual of Unfolds are Folds. Folds can also be called as push style
34+-- streams or reducers. These are strict consumers of streams. We keep pushing
35+-- elements to a fold and we can extract the result at any point. A driver can
36+-- choose which fold to push to and can also push the same element to multiple
37+-- folds. Therefore, folds allow splitting or demultiplexing a stream. On the
38+-- other hand, we cannot merge streams using this representation. So really
39+-- these are stream consumers that reduce the stream to a single value, these
40+-- consumers can be composed such that a stream can be split over multiple
41+-- consumers.
2042
43+-- Open control flow style streams can also have two representations. StreamK
44+-- is a producer style representation. We can also have a consumer style
45+-- representation. We can use that for composable folds in StreamK
46+-- representation.
47+--
2148module Streamly.Unfold
2249 (
2350 -- * Unfold Type