graphicButtonF :: F Drawing Click data Drawing = ...which creates buttons containing graphics that can be changed dynamically.
graphicButtonFis used to implement the squares of the board.
The second combinator is
type Coord = (Int,Int) boardF :: Coord -> (Coord -> F a b) -> F (Coord,a) (Coord,b)which given the size of the board and a function from the coordinates of a square to a fudget implementing that square creates a parallel composition of square fudgets with the appropriate layout. The square fudgets are addressed with their coordinates.
The implementation of the Explode game was done as follows (comments below):
explodeBoardF = loopF (absF routeSP >==< boardF boardSize boardSize atomsSquareF) where routeSP = ... -- 8 lines atomsSquareF :: Coord -> F AtomColor SquareEvent atomsSquareF (x,y) = loopThroughRightF (absF cltrSP) atomsButtonF where ctrlSP = ... -- 18 lines atomsButtonF :: F (NumberOfAtoms,AtomColor) Click atomsButtonF = ... -- 30 linesComments:
routeSPallows all square fudgets to communicate with each other. But, each square knowns its coordinates and send messages only to its neighbours. The actual communication structure is thus not directly reflected in the program structure.
routeSPkeeps track of whose turn it is. This is also where you would put the test for explosions involving all squares (which should end the game). Otherwise, all the work is done by the squares themselves.
data SquareEvent = ClickNoColor | ClickColor AtomColor | Explode Coord AtomColorwhere the messages mean
ClickNoColor"I was clicked and I was empty".
routeSPthen replies with an atom of the appropriate color (depending on whose turn it is).
ClickColorcolor: "I was clicked and my color was color". If the color matches with color of the current player,
routeSPreplies with an atom of that color, otherwise the message is ignored (some indication of an illegal move could be produced).
Explodesquare color: "I explode and invade square with color".
routeSPforwards the message to the square at square. 2-4 messages of this kind are sent when a square explodes.
combinationLockF, which takes the combination as a string argument. The fudget has the same type (
F Click Click) as
buttonF, which means that we can use
Here is a program which displays a combination lock. The program quits when the right combination (123) is pressed.
F a b -> F a b
Any user interface fudget can be placed in such container, for
example the Explode game.
There are two dynamic features of the program:
timerF :: F (Maybe (Int, Int)) Tick
which, when it receives the message
Just (i,0), will
dynListF, which accepts the messages
f, give it tag
mto fudget tagged
The answer is that each Life instance does not
"keep track" of other instances. Instances communicate by message
passing. The messages are encoded with the data types
LifeCommand. Each Life
instance is a fudget which accepts
LifeEvent messages, and sends
The data types
LifeCommand are more or less direct
encodings from the specification. Events correspond to user actions in
lists, buttons and input fields:
type Lno = Int data LifeEvent =
Commands are used to control each life instance. In a non-distributed environment,we need 'bring window to front', 'change cell size', as well as updating the status list:
- open a new Game of Life window.
- close itself, not quitting the program.
- close all windows, thereby quitting the program.
BringToFrontEvt Lno |
- by selecting one of the Game of Life windows from this list, the user can bring the window 'to the front' (whatever is appropriate given the screen management of your system).
NewSize CellSize |
- refresh display with new cell size.
- start/stop the computation and rendering of generations of life.
data LifeCommand = BringToFrontCmd | SetSize CellSize | LifeStatus [(Lno,String)]
`Global effects' is also achieved by message passing. In this case, a message is broadcast to all instances.
Since (3) includes a lot I/O (redrawing etc), there can be arbitrary delays (X-Windows is client/server based). But usually, changes are 'immediate', whatever that means.
The fudget library has support for socket communication and client/server programming. The message passing technique is (almost) transparent in that any fudget in a program can be replaced by a transceiver fudget which is connected to another program. (Currently, functions cannot be passed between programs, unfortunately.)
LifeEventand the functions that deal with them.
On the other hand, many programming errors are avoided with deterministic merging. In most situations, it is desirable that the program is allowed to react completely to one event from the outside, before accepting the next event.
type DiaF a = F a a class DialogItem a where dialog :: DiaF awith instance declarations
instance DialogItem Int where dialog = stripInputSP >^^=< intF instance DialogItem Bool where dialog = toggleButtonF ""for integer and boolean dialog items. Also, we would like to have
instance DialogItem String where dialog = ?but in Haskell, we cannot use
Stringin an instance declaration. We fix this by adding an extra method (confer the
Textclass) in DialogItem:
class DialogItem a where dialog :: DiaF a dialogList :: DiaF [a]and the instance
instance (DialogItem a) => DialogItem [a] where dialog = dialogListNow, we can add an instance for characters, which makes strings work as desired:
instance DialogItem Char where dialogList = stripInputSP >^^=< stringFBut this is of course not enough. We also want to combine dialog items when forming our dialogs. We use the product and sum types for this:
instance (DialogItem a,DialogItem b) => DialogItem (Either a b) where dialog = vBoxF (dialog >+< dialog) instance (DialogItem a,DialogItem b) => DialogItem (a,b) where dialog = hBoxF (dialog >.< dialog)
Here, we have made an arbitrary choice about the layout. Different choices of items have a vertical layout, whereas items that belong to the same choice are placed horizontally.
Now, we have enough power to define a dialog that handles values as complicated as
Either Int (String,Bool)for example! Here is a program that uses such a dialog:
main = fudlogue $ shellF "T" $ f dia :: DiaF (Either Int (String,Bool)) dia = dialog f = labLeftOfF "Output" (displayF' (setBorderWidth 0) >=^< show) >==< dia >==< labLeftOfF "Input" (read >^=< inputDoneSP >^^=< stringF)(Here, we ignore the fact that it isn't a good idea to use
read, since the program will crash if we input something which cannot be parsed. We should really use
|At the top, it displays the values output from the dialog. It also has an input field at the bottom, which let us change the value of the dialog (and therefore also the top display). The image shows the dialog after clicking on the toggle button, and entering the text "Hello" in input field to the right of it.|
|We can also enter a number in the integer input field. Note how the output changes.|
|Finally, we can simulate program control of the dialog by entering a value in the Input field at the bottom.|
dia. By changing that, we can get an entirely different dialog. This is an example of a program where the types determine the behaviour, via the overloading mechanism. This is what turns this into a PCI oriented solution: programmers are sometimes more interested in the functionality of their program, not the layout of the GUI. With the
Dialogclass, one can do experiments and changes to a program which changes the type of data in the GUI, without having to change the GUI code.
class DialogItem t a where dialog :: t -> DiaF a dialogList :: t -> DiaF [a] instance (DialogItem t a) => DialogItem t [a] where dialog = dialogList instance (DialogItem t a,DialogItem u b) => DialogItem (t,u) (Either a b) where dialog = dialog2 (>+<) vBoxF instance (DialogItem t a,DialogItem u b) => DialogItem (t,u) (a,b) where dialog = dialog2 (>.<) hBoxF dialog2 c p (t,u) = p (dialog t `c` dialog u) instance DialogItem String Char where dialogList ls = label ls $ stripInputSP >^^=< stringF instance DialogItem String Int where dialog ls = label ls $ stripInputSP >^^=< intF label = labLeftOfF instance DialogItem String Bool where dialog ls = label ls $ toggleButtonF ""Here is a silly example of such a dialog:
dia :: DiaF (Either Int (String,Bool)) dia = dialog ("A number",("or a string","and a bool"))
0in the title bar, whereas the right view is a copy, titled
1. The counter is controlled in a slightly different manner compared to the challenge, the ``auto-increment-mode'' is controlled by the toggle button Auto. It is activated in counter
0, as indicated by the black dot in the toggle button.
The Increment button is used to manually increment the counter,
independently of the state of the timer.
The program is structured in two parts, one that does the counting and
one for handling the copy and link operations. The latter is contained
in the fudget
multiF :: (s -> F v s) -> F s v -> s -> F a bIn the expression
multiF state_fudget view_fudget init_state
state_fudgetis a fudget that can maintain a state (type
s). Whenever the user demands a new copy, a new state fudget will be spawned inside a
dynListF. Note that the state fudget doesn't need to have a visual appearance. To start with, one state fudget will be applied to the initial state (
init_state) and launched.
Each launched state fudget resides inside a group fudget,
together with at least one view. The argument
view_fudget is used to create views. The view is
decorated with the Copy and Link buttons, and wrapped in a
which adds a title bar with the group number. Whenever a new link is
demanded, a new view will be launched in the group. Output from the
state fudget is broadcast to all views in the group. The output from a
view fudget (of type
v) is fed back to the state fudget
in the group.
Here is the complete code for
In the Multiple Counter example, the state fudget contains a counter and a timer. The view fudget is an integer display and a toggle button. The state that is output from the state fudget to the views has type
type State = (Int,Bool)and the view fudgets send messages of type
data View = Increment | Auto Boolto the state fudget. Here is the main program.