It connects the output stream of one stream processor to the input stream of another, as illustrated in Figure 29. Streams flow from right to left, just like values in function compositions,(-==-) :: SP b c -> SP a b -> SP a c
f . g
.
Figure 29. Serial composition of stream processors.
Serial composition of stream processors is very close to function composition. For example, it obeys the following law:
mapSP f -==- mapSP g
=mapSP (f . g)
Figure 30. Parallel composition of stream processors.
There is however, more than one possible definition of parallel composition. How should values in the input stream be distributed to the two stream processors? How should the output streams be merged? We define two versions:
sp1 -*- sp2
denote parallel composition where input
values are propagated to both sp1 and sp2, and output is
merged in chronological order. We will call this version
untagged, or broadcasting parallel composition.sp1 -+- sp2
denote parallel composition where the
values of the input and output streams are elements of a
disjoint union. Values in the input stream tagged Left
or
Right
are untagged and sent to either sp1 or sp2,
respectively. Likewise, the tag of a value in the output
stream indicates which component it came from. We will call
this version tagged parallel composition.
Note that only one of these needs to be considered as primitive. The other can be defined in terms of the primitive one, with the help of serial composition and some simple stream processors like(-*-) :: SP i o -> SP i o -> SP i o (-+-) :: SP i1 o1 -> SP i2 o2 -> SP (Either i1 i2) (Either o1 o2)
mapSP
and filterSP
.-*-
in terms of -+-
, and vice
versa.(-*-) :: SP i o -> SP i o -> SP i o sp1 -*- sp2 = mapSP stripEither -==- (sp1 -+- sp2) -==- toBothSP stripEither :: Either a a -> a stripEither (Left a) = a stripEither (Right a) = a toBothSP :: SP a (Either a a) toBothSP = concatMapSP (\x -> [Left x, Right x]) (-+-) :: SP i1 o1 -> SP i2 o2 -> SP (Either i1 i2) (Either o1 o2) sp1 -+- sp2 = sp1' -*- sp2' where sp1' = mapSP Left -==- sp1 -==- filterLeftSP sp2' = mapSP Right -==- sp2 -==- filterRightSP filterLeftSP = mapFilterSP stripLeft filterRightSP = mapFilterSP stripRight stripLeft :: Either a b -> Maybe a stripLeft (Left x) = Just x stripLeft (Right _) = Nothing stripRight :: Either a b -> Maybe b stripRight (Left _) = Nothing stripRight (Right y) = Just y
The simplest possible loop combinator connects the output of a stream processor to its input, as illustrated in Figure 31. As with parallel composition, we define two versions of the loop combinator:
Figure 31. A simple loop constructor.
loopSP sp
, loopLeftSP sp
, Left
are looped and values
tagged Right
are output. At the input, values from the
loop are tagged Left
and values from the outside are
tagged Right
.
Each of the two loop combinators can be defined in terms of the other, so only one of them needs to be considered primitive.loopSP
:: SP a a -> SP a aloopLeftSP
:: SP (Either l i) (Either l o) -> SP i o
Using one of the loop combinators, one can now obtain bidirectional communication between two stream processors as shown in Figure 32.
Figure 32. Using a loop to obtain bidirectional communication.
Another example shows that we can use loops and parallel composition to create fully connected networks of stream processors. With an expression like
we get a broadcasting network. By replacingloopSP (sp1 -*- sp2 -*- ... -*- spn)
-*-
with
-+-
and some tagging/untagging, we get a network with
point-to-point communication.loopSP
in terms of loopLeftSP
and vice versa.loopSP
in terms of loopLeftSP
is
relatively easy:
Vice versa is a bit trickier:loopSP :: SP a a -> SP a a loopSP sp = loopLeftSP (toBothSP -==- sp -==- mapSP stripEither)
loopLeftSP :: SP (Either l i) (Either l o) -> SP i o loopLeftSP sp = mapFilterSP post -==- loopSP sp' -==- mapSP Right where post (Left (Right x)) = Just x post _ = Nothing sp' = mapSP Left -==- sp -==- mapFilterSP pre where pre (Right x) = Just (Right x) pre (Left (Left x)) = Just (Left x) pre _ = Nothing