With the stream processors defined here, we abstract away from the streams. We define a number of combinators for stream processors, but no operations on the streams themselves. We have considered a number of different implementations of stream processors, some of which are deterministic and work in a pure, sequential functional language without any extensions, and some of which take advantage of parallel evaluation and indeterministic choice (Chapter 20).
We have also presented a library of combinators for constructing
applications with graphical user interfaces
(Part II) and typed network communication
(Chapter 26). Together with a range of applications,
the library has demonstrated that the stream-processor/fudget
concept is scalable; it can be used to program not only toy
examples, but more complex applications, like WWW-browsers
(Chapter 32) and syntax-oriented proof editors
(Chapter 33). A key combinator here is loopThroughRightSP
(Section 18.2), which allows existing stream
processors/fudgets to be reused in a style that resembles
inheritance in object-oriented programming.
The library also demonstrates that pure functional programming languages are suitable for these tasks, something which was not clear when this work started. Although GUI fudget programs do a fair amount of I/O, response times can be kept sufficiently low (Section 27.5.3).
Since we represent I/O effects by data constructors sent as messages, we have been able to write higher order functions that manipulate I/O effects of fudgets (Chapter 24), which provide a possibility for modifying the behaviour of existing fudgets. A caching mechanism (Section 24.1) and a click-to-type input model (Section 24.2) has been implemented with this method.
The default parameter mechanism (Chapter 15 and 30) demonstrates how Haskell's class system and higher order functions can be combined to simulate a missing language feature. Later, two other GUI libraries, Gadgets (Section 41.3.1) and TkGofer (Section 41.4), have adopted this mechanism.
The fudget graphicsF
in Chapter 27 shows that the task of
displaying and manipulating graphics can be handled efficiently in
a purely functional way. It has been used both in the web browser
in Chapter 32, and in the proof editor Alfa in
Chapter 33. On top of graphicsF
, we have also implemented
a set of combinators that allow syntax-oriented editors to be
built in a high-level style, resembling combinator parsers
(Chapter 28). Our experience with these combinators is
limited sofar, but we believe that they can be employed in a
future version of Alfa.
Although most stream processors we have shown are programmed in a
CPS style, other styles can be used. Simple stream processors can
be programmed by using concatMapAccumlSP
and a state
transition function. A monadic style can also be used, as is
demonstrated in Chapter 31.
As the related work shows in Chapter 41, a number of elegant libraries and interfaces have emerged for GUI programming in pure functional languages during the last years. Is it possible to evaluate and compare all these libraries and the Fudget library? This has been done to some extent in the review in [Nob95]. We will not give any further comparison here, but simply point out some distinguishing features of fudgets and stream processors in general:
The fudget concept has been implemented on top of a number of GUI toolkits [Nob95][Tay96][RS93][CVM97], something which also gives evidence that fudgets are easy to implement.
somF
in (Section 28.4, Figure 81), which has a
stream processor handling three output and three input
streams. An extreme example is the top-level fudget of Alfa
(Chapter 33), whose controlling stream processor defines 15
routing functions, to handle five levels deep messages of Either
type. By using routing functions defined in one
place, adding new subfudgets to the top-level fudget has become a
manageable task. It still requires a bit too much of mechanical
work and there is a need for some new set of combinators or
some other solution to simplify this programming task further.
One could argue that the combinator plumbing of messages imposes a degree of structure on programs which could be healthy. Having explicit identifiers for streams spread over a program results easily in a goto-like spaghetti. The functional language FP by John Backus [Bac78] is entirely based on the use of combinators instead of named variables.
As shown in the Chapter 25, the migration mechanism can be applied to fudgets and used to implement drag-and-drop of GUI fudgets.
How important was lazy evaluation in Fudgets library and programming? Could Fudgets be implemented in ML?When the work on Fudgets started, Haskell used the stream-based I/O model and stream processes were represented as list functions. Nowadays, the continuation-based representation of stream processors is used and Haskell has switched to a monadic I/O system, both of which would work in a strict language. So, lazy evaluation is no longer essential.
A problem with stream-based I/O is the danger of getting ``out of synch'' and reading one result too many or too few. Did this happen to you in practice?For a while, when we used the list based representation and that representation was visible to the programmer, the programmer had to be aware of the ``fudget law'', that is, the one-to-one correspondence between input and output messages (see Section 20.4.1). We sometimes made mistakes. When the stream processor type was made abstract, the fudget law became built-in and the programmer was relieved from thinking of it. Also, we started doing low level I/O through functions like
doStreamIOK
(Section 21.4), which effectively
removed all problems of this kind.Haskell has moved from stream-style I/O to monad-style I/O. Your operations are CPS-style, but they could equally be monad style. Did you make that choice consciously? Why?The monadic programming style had not become popular when we introduced the CPS style combinators, so we did not make a conscious choice between CPS style and monadic style.
Did you come across any situations where Haskell's type system prevented you doing the Right Thing?Yes. For example, the type
XCommand
is supposed be
an interface to X Windows, but we have added various ``pseudo
commands'' that are handled within the Haskell program and never
output to the window system. It would have been nice to define
the proper commands as a subtype of all commands. Making this
distinction in Haskell would require an extra level of tagging,
which we felt was not justified. Analogously, the type
XEvent
contains some ``pseudo events''.