comp.lang.ada
 help / color / mirror / Atom feed
From: Nick Roberts <nick.roberts@acm.org>
Subject: Re: OO problem: Performing actions on messages (very long, sorry)
Date: Fri, 24 Dec 2004 18:52:33 +0000
Date: 2004-12-24T18:52:33+00:00	[thread overview]
Message-ID: <gemini.i98prk00kdjon02hs.nick.roberts@acm.org> (raw)
In-Reply-To: 1103723394.299024.314670@c13g2000cwb.googlegroups.com

"per" <commander@death-star.com> wrote:

From what I can gather about the design you are looking for, my impression
is that you need two tagged types -- one for messages, one for actions --
and a mechanism of 'multiple dispatch' to select the appropriate Put
procedure.

Some languages support a form of method selection called 'multiple
selection', where a method can be selected depending on the type of not just
one but several parameters. Multiple dispatch is the workmanlike way that
you do the equivalent of multiple selection in languages that don't support
it (such as Ada). The essence is that first you dispatch selecting on one
kind of tagged type (in this case, a message), into a procedure that then
itself dispatches selecting on another tagged type (in this case, an
action), and possibly so on for yet more parameters.

I'll try to illustrate this idea.

   with AI302.Indefinite_Double_Linked_Lists; -- for private part only
   package Messaging is

      type Message is abstract tagged limited private;
      type Action  is abstract tagged private;

      procedure Execute (M: in out Message;
                         A: in     Action'Class) is abstract;

      procedure Enqueue (M: in out Message'Class;
                         A: in     Action'Class);

      procedure Execute_Actions (M: in out Message'Class);

      Unsupported_Action: exception; -- message could not execute action
      Execution_Error: exception; -- something went wrong in Execute_Actions

   private
      type Action is abstract tagged limited null record;

      package Action_Queueing is new
         AI302.Indefinite_Doubly_Linked_Lists( Action'Class );
      subtype Action_Queue is Action_Queueing.Vector;

      type Message is abstract tagged limited
         record
            Queue: Action_Queueing.Action_Queue;
         end record;

   end Messaging;

   package body Messaging is

      use Action_Queueing;

      procedure Enqueue (M: in out Message'Class;
                         A: in     Action'Class) is
      begin
         Append( M.Queue, A );
      end;

      procedure Execute_Actions (M: in out Message'Class) is
         C: Action_Queueing.Cursor_Type := First(M.Queue);
         Error: Boolean := False;
      begin
         while C /= Action_Queueing.No_Element loop
            begin
               Execute( M, Element(M.Queue,C) ); -- dispatching
            exception
               when others => Error := True; -- note if something failed
            end;
            C := Next(C);
         end loop;
         Clear(M.Queue);
         if Error then raise Execution_Error; end if;
      end;

   end Messaging;

   package Messaging.Scoring is

      type Scoring_Action is new Action with
         record
            Score: Float;
         end;

      Minimum_Score: constant Float := 0.1;

   end;

   package Messaging.Hierarchy is

      type Hierarchy_Action is new Action with
         record
            Level: Natural;
         end;

   end;

   package Messaging.Stock_Movement is

      type Stock_Arrival_Message  is new Message with private;
      type Stock_Despatch_Message is new Message with private;

      procedure Execute (M: in out Stock_Arrival_Message;
                         A: in     Action'Class);

      procedure Execute (M: in out Stock_Despatch_Message;
                         A: in     Action'Class);

      Stock_Item_Error: exception; -- not a current stock item

   private
      type Stock_Movement_Message is new Message with
         record
            Item: Stock_ID;
         end record;

      type Stock_Arrival_Message is new Stock_Movement_Message with 
         record
            ...
         end record;

      type Stock_Despatch_Message is new Stock_Movement_Message with
         record
            ...
         end record;

   end;

   with Scoring, Hierarchy;
   package body Messaging.Stock_Movement is

      procedure Check_ID (M: in out Stock_Movement_Message'Class) is
      begin
         if not Is_Current(M.Item) then
            raise Stock_Item_Error;
         end if;
      end Check_ID;

      procedure Execute (M: in out Stock_Arrival_Message;
                         A: in     Action'Class) is
      begin
         Check_ID( M );
         if A in Scoring.Scoring_Action'Class then
            declare
               use Scoring;
               SA: constant Scoring_Action := Scoring_Action(A);
            begin
               if SA.Score < Minimum_Score then
                  ...
               else
                  ...
               end if;
            end;
         elsif A in Hierarchy.Hierarchy_Action'Class then
            ...
         else
            raise Unsupported_Action;
         end if;
      end Execute; -- for Stock_Arrival_Message

      procedure Execute (M: in out Stock_Despatch_Message;
                         A: in     Action'Class) is
      begin
         Check_ID( M );
         if A in Scoring.Scoring_Action'Class then
            ...
         elsif A in Hierarchy.Hierarchy_Action'Class then
            ...
         else
            raise Unsupported_Action;
         end if;
      end Execute; -- for Stock_Despatch_Message

   end Messaging.Stock_Movement;

The idea here is that the Execute procedure dispatches (singly) on a type
derived from Message, and within the body of any such Execute procedure, we
select again based on the actual type of the action.

In fact, to select again, we have to resort to the old fashioned technique
of a series of 'if' statements. This is because we cannot know in advance
all the possible types derived from Action (because new types could be added
after we have compiled), and Ada demands that all possible tags are known to
perform dynamic dispatch (so that there is no danger of jumping to a
procedure that is not there, and expensive tests can be avoided). If an
action is of a type we do not recognise, we raise an exception
(Unsupported_Action). Note the form of the tests ("if A in X'Class then
...") and the convenient conversion so that we can easily refer to specific
components (e.g. SA.Score).

Both new actions and new messages can be added. However, if a new action is
added, it might be necessary to modify the implementations of some (or all)
of the existing messages to support it.

Please study the code I've written carefully. However, I've not tested it in
any way, so it's likely to have some faults! I'm hoping that I've got close
here to providing a design that will be of genuine use to you, Per. If not,
please say! You will probably have a lot of questions, but that's fine by
me.

I've tried to demonstrate how it is often more appropriate to declare more
than one tagged type in a particular package, if they are closely related.
I've done this twice in my example above. In Messaging, I've declared
Message and Action. In Messaging.Stock_Movement, I've declared both
Stock_Arrival_Message and Stock_Despatch_Message, which share a common
private subancestor (Stock_Movement_Message) with common data and a private
operation of it own (Check_ID).

I've used Matthew Heaney's AI-302 implementation of containers for the queue
implementation, available at:

   http://home.earthlink.net/~matthewjheaney/charles/ai302-20040227.zip

Note that the Ada 2005 standard, when it is published, is likely to have
some minor differences to this package.

-- 
Nick Roberts



  parent reply	other threads:[~2004-12-24 18:52 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2004-12-22 13:49 OO problem: Performing actions on messages (very long, sorry) per
2004-12-22 15:17 ` Dmitry A. Kazakov
2004-12-22 16:28 ` Martin Krischik
2004-12-22 17:42   ` per
2004-12-22 18:16     ` Martin Krischik
2004-12-22 19:54     ` Dmitry A. Kazakov
2005-01-03 12:37       ` per
2005-01-03 14:14         ` Dmitry A. Kazakov
2005-01-04 12:05           ` per
2005-01-04 13:30             ` Dmitry A. Kazakov
2005-01-04 15:21               ` per
2005-01-04 17:47                 ` Dmitry A. Kazakov
2005-01-05 12:01                   ` per
2005-01-05 13:23                     ` Dmitry A. Kazakov
2005-01-05 15:59                       ` per
2005-01-05 20:44                         ` Dmitry A. Kazakov
2005-01-10  8:42                           ` per
2005-01-10 14:22                             ` Dmitry A. Kazakov
2005-01-10 16:24                               ` per
2005-01-10 19:09                                 ` Dmitry A. Kazakov
2005-01-11  9:06                                   ` per
2004-12-22 17:46   ` per
2004-12-22 18:02     ` Martin Krischik
2005-01-03 10:05       ` per
2004-12-22 18:35     ` u_int32_t
2004-12-22 18:38       ` u_int32_t
2004-12-24 18:52 ` Nick Roberts [this message]
2005-01-03 16:59   ` per
2005-01-10 12:10   ` per
2005-01-10 13:49     ` Marius Amado Alves
2005-01-10 21:54 ` Simon Wright
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox