From: bwhite@cobra.camb.inmet.com (Bill White)
Subject: Re: Ada 9x Dispatching Question
Keywords: Ada Dispatching Polymorphism
Organization: Intermetrics Inc, Cambridge MA
Date: Mon, 17 Aug 1992 21:52:19 GMT

In response to Joe Tallet's recent note in comp.lang.ada:

Dispatching only occurs when (1) the operation is an operation of a tagged
type, and (2) the operand is of a class-wide type.  It does not matter that
the operation is obtained by an expanded name or by direct (use) visibility.
Given this, the Shapes package looks like:

package Shapes is
    type shape is tagged ... ;
    procedure draw (x : shape) is <>; -- abstract
end Shapes;

This is Alex Blakemore's original definition.  (Is this name right?)

Now, draw is an abstract operation of the type shape.  However,
since it is abstract, it must be redefined for each child type of
shape.  An example is:

with Shapes;
package Circles is
  type circle is new Shapes.shape ...;  
   -- inherits primitive ops of shape
  procedure draw (x : circle); 
   -- overide draw, now circle is not abstract
end Circle;
This is also Alex Blakemore's definition I believe.  After this
definition, draw is a primitive operation of circle.

In order to get dispatching, we need to have an operand which is of
a class-wide type.  Alex's later example shows this.

with Shapes;
procedure process(x : Shapes.shape'class; y : Shapes.shape);
begin
    ...
    Shapes.draw(x); -- <-- This is a dispatching call
    Shapes.draw(y); -- <-- This is not a dispatching call, since
		    --     the type of y is not class-wide.
		    --     In fact, since Shapes.draw is abstract,
		    --     this call is not allowed.
    ...
end process;

The reason I have gone on at such length is that there is another wrinkle
here.  Inside of the body of the circular draw, the compiler assumes that 
the parameter x is actually a circle.  This is true even if the operation
was called through dispatching.  For example, imagine that we have:

    with Shapes; use Shapes;
    with Circles; use Circles;
    package ProcessGeometry is
    end Process Geometry;
    package body ProcessGeometry is
        circ_object : circle;
	    ...
    begin
        draw(Shapes.shape'class(circ_object)); -- dispatching
        draw(circ_object);			-- not dispatching
    end ProcessGeometry;

These two calls are to the same procedure, but the first is dispatching
and the second is not.  Inside of the body of circle's draw operation,
the object x is assumed to be a circle -- this is even if it is in a class
derived from circle.

We need a better example.  This one is somewhat contrived, and is probably
more long-winded than necessary, but it illustrates the main point.  
Imagine first that I have a Geometry manager package which has an operation 
to draw arbitrary circular arcs.  This operation is given by:
	package GeometryManager is
	    type Point is ...;
	    type Distance is ...;
	    type Angle is ...;
	    DrawArc(Center : Point; Radius : Distance;
		    StartAngle : Angle; EndAngle: Angle);
	end GeometryManager;


Imagine furthermore that I have several possible ways of representing circles. 
One is as a center and a radius.  Another is as three points which lie 
on the circle.  There may be more.  These representations will be child 
types of circle.

Now, we flesh out the implementation of circles somewhat.  The draw
routine of a circle will acquire the center and radius of the
circle and then call the GeometryManager's routine to draw a circular
arc.  The two representations of circles have different ways of
getting the center and radius.  In one they are there to look up,
and in the other they must be calculated using a standard formula.
In any case, the operations "calculate the center" and "calculate
the radius" are different operations for the different child types
of circle.  We make them dispatching operations.
with Shapes;
with GeometryManager;
package Circles is
    type circle is new Shapes.shape ...;
    procedure draw(x : circle);
    function center(x : circle) return GeometryManager.Point is <>;
    function radius(x : circle) return GeometryManager.Distance is <>;
end Circles;

package body Circles is
    procedure draw(x : circle) is
    begin
	--
	-- If we do some calculation using the center of x,
	-- we will get the non-dispatching operation here.
	-- For example, since the circular center is abstract,
	--         ..., center(x), ...
	-- would not be allowed.
	--
	GeometryManager.DrawArc(center(circle'class(x)),
			   --          ^  Notice the conversion
				radius(circle'class(x)),
			   --          ^  Notice the conversion
				Angle(0), Angle(360));
    end draw;
end Circles;

We want the operations center and radius to be dispatching. We must 
convert the parameter x to be of class wide type inside of the body of
draw.  Even though the compiler things that the type of x is circle, 
the run-time type of x is some child type of circle.  This is the
only way to get re-dispatching behavior.

The MRT has discussed this at length in the past.  Tucker believes that
it is important to have a syntactic marker to tell when a call is
dispatching.  An alternative rule would be the C++ rule, where all
dispatching operations dispatch always.  (Ada9X dispatching operations
are roughly equivalent to C++ virtual operations.)  This means that the 
compiler could not replace a dispatching call with a non-dispatching
call even if the compiler knows where the routine is going to go
anyway.  This is considered expensive.

					Peace,
					Bill White
Newsgroups: comp.lang.ada