[clean-list] Stateful components using ObjectIO receivers...

Fabien Todescato f.todescato@larisys.fr
Thu, 7 Jun 2001 18:19:36 +0200


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.

The design principle I have finally hit upon is the following : I structure
the state of the application as a set of stateful components that are
separately modularized. The stateful components maintain an encapsulated
state and may react to messages emanating from the other parts of the
system. At initialization or during life time, the component may be given a
set of call back function so as to be able to react in turn on the other
parts of the system.

A stateful component holds a state implemented as a parameterized abstract
data type S a. The functions of type IdFun .( S .a ) are transition function
that do affect only the local state of the object, while the functions of
type IdFun * ( .S .a, * PSt .a ) are transition functions that may also
involve side-effects on the rest of the system, let's call this latter type
as transition function. From the type IdFun * ( .S .a, * PSt .a ) we see
that the role of the type variable a in S a is to specify the type of the
local state of the interactive process in which the stateful component is
run. Also, thanks to currying, transition functions accepting more arguments
are understood as higher-order functions returning state transition
functions.

Now, the stateful components are in general implemented as follows. The
stateful component with state .( S .a ) is implemented by means of a
receiver of type ( Receiver ( IdFun * ( .S .a , * PSt .a ) ) .( S .a ) * (
PSt .a ) ), where the type of the message is a state-transition function,
and the type of the encapsulated state is .( S .a ). As an example of a
stateful component - real code fragments -, say I have :

:: TaskWindowState a
= { window :: ColouredTextWindow a // Another stateful component
encapsulated as record member...
  , accSymbolTable :: * ( PSt a ) -> * ( .SymbolTable , * PSt a ) // Not
important in the example here...
  , accTaskArray   :: * ( PSt a ) -> * ( .TaskArray   , * PSt a ) // Not
important in the example here...
  , accTaskList    :: * ( PSt a ) -> * ( .TaskList    , * PSt a ) // Not
important in the example here...
  }
:: TaskWindowTransition a :==  * ( .TaskWindowState a , * PSt a ) -> * (
.TaskWindowState a , * PSt a )

:: TaskWindow a
= { receiverId :: RId ( TaskWindowTransition a )
  }

The following function creates an ( TaskWindow a ) stateful component. The
functions accSymbolTable accTaskArray accTaskList passed to the stateful
component are call back functions that allows the ( TaskWindow a ) to reach
the * PSt .a global state. In that example, these are access functions that
return components of the global state, but call back functions performing IO
and global state transitions could be passed as well.

openTaskWindow :: (*(PSt a) -> *(.SymbolTable,*PSt a))
                  (*(PSt a) -> *(.TaskArray,*PSt a))
                  (*(PSt a) -> *(.TaskList,*PSt a))
                  *(PSt a) -> *(.TaskWindow a,*PSt a)
openTaskWindow accSymbolTable accTaskArray accTaskList pst
  #! ( receiverId , pst ) = openRId pst
  #! pst = openColouredTextWindow window
                                  TASK_WINDOW_TITLE
                                  ( ( textSetFont   TASK_WINDOW_FONT
                                    o textSetColour TASK_WINDOW_BACKGROUND
                                    )
                                    textNew
                                  )
                                  pst

  // Here the receiver is opened as a top-level ObjectIO element.
  // The receiver local state is precisely a ( TaskWindowState a ).
  // The receiver function receiveTransition just applies the message
function to the state.

  #! pst = ( snd o openReceiver ( initialTaskWindowState window ) ( Receiver
receiverId receiveTransition [ ] ) ) pst

  #! taskWindow = { TaskWindow | receiverId = receiverId }
  #! pst = taskWindowDo taskWindow taskWindowUpdate pst
  #! pst = windowFitContent window pst
  = ( taskWindow , pst )
where
  receiveTransition f st
    = f st 
  initialTaskWindowState window
    = { TaskWindowState
      | window         = window
      , accSymbolTable = accSymbolTable
      , accTaskArray   = accTaskArray
      , accTaskList    = accTaskList
      }

The function to have such a ( TaskWindow a ) stateful component react to
some message is then simply :

  taskWindowDo :: !.(TaskWindow .a) ( TaskWindowTransition .a ) -> .IdFun *(
PSt .b )
  taskWindowDo { receiverId } m = snd o syncSend receiverId m

That is, the message m is directly a state transition function. No encoding
using an algebraic data type is required. This is so because of the
receiveTransition function used in the initialization of the encapsulated
receiver :

  receiveTransition f st
    = f st 

Consider for example the following state-transition function :

  taskWindowUpdate :: !*(!u:TaskWindowState a,*PSt a) -> *(v:TaskWindowState
a,*PSt a), [u <= v]
  taskWindowUpdate ( tws =: { accSymbolTable , accTaskArray , accTaskList ,
window } , pst )
    <code deleted>

I can send as follows a taskWindowUpdate message to some ( TaskWindow a ) tw
:

taskWindowDo tw taskWindowUpdate 

Or, if I define :

  (>-) infix 9
  (>-) w m = taskWindowDo w m

I write something which resembles the use of the C++ -> member access
operator to have a member function invoked on some instance :

  tw >- taskWindowUpdate

and this is of type .IdFun *( PSt .b ).

Now, the local state of my ObjectIO application is some record, or
structure, where I keep the various stateful components used by the
application :

:: SessionState
= { symbolTable :: SymbolTable
  , taskWindow  :: Maybe ( TaskWindow SessionState ) // Notice how
SessionState is recursive...
  , taskArray   :: Maybe TaskArray
  , taskList    :: Maybe [ Task ]
  }

I have for example the following call back functions that reach for other
components of the state :

accSymbolTable pst =: { ls = { symbolTable                } } =  (
symbolTable , pst )
accTaskArray   pst =: { ls = { taskArray = Just taskArray } } =  ( taskArray
, pst )
accTaskList    pst =: { ls = { taskList  = Just taskList  } } =  ( taskList
, pst )

And I initialize the ( TaskWindow SessionState ) stateful component by
passing him these callback functions at initialization time :

computeTaskArray pst =: { ls } = switch ls
where
  switch { taskWindow  = Nothing
         , taskArray   = Nothing
         , taskList    = Nothing
         }
  #! pst = printMessageList [ "Computing tasks from TSL database." ] pst
  #! ( taskArray , taskList ) = newTaskArray tslDatabase messageList
  #! pst = { pst
           & ls = { ls
                  & taskArray  = Just taskArray
                  , taskList   = Just taskList
                  }
           }

  // Here I initialize the ( TaskWindow SessionState ) component with...
  // ...the call back functions.

  #! ( taskWindow , pst ) = openTaskWindow accSymbolTable accTaskArray
accTaskList pst
  #! pst = { pst
           & ls = { ls
                  & taskWindow = Just taskWindow
                  }
           }
  = pst
  switch _
  = pst

Using receivers this way, I can separately modularize the components of the
state of my interactive application. The components are related to the
global state, and between them, by means of the call back functions of the
global state.

Voilą, I am looking forward to your opinions.

Sincerely yours, Fabien TODESCATO

PS.As I have corrected manually some names occurring in type declarations, I
may have introduced errors in the delicate uniqueness attributes. I
apologize for these and hope however that the big picture is correctly
presented. I have, however, running code using that architecture.