TutorialThe following tutorial assumes that the reader is familiar with wxHaskell.
Atomic forms, the building blocks of larger forms, correspond closely to wxHaskell's controls. The atomic form corresponding to entry is called entry', and can be used in a similar way, but without the pointer to the parent window. For example,
myForm = entry' [color := blue]
generates a text entry control with blue text. Because it edits strings, we say that its subject type is String. Like any form, this atomic form can be run in a dialog with the function run_in_dialog, which produces an IO action. The arguments to this function are:
- a parent window for the dialog
- the form to run
- an initial value (of the form's subject type)
new_val <- run_in_dialog pwin myForm "hello"
This IO action shows the dialog containing the form modally. The dialog also contains an OK and a Cancel button with the following behaviour:
- When OK is pushed, the dialog closes and the IO action returns the new value of the form.
- When Cancel is pushed, the dialog closes and the IO action returns the initial value of the form.
Atomic forms can be joined together into composite forms. The easiest way to do this is with the combinators *- and *|. For example, the composite form
(entry' ) *- (checkBox' )
contains a text entry control and a check box. They are placed next to each other, with a 5-pixel gap in between. If we use *| instead of *-, the text entry control is placed above the check box — notice how the symbols - and | designate horizontal and vertical placement.
The resulting composite form also has a composite subject type, namely (String,Bool). This means that if we run this form, we have to provide run_in_dialog with an initial value like ("hello",True); after the dialog is closed, it will also return a value of this type.
Of course, composite forms can also be joined into larger composite forms themselves. Such a form has a subject type consisting of nested pairs, where the nesting structure reflects the application order of the combinators. So,
(entry' ) *| ((checkBox' ) *- (checkBox' ))
has subject type (String,(Bool,Bool)).
Advanced composite forms
With the combinators *- and *|, only very basic layouts can be produced. Also, the subject type structure always reflects the layout structure. Both problems are solved by switching to the more advanced style of composing forms, which uses references.
Every form actually has an extra last argument which is of a special reference type. It tells the form which part of the composite subject type it is editing. In the previous examples, we omitted this argument; references are invisibly added by run_in_dialog, *- and *|. When we assign these values manually, we obtain more control over the layout: in fact, we can use wxHaskell's layout combinators on a form level. For example, the expression
row' 10 [checkBox'  ref2, entry'  ref1]
places the check box to the left of the text entry control (the apostrophe in row' indicates that we are using an adaptation of the row combinator which works on forms). By properly creating the reference values ref1 and ref2, we can specify that the text entry control still edits the first element of the pair, and the check box edits the second. To do this, we have to extend the expression in the following way:
declare2 $ \(ref1,ref2) -> row' 10 [checkBox'  ref2, entry'  ref1]
We can use this new expression as a form; for example, as an argument to run_in_dialog or *-. It is helpful to think of it in the following way:
- the first line declares the form's subject type, which is a pair of two elements named ref1 and ref2
- the second line defines the form's layout in terms of sub-forms; it associates the subject types of those sub-forms with these two elements
(What actually happens here is that the function declare2 produces a pair of reference values, onto which it applies its argument (everything following the $). As a matter of fact, we have partially applied declare2 here, so this is only its first argument; declare2 also has a second (and last) argument which is a reference value. It uses this reference value &mdash a reference to a pair &mdash to derive the two references to the pair's elements.)
declare2 always constructs a subject type which is a pair. There are also variants of declare2 for n-tuples, trees of nested tuples and lists.
Converting a form's subject type
With the atomic forms choice' and radioBox', the user chooses between a finite number of options. They have Int as their subject type, but this is generally not the type representing the information in the rest of the program. For example, if we have a radio box indicating the eye color of a person, it is better to represent this information with a custom type like
data EyeColor = BlueEyes | BrownEyes | GreenEyes deriving (Show,Eq)
and explicitly map these choices to their corresponding position in the radio box. The rest of the program then does not depend on the particular order of the options in the radio box; if we later decide to change this order, we only need to adapt this mapping. This kind of abstraction can be achieved with the function convertL. It takes an enumerated list of values (of an arbitrary type Eq a => a) as its first argument, and a form with subject type Int as its second:
eyeForm = convertL [BrownEyes,BlueEyes,GreenEyes] $ radioBox' Vertical ["brown","blue","green"] 
This has turned eyeForm into a form with subject type EyeColor. It clearly shows the correspondence between the EyeColor values and the strings in the radio box.
Actually, convertL is a specialized case of convert, a function which can convert the subject type of any form into an isomorphic type. Instead of a list, it takes a bijection as its first argument, encoded as a (function, inverse function) pair.
Example. We want to construct a form to edit a time value, using a spin control for hours (0–23) and one for minutes (0–59). This is accomplished using spinCtrl' and *|. However, instead of (Int,Int), we want our form to have a single Int subject type, corresponding to the number of minutes elapsed since midnight. We write:
timeForm = convert (splittime,jointime) $ (spinCtrl' 0 23 ) *| (spinCtrl' 0 59 ) where splittime total = (total `div` 60, total `mod` 60) jointime (hours,minutes) = 60*hours + minutes
As an example of a more elaborate form, we show a form for editing three alarms. It is produced like this:
alarmListForm = declareL $ \refs -> column' 10 $ take 3 $ zipWith makeBox [1..] refs where makeBox nr ref = boxed' ("Alarm " ++ show nr) (alarmForm ref) alarmForm = (id.*.(id.*.id)) $ \(enab,(time,msg)) -> margin' 3 $ row' 8 [ checkBox'  enab , grid' 5 5 [ [floatLeft' $ label' "time:", timeForm time] , [floatLeft' $ label' "message:", entry'  msg] ] ] timeForm = convert (splittime,jointime) $ declare2 $ \(hrs,mins) -> row' 2 [ spinCtrl' 0 23 [outerSize := sz 40 20] hrs , spinCtrl' 0 59 [outerSize := sz 40 20] mins ] where splittime total = (total `div` 60, total `mod` 60) jointime (hours,minutes) = 60*hours + minutes
The subject type of this form is [(Bool,(Int,String)]. The list is declared using declareL and the nested pairs are declared using (id.*.(id.*.id)). An example value of this type (corresponding to the current value in the picture) is:
i_alarms = [ (True, (450, "wake up")) , (False,(645, "meeting")) , (False,(1140,"dinner")) ]
This concludes this tutorial for now. Since FunctionalForms is still in an early stage, I have not written any more documentation material yet, except for the articles. If you have any questions I'll be glad to answer them by e-mail: s.evers AT cs.ru.nl.