30 Parameters for customisation

There are many aspects of GUI fudgets that one might want to modify, e.g. the font or the foreground or background colours for displayF. The simple GUI fudgets have some hopefully reasonable default values for these aspects, but sooner or later, we will want to change them.

In early versions of the Fudget library, the GUI fudgets had several extra parameters to make them general and adaptable to different needs. For example, the type of displayF was something like:

displayF :: FontName -> ColorName -> ColorName -> F String a
Having to specify all these extra parameters all the time made it hard to write even the simplest program: when creating a program from scratch, it was next to impossible to write even a single line of code without consulting the manual. When we wrote programs on overhead slides or on the blackboard, we always left out the extra parameters, to make the code more readable.

A simple way to improve on this situation would be to introduce two versions of each GUI fudget: one standard version, without the extra parameters, and one customisable version, with a lot of extra parameters:

displayF :: F String a
displayF' :: FontName -> ColorName -> ColorName -> F String a

displayF = displayF' defaultFont defaultBgColor defaultFgColor
This would make it easy to use the standard version, and the blackboard examples would be valid programs. But the customisable version (displayF') would still be hard to use: even if you just wanted to change one parameter, you would have to specify all of them and you would have to remember the order of the parameters. So, we went a step further.

First, we wanted be able to change one parameter without having to explicitly give values for all the other ones. A simple way of doing this would be to have a data type with constructors for each parameter that has a default value. In the case of displayF, it might be

data DisplayFParams =  Font FontName 
                    |  ForegroundColor ColorName
                    |  BackgroundColor ColorName
Then, one could have the display fudget take a list of display parameters as a first argument:
displayF' :: [DisplayFParams] -> F String a
We no longer have to remember the order of the parameter, and, whenever we are happy with the default values, we just leave out that parameter from the list, and all is fine.

displayF = displayF' []
However, suppose we want to do the same trick with the button fudget. We want to be able to customise font and colours for foreground and background, like the display fudget, and in addition we want to specify a ``hot-key'' that could be used instead of clicking the button:
data ButtonFParams =  Font FontName 
                   |  ForegroundColor ColorName
                   |  BackgroundColor ColorName
                   |  HotKey (ModState,Key)
Now, we are in trouble if we want to customise a button and a display in the same module, because in a given scope in Haskell, no two constructor names should be equal. Of course, we could qualify the names with module names, but this is tedious. We could also have different constructor names to start with (ButtonFFont, ButtonFForegroundColor etc.), which is just as tedious.

30.1 A mechanism for default values

Our current solution(Footnote: The basics of this design are due to John Hughes.) is not to use constructors directly, but to use overloaded functions instead. We will define a class for each kind of default parameter. Then, each customisable fudget will have instances for all parameters that it accepts. This entails some more work when defining customisable fudgets, but the fudgets become easier to use, which we feel more than justifies the extra work.

Let us return to the display fudget example, and show how to make it customisable. First, we define classes for the customisable parameters:

type Customiser a = a -> a

class HasFont a where
   setFont :: FontName -> Customiser a

class HasForegroundColor a where
   setForegroundColor :: ColorName -> Customiser a

class HasBackgroundColor a where
   setBackgroundColor :: ColorName -> Customiser a
Then, we define a new type for the parameter list of displayF:
newtype DisplayF = Pars [DisplayFParams]
and add the instance declarations
instance HasFont DisplayF where
   setFont p (Pars ps) = Pars (Font p:ps)

instance HasForegroundColor DisplayF where
   setForegroundColor p (Pars ps) = Pars (ForegroundColor p:ps)

instance HasBackgroundColor DisplayF where
   setBackgroundColor p (Pars ps) = Pars (BackgroundColor p:ps)
The type of displayF will be
displayF :: Customiser DisplayF -> F String a
We put these declarations inside the module defining displayF, making DisplayF abstract. When we later use displayF, the only thing we need to know about DisplayF is its instances, which tell us that we can set font and colours. For example:
myDisplayF = displayF (setFont "fixed" .
                       setBackgroundColor "green")
If we want to have buttonF customisable in the same way, we define the additional class:
class HasKeyEquiv a  where
   setKeyEquiv :: (ModState,Key) -> Customiser a
The button module defines
newtype ButtonF = Pars [ButtonFParams]
and makes it abstract, as well as defining instances for font, colours and hot-keys. Note that the instance declarations for font and colours will look exactly the same as for the display parameters! (We can reuse the constructor name Pars as long as we define only one customisable fudget in each module.) In the Fudget library implementation, we have used cpp macros to simplify the implementation of customisable fudgets and avoid code duplication.

We can now customise both the display fudget and the button fudget, if we want:

myFudget = displayF setMyFont >+< buttonF (setMyFont.setMyKey) "Quit"
   where setMyFont  = setFont "fixed"
         setMyKey   = setKeyEquiv ([Meta],"q")
If we do not want to change any default values, we use standard, which does not modify anything:
standard :: Customiser a
standard p = p

standardDisplayF = displayF standard

30.2 Naming conventions for the customisable GUI fudgets

The GUI fudget library is designed so that when you start writing a fudget program, there should be as few distracting parameters as possible. Default values will be chosen for colour, fonts, layout, etc. But a customisable fudget must inevitably have an additional argument, even if it is standard. We use short and natural names for the standard versions of GUI fudgets, without customisation argument. So we have
buttonF :: String -> F Click Click
buttonF = buttonF' standard

buttonF' :: Customiser ButtonF -> String -> F Click Click
buttonF' = ...

displayF :: F String a
displayF = displayF' standard

displayF' :: Customiser DisplayF -> F String a
displayF' = ...
and so on. This way, a programmer can start using the toolkit without having to worry about the customisation concept. Later, when the need for customisation arises, just add an apostrophe and the parameter. One could also have the reverse convention and use apostrophes on the standard versions, something that sounds attractive since apostrophes usually stand for omitted things (in this case the customiser). But then a programmer must learn which fudgets are customisable (and thus need an apostrophe), even if she is not interested in customisation.

30.3 Dynamic customisation

Apart from specifying parameters in the program text, most parameters can in fact be changed dynamically, if needed. Therefore, each customisable fudget comes in a third variant, which is the most expressive. Their names end with two apostrophes. These dynamically customisable fudgets allow customisers as input messages in addition to the usual message type:
type CF p a b = F (Either (Customiser p) a) b
As an example, the button and the display fudgets can be dynamically customised:
buttonF'' :: Customiser ButtonF->String->CF ButtonF Click Click
displayF'' :: Customiser DisplayF -> CF DisplayF String a

30.4 Discussion

Collecting all parameters in a customiser rather than using a high arity function has a number of advantages:The last point is an advantage even when compared to what you can do in languages with support for default values for parameters.

In the X Windows system, customisation is done via a resource database, where the application can lookup values of various parameters. The database is untyped, that is, all values are strings, so no static type checking can be performed. With our customiser solution, parameters are type checked. In addition, the compiler can check that the parameters you specify are supported by the fudget in question, whereas parameters stored in the resource database are silently ignored if the are not supported.

Disadvantages with this method, as compared to such languages, are that

We used lists of parameters in the implementation of customisers:

newtype DisplayF = Pars [DisplayFParams]
data DisplayFParams = ...
An alternative would be to use record types instead:

data DisplayF = Pars { font::FontName,
                       foregroundColor, backgroundColor :: ColorName }

instance HasFont DisplayF where setFont f p = p { font=f }
...
This would make it easier to extract the values of the various parameters in the implementation of the customisable fudgets. A possible disadvantage with this representation is that in the implementation of dynamically customisable fudgets, it would be more difficult to tell what parameters have actually been changed. With the list representation, only the parameters that have been changed occur in the list.