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:
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.displayF :: FontName -> ColorName -> ColorName -> F String a
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:
This would make it easy to use the standard version, and the blackboard examples would be valid programs. But the customisable version (displayF :: F String a displayF' :: FontName -> ColorName -> ColorName -> F String a displayF = displayF' defaultFont defaultBgColor defaultFgColor
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
Then, one could have the display fudget take a list of display parameters as a first argument:data DisplayFParams = Font FontName | ForegroundColor ColorName | BackgroundColor ColorName
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' :: [DisplayFParams] -> F String a
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:displayF = displayF' []
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 (data ButtonFParams = Font FontName | ForegroundColor ColorName | BackgroundColor ColorName | HotKey (ModState,Key)
ButtonFFont
, ButtonFForegroundColor
etc.), which
is just as tedious.
Let us return to the display fudget example, and show how to make it customisable. First, we define classes for the customisable parameters:
Then, we define a new type for the parameter list oftype 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
displayF
:and add the instance declarationsnewtype DisplayF = Pars [DisplayFParams]
The type ofinstance 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)
displayF
will beWe put these declarations inside the module definingdisplayF :: Customiser DisplayF -> F String a
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:If we want to havemyDisplayF = displayF (setFont "fixed" . setBackgroundColor "green")
buttonF
customisable in the same way, we define
the additional class:The button module definesclass HasKeyEquiv a where setKeyEquiv :: (ModState,Key) -> Customiser a
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 namenewtype ButtonF = Pars [ButtonFParams]
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:
If we do not want to change any default values, we usemyFudget = displayF setMyFont >+< buttonF (setMyFont.setMyKey) "Quit" where setMyFont = setFont "fixed" setMyKey = setKeyEquiv ([Meta],"q")
standard
, which does not modify anything:standard :: Customiser a standard p = p standardDisplayF = displayF standard
standard
. We use short and natural names for the standard
versions of GUI fudgets, without customisation argument. So we
haveand 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.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' = ...
As an example, the button and the display fudgets can be dynamically customised:type CF p a b = F (Either (Customiser p) a) b
buttonF'' :: Customiser ButtonF->String->CF ButtonF Click Click displayF'' :: Customiser DisplayF -> CF DisplayF String a
setFont
function.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
buttonF
and buttonF'
)
We used lists of parameters in the implementation of customisers:
An alternative would be to use record types instead:newtype DisplayF = Pars [DisplayFParams] data DisplayFParams = ...
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.data DisplayF = Pars { font::FontName, foregroundColor, backgroundColor :: ColorName } instance HasFont DisplayF where setFont f p = p { font=f } ...