[clean-list] Stateful components using ObjectIO
receivers...
Peter Achten
peter88@cs.kun.nl
Fri, 29 Jun 2001 11:45:58 +0200
--=====================_9776609==_.ALT
Content-Type: text/plain; charset="iso-8859-1"; format=flowed
Content-Transfer-Encoding: quoted-printable
At 18:19 7-6-01 +0200, Fabien Todescato wrote:
>Dear Cleaners,
>
>And ObjectIO1.2 practitioners, I am at the moment writing an application
>with a strong GUI part, and I must admit I had to fight a while to discover
>a way to neatly structure the state of the application in a modular and
>extendible manner. I found indeed the learning curve to the ObjectIO1.2
>somewhat steep... In this letter, I try to explain a pompously called
>'design principle' that I have found useful, and that you may also want to
>be using. I hope this will sparkle discussions about useful design
>techniques for interactive programs using the ObjectIO library. Though I
>found the example in the documentation quite clear and informative, I have
>the feeling that they do not provide the reader with design techniques that
>scales up well with the complexity of the applications.
As you correctly observe, the 'A Tutorial to the CLEAN Object I/O Library'=
=20
currently focusses on the technical issues of how to use the set of GUI=20
objects that are standard available in the Object I/O library, and much=20
less to creating new instances yourself.
For the past period we have been collecting a number of useful custom=20
defined GUI components (especially Maarten de Mol who is developing a Proof=
=20
Tool dedicated for Clean programs has made a number of goodlooking *and*=20
goodworking objects).
I intend to devote a chapter and/or more examples on this issue in a next=20
version of the Tutorial and Object I/O library.
I welcome initiatives such as yours that describe a systematic approach to=
=20
solve certain typical reoccuring programming patterns.
>Voil=E0, I am looking forward to your opinions.
Allow me to rephrase your technique:
(1) Use uni-directional (Receiver ls pst) to which functions can be sent of=
=20
type (ls,pst) -> (ls,pst). The receiver function fun simply applies the=20
received function to the current state: (fun f st =3D f st). Then the value=
=20
of type ls is the local state to be modularized.
[ NOTE: fun is equivalent to function application. If you define=20
the operator
(@) infixl 9 :: !(.a -> .b) .a -> .b
(@) f x =3D f x
then you can create the receiver as: (Receiver rid (@) []).
]
(2) In the appropriate (local or public) state of the application store the=
=20
(RId ((ls,pst) -> (ls,pst))) identification values. Suppose rid is such a=20
value, and f is a function of type ((ls,pst) -> (ls,pst)), then rid >- f=20
applies f to the current state of the receiver.
In other words: instead of storing pieces of state in the public state=20
(.ps) of the process state (PSt .ps) you store references (RId ((ls,pst) ->=
=20
(ls,pst))) to these states, and instead of applying a function f to a piece=
=20
of state in the public state you apply >- to the reference and the=20
function. In an example, instead of writing:
:: MyState =3D { s1 :: S1
, ...
, sN :: SN
}
changeS1 :: (PSt MyState) -> PSt MyState
changeS1 pst=3D:{ls=3Dls=3D:{s1}}
# (s1,pst) =3D f (s1,pst)
=3D { pst & ls=3D{ls & s1=3Ds1} }
you write:
:: MyState =3D { s1 :: RId (IdFun (S1,PSt MyState))
, ...
, sN :: RId (IdFun (SN,PSt MyState))
}
changeS1 :: (PSt MyState) -> PSt MyState
changeS1 pst=3D:{ls=3D{s1}} =3D s1 >- f pst
Here are my observations:
(A) Using receivers to store state is more dynamic than using state passing=
=20
because receivers can be opened and closed at run-time [1]. However,=20
because you store the receivers in the (public) state you loose this=20
advantage.
(B) Your type of >- is restricted to (TaskWindow .a) state reference=20
containers of (TaskWindowState .a) state transition functions. It would be=
=20
better to make it more generally applicable.
(C) Using a function as message is extremely powerful, but can also be=20
unsafe. The receiver gives full control of its behaviour to any external=20
sender. So, in changeS1 above, if f =3D noLS (appPIO (closeReceiver s1)),=20
then you close the receiver; or, f =3D noLS closeProcess terminates the=20
interactive process. This is one reason why I generally prefer the use of=20
algebraic types for messages.
Regards,
Peter
-----------
[1] In fact, you can use receivers to mimic Concurrent Haskell MVar-s. The=
=20
only difference is that the application programmer must close receivers=20
himself instead of relying on the garbage collector to get rid of=20
unreferenced MVar-s.=20
--=====================_9776609==_.ALT
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
At 18:19 7-6-01 +0200, Fabien Todescato wrote:
Dear Cleaners,
And ObjectIO1.2 practitioners, I am at the moment writing an
application
with a strong GUI part, and I must admit I had to fight a while to
discover
a way to neatly structure the state of the application in a modular
and
extendible manner. I found indeed the learning curve to the
ObjectIO1.2
somewhat steep... In this letter, I try to explain a pompously
called
'design principle' that I have found useful, and that you may also want
to
be using. I hope this will sparkle discussions about useful design
techniques for interactive programs using the ObjectIO library. Though
I
found the example in the documentation quite clear and informative, I
have
the feeling that they do not provide the reader with design techniques
that
scales up well with the complexity of the=20
applications.
As you correctly observe, the 'A Tutorial to the CLEAN Object I/O
Library' currently focusses on the technical issues of how to use the set
of GUI objects that are standard available in the Object I/O library, and
much less to creating new instances yourself.
For the past period we have been collecting a number of useful custom
defined GUI components (especially Maarten de Mol who is developing a
Proof Tool dedicated for Clean programs has made a number of goodlooking
*and* goodworking objects).
I intend to devote a chapter and/or more examples on this issue in a next
version of the Tutorial and Object I/O library.
I welcome initiatives such as yours that describe a systematic approach
to solve certain typical reoccuring programming patterns.
Voil=E0, I am looking forward to your
opinions.
Allow me to rephrase your technique:
(1) Use uni-directional (Receiver ls pst) to which functions can be sent
of type (ls,pst) -> (ls,pst). The receiver function fun simply applies
the received function to the current state: (fun f st =3D f st). Then the
value of type ls is the local state to be modularized.
[ NOTE:
fun is equivalent to function application. If you define the
operator
=
(@)
infixl 9 :: !(.a -> .b) .a -> .b
=
(@)
f x =3D f x
then you
can create the receiver as: (Receiver rid (@) []).
]
(2) In the appropriate (local or public) state of the application store
the (RId ((ls,pst) -> (ls,pst))) identification values. Suppose rid is
such a value, and f is a function of type ((ls,pst) -> (ls,pst)),
then rid >- f applies f to the current state of the receiver.
In other words: instead of storing pieces of state in the public state
(.ps) of the process state (PSt .ps) you store references (RId ((ls,pst)
-> (ls,pst))) to these states, and instead of applying a function f to
a piece of state in the public state you apply >- to the reference and
the function. In an example, instead of writing:
:: MyState =3D { s1 :: S1
&nbs=
p;
, ...
&nbs=
p;
, sN :: SN
&nbs=
p;
}
changeS1 :: (PSt MyState) -> PSt MyState
changeS1 pst=3D:{ls=3Dls=3D:{s1}}
# (s1,pst) =3D f (s1,pst)
=3D { pst & ls=3D{ls &
s1=3Ds1} }
you write:
:: MyState =3D { s1 :: RId (IdFun (S1,PSt
MyState))
&nbs=
p;
, ...
&nbs=
p;
, sN :: RId (IdFun (SN,PSt MyState))
&nbs=
p;
}
changeS1 :: (PSt MyState) -> PSt MyState
changeS1 pst=3D:{ls=3D{s1}} =3D s1 >- f pst
Here are my observations:
(A) Using receivers to store state is more dynamic than using state
passing because receivers can be opened and closed at run-time [1].
However, because you store the receivers in the (public) state you loose
this advantage.
(B) Your type of >- is restricted to (TaskWindow .a) state reference
containers of (TaskWindowState .a) state transition functions. It would
be better to make it more generally applicable.
(C) Using a function as message is extremely powerful, but can also be
unsafe. The receiver gives full control of its behaviour to any external
sender. So, in changeS1 above, if f =3D noLS (appPIO (closeReceiver s1)),
then you close the receiver; or, f =3D noLS closeProcess terminates the
interactive process. This is one reason why I generally prefer the use of
algebraic types for messages.
Regards,
Peter
-----------
[1] In fact, you can use receivers to mimic Concurrent Haskell MVar-s.
The only difference is that the application programmer must close
receivers himself instead of relying on the garbage collector to get rid
of unreferenced MVar-s.