graphicButtonF :: F Drawing Click data Drawing = ...which creates buttons containing graphics that can be changed dynamically.
graphicButtonF
is 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:
routeSP
allows 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.
routeSP
keeps 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.
SquareEvent
:
data SquareEvent = ClickNoColor | ClickColor AtomColor | Explode Coord AtomColorwhere the messages mean
ClickNoColor
"I was clicked and I was empty". routeSP
then replies with an atom of
the appropriate color (depending on whose turn it is).
ClickColor
color:
"I was clicked and my color was
color". If the color matches with color of
the current player, routeSP
replies with an
atom of that color, otherwise the message is ignored (some
indication of an illegal move could be produced).
Explode
square color: "I explode
and invade square with color". routeSP
forwards the
message to the square at square. 2-4 messages of
this kind are sent when a square explodes.
atomsButtonF
.
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 combinationLockF
wherever a
buttonF
was used.
Here is a program which displays a combination lock. The program quits when the right combination (123) is pressed.
scrollF ::
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
output a Tick
every i
seconds.
dynListF
, which accepts the messages
(t,DynCreate f)
f
, give it tag t
(t,DynMsg m)
m
to fudget tagged t
(t,DynDestroy)
t
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
LifeEvent
and LifeCommand
. Each Life
instance is a fudget which accepts
LifeEvent
messages, and sends
LifeCommand
messages.
The data types LifeEvent
and 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:
NewGame |
- open a new Game of Life window.
CloseMe |
- close itself, not quitting the program.
QuitAll |
- 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.
Computing Bool
- 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.)
LifeCommand
and LifeEvent
and 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
String
in an instance
declaration. We fix this by adding an extra method (confer the Text
class) 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 reads
.)
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 Dialog
class, 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"))
0
in 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
:
multiF :: (s -> F v s) -> F s v -> s -> F a bIn the expression
multiF state_fudget view_fudget init_state
state_fudget
is 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 shellF
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
multiF
.
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.
(Magnus Carlsson)