42 Evaluation and conclusions

In this thesis, we have presented stream processors to support a concurrent programming style in pure functional languages. Stream processors allow programs to be built in a hierarchical structure of concurrent processes with internal state. They thus support modular design of large programs.

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:

42.1 Frequently Asked Questions

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''.