15 Parameters for customisation

When constructing software libraries, there may be a tension between simplicity and generality. Generality can be achieved by providing many parameters for adapting library components to different needs. But it ruins simplicity if the programmer has to specify a large number of parameters each time a library component is used. To solve this, some programming languages allow some of the arguments in a function call to be omitted, provided that default values are specified for them in the function definition. Haskell does not allow this, but by using one of the powers of functional languages, higher order functions, and the Haskell class system, something very similar can be achieved. The solution used in the Fudget library is presented below. Design and implementation issues are discussed in more detail in Chapter 30.

15.1 Customisers

In order to make fudgets easy to use in the common case and still flexible, they often come in two versions: a standard version, for example buttonF, and a customisable version, for example buttonF'. The name of the customisable version is obtained by appending a ' to the name of the standard version.

Customisable fudgets have a number of parameters that allow things like fonts, colors, border width, etc., to be specified. All these parameters have default values which are used in the standard version of the fudget.

Rather than having one extra argument for each such parameter, customisable versions of fudgets (or other functions) have one extra argument which is a customiser. The customiser is always the first argument. A customiser is a function that modifies a data structure containing the values of all parameters.

type Customiser a = a -> a
The type of the data structure is abstract. Its name is usually the name of the fudget, with the first letter change to upper case--for example, ButtonF in the case of buttonF'.
buttonF' :: (Graphic a) => 
            (Customiser (ButtonF a)) -> a -> F Click Click
So, customisers are obtained by composing a number of modifying functions using ordinary function composition. The function standard,
standard :: Customiser a
acts as the identity customiser and does not change any parameters. The standard versions of the fudgets are simply the customisable versions applied to standard, for example:

buttonF = buttonF' standard

15.2 Sample customisers

There are customisable versions of most fudgets presented earlier in this chapter.

The customisers that are common to many fudgets are overloaded. Some customiser classes are shown in Figure 23.

class HasBgColorSpec a  where setBgColorSpec  ::  ColorSpec -> Customiser a 
class HasFgColorSpec a  where setFgColorSpec  ::  ColorSpec -> Customiser a 
class HasFont a         where setFont         ::  FontName -> Customiser a 
class HasMargin a       where setMargin       ::  Int -> Customiser a 
class HasAlign a        where setAlign        ::  Alignment -> Customiser a 
class HasKeys a         where setKeys         ::  [(ModState, KeySym)] 
                                              ->  Customiser a 
...

Figure 23. Some customiser classes.

The table in Figure 24 shows what customisers are supported by the different customisable fudgets in the current version of the Fudget library.

BgColorSpecFgColorSpecFontMarginAlignKeys
TextFyyyyyn
DisplayFyyyyyn
StringFyyynnn
ButtonFyyynny
ToggleButtonFnnynny
RadioGroupFnnynnn
ShellFnnnynn

Figure 24. Some customiser instances.

Some fudgets also have non-overloaded customisers, for example:

setInitDisp :: a -> Customiser (DisplayF a)
-- changes what is displayed initially

setAllowedChar :: (Char -> Bool) -> Customiser StringF
-- changes what characters are allowed

setPlacer :: Placer -> Customiser RadioGroupF
-- changes the placements of the buttons
As an example of the use of customisation, Figure 25 shows a variation of the radio group shown in Figure 10.
radioGroupF' (setFont "fixed" .
              setPlacer (matrixP 2))
             [(1,"P1"),(2,"P2"),(3,"P3"),(0,"Off")] 0

Figure 25. Custom version of the radio group in Figure 10.