[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. --=====================_9776609==_.ALT--