To illustrate what kind of program structure we are looking for, take a look at the counter example (pun intended). The user interface should contain two GUI elements: a button and a numeric display. Each time the button is pressed, the number in the display is incremented. We would like the program to contain one stream processor per GUI element, taken from a library (often called GUI toolkit or widget set), and an application-specific stream processor that counts the button presses and outputs numbers to the numeric display. The stream processors should be connected as in Figure 3.
Figure 3. The desired program structure of the counter example.
The key idea is that stream processors from the library handle the low-level details of the GUI elements, and the code that the application programmer writes, communicates with the GUI elements on a higher level of abstraction.
GUI elements can be seen as a particular kind of I/O device that a program can communicate with. The idea naturally extends to communication with other types of I/O devices, such as other computers on the Internet.
Our solution to building programs with this structure in a purely functional language, is based on a special kind of stream processor, the Fudget (see Figure 4. ``Fudget'' is an abbreviation of functional widget, where widget is an abbreviation of window gadget).
Figure 4. The Fudget.
A fudget has both low-level streams and high-level streams. The low-level streams are always connected to the I/O system, allowing the fudget to control a GUI element by receiving events and sending commands to the window system. The high-level streams can carry arbitrary (usually more abstract) values, and they connect the fudgets that make up a program in an application-specific way.
We will write the type of a fudget as
where hi and ho are the types of the messages in the high-level input and output streams, respectively.F
hi ho
The high-level streams between fudgets are connected by the programmer using combinators. Three basic ways to combine fudgets (and stream processors in general) are serial composition, parallel composition and loops, see Figure 5.
Figure 5. Serial composition, parallel composition, and loop.
The types of these combinators are:
These simple ideas allow programs with graphical user interfaces to be built in a hierarchical way using a declarative style. For example, the counter example can be expressed as
>==< :: F a b -> F c a -> F c b
-- serial composition
>*< :: F a b -> F a b -> F a b
-- parallel composition
loopF :: F a a -> F a a
-- loop
that is, a serial composition of three fudgets, wheredisplayF >==< counterF >==< buttonF "Increment"
displayF
and buttonF
handle the widgets that the user
interacts with, and counterF
just counts the button
presses.
Serial composition is closely related to ordinary function composition. With this in mind, one can see that the program has much the same structure as the Landin stream I/O number-summing example shown in Chapter 4. Examples like this one will be explained further in Chapter 9.