13 Fudget plumbing

We have already seen examples of how to use the fudget plumbing combinators. There are three basic forms of compositions: serial composition, parallel composition and loops.

-- Serial composition:
>==< :: F b c -> F a b -> F a c

-- Parallel composition:
>+< :: F i1 o1 -> F i2 o2 -> F (Either i1 i2) (Either o1 o2)
>*< :: F i o -> F i o -> F i o
listF :: (Eq t) => [(t, F i o)] -> F (t, i) (t, o)

-- Loops:
loopF :: F a a -> F a a
loopLeftF :: F (Either loop input) (Either loop output) -> F input output
loopThroughRightF :: F (Either oldo newi) (Either oldi newo) -> 
                     F oldi oldo -> 
                     F newi newo
The different fudget combinators treat the high-level streams in different ways, while the low-level streams are treated in the same way in all combinators. Figure 22 illustrates serial and parallel composition of fudgets.

Figure 22. Serial and parallel composition of fudgets.

Apart from the plumbing combinators listed above, the Fudget library contains further combinators that capture common patterns. Some of these combinators are described in the following sections.

The fudget combinators have corresponding combinators for plain stream processors, which are discussed in more detail in Chapter 17. Their names are obtained by replacing the F suffix with an SP, or substituting -...- for >...< in the operators.

13.1 Serial compositions

Serial composition connects the output of one fudget to the input of another fudget. As with function composition, data flow from right to left, so that in the composition fud2 >==< fud1, the output of fud1 is connected to the input of fud2.

Many of the examples in Chapter 9 contain serial compositions of the form

mapF f >==< fud
fud >==< mapF f
The library provides the following combinators to capture these cases:

>=^< :: F a b -> (c -> a) -> F c b
fud >=^< f = fud >==< mapF f

>^=< :: (a -> b) -> F c a -> F c b
f >^=< fud = mapF f >==< fud
(The library versions of >^=< and >=^< have more involved definitions to be more efficient.)

Compositions of the form

absF sp >==< fud
fud >==< absF sp
are also common. The library provides two operators for these special cases:

>^^=< :: SP b c -> F a b -> F a c
sp >^^=< fud = absF sp >==< fud

>=^^< :: F b c -> SP a b -> F a c
fud >=^^< sp = fud >==< absF sp
Some combinators, like popupMenuF (see Section 10.4), create parallel compositions of fudgets, but sometimes a serial composition is instead required. This could be accomplished by using a loop and an abstract fudget to do the necessary routing, but the library contains two combinators that do this:

serCompLeftToRightF :: F (Either a b) (Either b c) -> F a c
serCompRightToLeftF :: F (Either a b) (Either c a) -> F b c
The following equations hold:
serCompRightToLeftF (l >+< r) = l >==< r
serCompLeftToRightF (l >+< r) = r >==< l

13.2 Parallel compositions

When combining more than two or three fudgets, the tagging obtained by using >+< can become a bit clumsy. It may then be more convenient to use listF,

listF :: (Eq a) => [(a, F b c)] -> F (a, b) (a, c)
which allows any type in the Eq class to be used as addresses of the fudgets to be combined. The restriction is that the fudgets combined must have the same type. (See Section 40.4 for a discussion of how a language with dependent types could eliminate this kind of restriction.)

There is also a combinator for untagged parallel composition:

>*< :: F i o -> F i o -> F i o
Input to an untagged parallel composition is sent to both argument fudgets.

There is a list version of untagged parallel composition as well,

untaggedListF :: [F a b] -> F a b
which can easily be defined using >*<:

untaggedListF = foldr (>*<) nullF
where nullF,

nullF :: F a b
is the fudget that ignores all input and never produces any output.

The untagged parallel compositions are not as widely used as the tagged ones. The reason is probably that you usually do not want input to be broadcast to all fudgets in a composition.

There are some further combinators that tend to be useful every once in a while. These are various parallel compositions with the identity fudget:

idRightF :: F a b -> F (Either a c) (Either b c)
idLeftF :: F a b -> F (Either c a) (Either c b)
bypassF :: F a a -> F a a
throughF :: F a b -> F a (Either b a)

idRightF  fud = fud >+< idF
idLeftF   fud = idF >+< fud
bypassF   fud = idF >*< fud
throughF  fud = idRightF fud >==< toBothF

toBothF :: F a (Either a a)
toBothF = concatMapF (\ x -> [Left x,Right x])

idF :: F a a
idF = mapF id

13.3 Loops

The simplest loop combinator is loopF,

loopF :: F a a -> F a a
In the composition loopF fud, the output from fud is not only output from the composition, but also sent back to the input of fud.

The most useful loop combinator is probably loopThroughRightF. An example use was shown in Section 9.7 and it is discussed further in Section 18.2.

Some loop combinators that have been useful are:

loopCompThroughRightF :: F (Either (Either a b) c) 
                           (Either (Either c d) a) -> F b d

loopCompThroughLeftF :: F (Either a (Either b c))
                          (Either b (Either a d)) -> F c d
These turn parallel compositions into loops. The following equations hold:
loopCompThroughRightF (l >+< r) = loopThroughRightF l r
loopCompThroughLeftF (l >+< r) = loopThroughRightF r l

13.4 Dynamic fudget creation

The combinators described in the previous sections can be used to build static networks of fudgets. The Fudget library also provides combinators that can be used to add or remove fudgets dynamically (for example, to create new windows dynamically).

To create dynamically changing parallel compositions of fudgets, the library provides

dynListF :: F (Int, DynFMsg a b) (Int, b)
where
data DynFMsg i o = DynCreate (F i o) | DynDestroy | DynMsg i
Above we saw listF that creates tagged parallel compositions that are static. The combinator dynListF can be seen as a variant of listF with a more elaborate input message type. When the program starts, dynListF is an empty parallel composition. A new fudget fud with address i can be added to the parallel composition by passing the message
(i,DynCreate fud)
to dynListF. The fudget with address i can be removed from the parallel composition by passing the message
(i,DynDestroy)
Finally, one can send a message x to an existing fudget with address i by passing the message
(i,DynMsg x)
to dynListF.

(The addresses used by dynListF have been restricted to the type Int for efficiency reasons, but in principle, more general address types could be supported, as for listF.)

A simpler combinator that allows fudgets to change dynamically is dynF:

dynF :: F a b -> F (Either (F a b) a) b
The fudget dynF fud starts out behaving like fud, except that messages to fud should be tagged with Right. The fudget fud can be replaced by another fudget fud' by passing in the message Left fud'.