comp.lang.ada
 help / color / mirror / Atom feed
* Design problem using Multiple Dispatch or Redispatch (long)
@ 2000-03-14  0:00 Thom Brooke
  2000-03-14  0:00 ` David Kristola
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Thom Brooke @ 2000-03-14  0:00 UTC (permalink / raw)


I've been struggling with a design problem for some time now, 
and I'm looking for suggestions (or some reason why it won't work).

I have developed a solution, but it's ugly -- little better than 
Ada83.  I want a nice, clean, Ada95 solution that does it _right_.

Here's the situation:
The system consists of a bunch of "commands", and several kinds of
"processor" which "execute" the commands. 

The commands are related in that commands can be received in any 
order, and they have some  common structure, but each specific 
command is distinct -- typically in terms of the fields defined
within the command.

The processors, too, are related, in that they represent 
alternative implementations of the same interface.  Each processor
supports a subset of the commands, and these subsets may overlap
(i.e., two processors may support the same command).  The
way that each processor executes a particular command may be
completely different.  In general, command execution requires
detailed knowledge of the internal representation of the processor,
as well as the structure and fields of the command itself.
Furthermore, there may be multiple instances of each "kind" of
processor.

The set of commands may change over time (typically, new commands
will be added.  But it is possible that existing commands will
be modified or deleted).  Also, the various processors may be
"upgraded" to support additional commands.

What I'm trying to do is define the commands, the processors, and
the mechanism for executing commands on the processors so as to 
reduce redundant and error-prone coding, and make it easier to add
new commands, new processors, or new processor command execution
support.

So far, I've been trying to devise some kind of multiple-dispatching
or redispatching architecture -- I've also tried to work in mixins --
to avoid parallel "if" or "case" statements, but I haven't gotten it
to work.  I suspect I'm either on entirely the wrong track, or I've
missed something really obvious.

Here are some of the things I've tried:

It seems "obvious" to me that the commands represent a family of
types.   There is a Root_Command type, with the general command
operations (the commands are received as a stream of bytes which
must be parsed and formatted into their internal representation).

Each specific command type is derived from the Root_Command type.
At this point, the family is 1-deep.  That is, all specific 
commands are derived directly from Root_Command; no commands are 
derived from other commands.  However, that may change (and it 
greatly complicates maintenance for if/case statememt based 
approaches).

So I have something like this:

with Supporting_Stuff;
package Commands is

    type Root_Command is abstract tagged private;

    function Parse_Command (Input : in Bytes) return Root_Command 
        is abstract;

    -- other general Command subprograms.  Not necessarily abstract

private 

    type Root_Command is ...

end Commands;

with Commands;
package Specific_Commands is

    type Command_1 is new Root_Command with ...

    function Parse_Command ...


    type Command_2 is new Root_Command with ...

    function Parse_Command ...

end Specific_Commands.


Similarly, since the Processors are "alternative implementations", 
an abstract root class with specific derived types for the various
implementations seemed obvious.  Each instance of a processor has 
some "fetch" function available:

function Get_Next_Command (P : Processor) return Root_Command'Class;

and some way to Execute specific commands. The objective was to be 
able to  write something like the following for each processor:

loop
    declare 
        Cmd : Root_Command'Class := Get_Next_Command(Myself);
    begin
        Execute (Myself, Cmd);
    end;
end loop;

where the Execute() procedure would dispatch on the specific Command
type, to some procedure written for that command and the current
processor.  With C different specific commands, and P different
processor variations, there could be up to (C * P) different Execute
procedures.  That's fine.  I want to be able to just write a new
Execute procedure, and have it "automatically" picked up (e.g., by
dispatching).  In particular, what I want to avoid is something like
the following:

loop
    declare 
        Cmd : Root_Command'Class := Get_Next_Command(Myself);
    begin
        if Cmd in Command_1'Class then
            Execute_Cmd_1 (Myself, Command_1(Cmd));
        elsif Cmd in Command_2'Class then
            Execute_Cmd_2 (Myself, Command_2(Cmd));
         ...
         end if;
    end;
end loop;

Initially, I made Execute a primitive operation of Root_Command.
However, that required that Commands know about Processors, which
seemed backwards.  Furthermore, it couldn't support multiple
processor variations:

-- In Commands:
procedure Execute (P : in out Root_Processors'Class;
                   Cmd : in Root_Comand) is
begin
    P_Execute( P, Root_Command'Class(Cmd));
    -- where P_Execute is a primitive operation of 
    -- Root_Processor, and so is dispatching on P.
end Execute;

Even if Execute turned around and redispatched on the Processor
parameter to P_Execute, P_Execute still has to somehow figure 
out which specific command is coming in.  I can't have two 
controlling operands in Execute, so one has to be class-wide.

I looked at making Execute a primitive operation of Processors,
but that has the inverse problem: I can dispatch on specific 
processors (which isn't really necessary, anyway, since execution
stays within the same command/processor pipeline), but I can't 
then dispatch on the Commands.

I thought about mixin's, but I'm really not dealing with 
independent types.  Commands and Processors are related; I'm not
trying to add an orthogonal facet to, say, an Address_Book class.

Now, I can get dispatching on Commands within the different 
Processor variations by using creating second-level 
Commands derived in parallel for each supported specific command:

-- In Commands:
procedure Execute (P : in out Root_Processor'Class;
                   Cmd : in Root_Commands);
-- has "null" body.

with Specific_Commands;
package Processor_1_Commands is

    type P1_Command_1 is new Specific_Commands.Command_1 with null
record;

    procedure Execute (P : in out Root_Processor'Class;
                      Cmd : in P1_Command_1);
    -- keep inherited Command operations, like Parse_Command).

    -- etc.
end Processor_1_Commands;

And in the body of Processor_1_Commands, implement Execute to 
require and operate on a Processor_1 type.  Similarly, for 
Processor_2.  Unfortunately, this means that the Get_Next_Command
function has to return one of the P1_Command_x commands when 
called from Processor_1, and return a P2_Command_x when called
from Processor_2.   Only, the command structures are identical, 
so the only difference would be which specific Processor-Command
type the factory called.  Parallel code, again.

So my dilemma:
I _can_ solve the problem using Ada83-esque if/case statements.
But this seems like such a "mormal" thing to want to do.  It 
can't be that obscure; I can't believe there isn't some feature
or combination of features in Ada95 that will let me do this
cleanly.  Does anyone have any ideas?


-- 

-- Thom Brooke
-- Cut out "_CUT_OUT" to get my real email address.




^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2000-03-16  0:00 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2000-03-14  0:00 Design problem using Multiple Dispatch or Redispatch (long) Thom Brooke
2000-03-14  0:00 ` David Kristola
2000-03-15  0:00   ` Thom Brooke
2000-03-14  0:00 ` tmoran
2000-03-15  0:00   ` Thom Brooke
2000-03-14  0:00 ` Nick Roberts
2000-03-15  0:00   ` Thom Brooke
2000-03-14  0:00 ` Simon Wright
2000-03-15  0:00   ` Thom Brooke
2000-03-16  0:00     ` Simon Wright

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