To promote quick development of prototype programs, a programmer might prefer to concentrate on the functionality, and ignore the GUI design (at least to start with). Since this method can make life easier for the programmer, and to put it in contrast with HCI, we call it PCI (Programmer Computer Interaction) oriented.
With the PCI method, the GUI must be generated automatically
somehow. The basic idea is simple, and can be seen as the GUI
variant of the
Show classes in Haskell,
which allow values of any type to be converted to and from
strings, using the functions
Part of the convenience with these classes is that instances can be derived automatically by the compiler for newly defined datatypes. By usingread :: Read a => String -> a show :: Show a => a -> String
show, it is easy to store data on files, or exchange it over a network (as is done in Chapter 26).
In this section, we will define the class
which plays a similar role to
Show, but for
GUIs. Form elements are combined into forms, which can be
regarded as simple graphical editors that allow a fixed number of
values to be edited. They are often used in dialog windows to
modify various parameters in a GUI application.
Assuming that all the necessary instances of
are available, we show how forms can be generated automatically,
entirely based on the type of the value that the form should
A candidate type for form elements for a type a is a fudget with the type a both on input and output.
The form element class has a method which specifies such a fudget.type FormF t = F t t
We have used the standard trick of adding a special methodclass FormElement t where form :: FormF t formList :: FormF [t] instance (FormElement t) => FormElement [t] where form = formList
formListwhich handles lists, so that we can get an instance for strings (this is discussed in Section 40.2).
We can now define instances for the basic types integers, booleans, and strings.
We also need instances for structured types. The fundamental structured types are product and sum.instance FormElement Int where form = intInputF instance FormElement Bool where form = toggleButtonF " " instance FormElement Char where formList = stringInputF
Note the vertical layout of alternatives, whereas elements within an alternative have a horizontal layout.instance (FormElement t, FormElement u) => FormElement (Either t u) where form = vBoxF (form >+< form) instance (FormElement t, FormElement u) => FormElement (t,u) where form = hBoxF (form >·< form)
>·< puts two fudgets in parallel, just like
>*<, but input and output are pairs.
Input to(>·<) :: F a1 b1 -> F a2 b2 -> F (a1, a2) (b1, b2) f >·< g = pairSP >^^=< (f >+< g) >=^^< splitSP pairSP :: SP (Either a b) (a,b) pairSP = merge Nothing Nothing where merge ma mb = (case (ma,mb) of (Just a,Just b) -> put (a,b) _ -> id) $ get $ \y -> case y of Left a -> merge (Just a) mb Right b -> merge ma (Just b)
f >·< gis split, the first component is fed into f, and the second component is fed into g. The combined fudget will not output anything until both f and g has output something. After this has occurred, a message from one of the subfudgets f or g is paired with the last message from the other subfudget and emitted.
We are ready for a small example. The figure shows a form which can handle input which either is an integer, or a pair of a string and a boolean.
An extended example connects the input and output of the form with fudgets to demonstrate the message traffic:myForm :: FormF (Either Int (String,Bool)) myForm = border (labLeftOfF "Form" form)
This program is illustrated in Figure 82.main = fudlogue $ shellF "Form" $ labLeftOfF "Output" (displayF >=^< show) >==< myForm >==< labLeftOfF "Input" (read >^=< stringInputF)
Figure 82. First, the user has entered the string "Hello" and activated the toggle button. Then, the user entered a number in the integer form element. The last picture is a simulation of how the form can be controlled by the program, in this case by entering a value in the Input field. The value sets the form and is propagated to the output.
Either. It would be desirable to highlight the part that is valid (or to dim the other part).
This generation can also be performed for user defined datatypes by using polytypic programming [JJ97], based on the instances for products and sums. Polytypic programming allows us to define how instances should be derived, based on the structure of the user-defined datatype. For more complicated (for example recursive) types, it might be a better idea to base the form elements on the fudgets for structured graphics in Chapter 27.
After the functionality is there, the programmer's attention might
turn to the look of the forms, and we need a way to tune
them. An approach that immediately comes to mind is to add an
extra attribute parameter to the
If we have an instanceclass FormElement a t where form :: a -> FormF t
FormElement a t, we can construct a form for a type t, given an attribute value of type a. A problem with this approach is that currently, only one parameter may be specified in a class declaration in Haskell. Multi-parameter classes are allowed in Mark Jones' Gofer [Jon91], which also allows instance declarations for compound types like
String. With these features, we could define instances as follows.
instance (FormElement a t, FormElement b u) => FormElement (a,b) (Either t u) where form (a,b) = vBoxF (form a >+< form b) instance (FormElement a t, FormElement b u) => FormElement (a,b) (t,u) where form (a,b) = hBoxF (form a >·< form b) instance Graphic a => FormElement a String where form a = labLeftOfF a $ stripInputSP >^^=< stringF instance Graphic a => FormElement a Int where form a = labLeftOfF a $ stripInputSP >^^=< intF instance Graphic a => FormElement a Bool where form a = toggleButtonF a