Version 1.2 of acs/ac-00119.txt

Unformatted version of acs/ac-00119.txt version 1.2
Other versions for file acs/ac-00119.txt

!standard 8.3.1(0/2)          05-10-24 AC95-00119/01
!class Amendment 05-10-24
!status received no action 05-10-24
!status received 05-09-30
!subject Extending a primitive routine
!appendix

From: Randy Brukardt
Date: Friday, September 30, 2005  7:54 PM

After a discussion on comp.lang.ada, I posting this here to put it on the
record as an idea for Ada 2019.

The discussion was about extending (rather than replacing or inheriting)
routines. Several O-O languages (Common Lisp was given as an example)
provide some mechanism like that, and it is commonly needed in Ada. For
instance, Finalize routines usually need to call their parent when they are
completed, so that the parent finalization is accomplished. Failing to do
that (or doing it wrong) is a common source of errors in Ada code, and the
consequences can remain hidden for a long time.

It occurred to me that we already have the needed syntax hooks in Ada 200Y;
we're just missing the necessary keywords.

We have the "overriding" keyword to specify overriding. Additional keywords
could specify other types of extension -- "extends" perhaps. That would look
like:

    extends
    procedure Finalize (Obj : in out My_Type);

or perhaps

    extends after
    procedure Finalize (Obj : in out My_Type);

to specify when the parent routine is called. The parent routine would be
called just as the inherited one would have been at an appropriate place.

But I think that we'd only want to support calling the parent routine first
or last; special syntax for calling it in the middle hardly seems worth it
(and getting the parameters right then would be messy - calling the parent
last could be done with jumps, and first probably also could be done with
shared code).

I'm mildly sorry I didn't think of this when we were working on "overrides";
its a rather natural extension to the idea that also helps to prevent bugs.

****************************************************************

From: Tucker Taft
Date: Saturday, October 1, 2005  10:30 AM

If we really want to prevent bugs, it seems the declaration
really belongs on the parent type's routine, rather than the
child's routine.  Writing "overriding" instead of "extending"
seems just as easy a mistake to make as leaving out the
call on the parent routine.  On the other hand, if the parent
routine indicates that it *must* be called at some point from
the child's routine, then that would prevent the error from
occurring.

This makes me think that this would be just the right
thing for a pragma.  It is relatively rare, and adding it
only makes a legal program illegal, rather than vice-versa.
Something like "pragma Requires_Inclusion(Finalize);" on
the declaration of a primitive would require each descendant
that overrides the primitive to include a call on
their parent's primitive within the body of the overriding
routine, at a point that is executed exactly once on every call
that completes normally (i.e. it appears once on
every path to a "return", and not inside a loop).

****************************************************************

From: Bob Duff
Date: Saturday, October 1, 2005  1:30 PM

> If we really want to prevent bugs, it seems the declaration
> really belongs on the parent type's routine, rather than the
> child's routine.  Writing "overriding" instead of "extending"
> seems just as easy a mistake to make as leaving out the
> call on the parent routine.  On the other hand, if the parent
> routine indicates that it *must* be called at some point from
> the child's routine, then that would prevent the error from
> occurring.

I think that makes sense.

But I also think that before any of us starts designing such a feature,
we ought to look at how other languages do it.  The only one I know of
is CLOS (and Lisp Flavors, from whence it came).  Any others?

And I don't think anybody is in a hurry about this.  ;-)

> This makes me think that this would be just the right
> thing for a pragma.  It is relatively rare, and adding it
> only makes a legal program illegal, rather than vice-versa.
> Something like "pragma Requires_Inclusion(Finalize);" on
> the declaration of a primitive would require each descendant
> that overrides the primitive to include a call on
> their parent's primitive within the body of the overriding
> routine, at a point that is executed exactly once on every call
> that completes normally (i.e. it appears once on
> every path to a "return", and not inside a loop).

If we're going to do that, we ought also to change the rules for return
statements in functions.  Remove the rule requiring at least one return
statement.  Remove the run-time check for falling off the end.  Add a
rule saying there must be a return or a raise on every path.  Or a call
to a pragma No_Return procedure.

****************************************************************

From: Florian Weimer
Date: Tuesday, October 4, 2005  5:53 PM

> But I also think that before any of us starts designing such a feature,
> we ought to look at how other languages do it.  The only one I know of
> is CLOS (and Lisp Flavors, from whence it came).  Any others?

AOP has picked up some aspects (ahem) of method combination.

I like the pragma-based approach.  I'm a bit concerned that the
"extends" proposal doesn't really fit Ada because there appears to be
a tendency in the language to avoid magic calls to user-defined
subprograms.

****************************************************************

From: Tucker Taft
Date: Tuesday, October 4, 2005  6:10 PM

A somewhat more pleasant name for the pragma might be "essential".

That is:

     procedure Finalize(X : in out T);
     pragma Essential(Finalize);

This means that this operation is an "essential" primitive of T,
i.e. it is part of the "essence" of T, and if it is overridden,
it must called from within the overriding, exactly once
on every path that returns normally.

****************************************************************

From: Thomas Quinot
Date: Wednesday, October 5, 2005  3:31 AM

> But I think that we'd only want to support calling the parent routine first
> or last; special syntax for calling it in the middle hardly seems worth it
> (and getting the parameters right then would be messy - calling the parent
> last could be done with jumps, and first probably also could be done with
> shared code).

This seems unnecessarily rigid to me. An alternative might be to provide
an attribute allowing the body of a primitive operation to make a
reference to the overridden primitive of the ancestor type:

  procedure Finalize (X : in out T) is
  begin
     ...
     Finalize'Overridden (X);
     ...
  end Finalize;

This would allow calling the parent operation before, after, in the
middle of, or conditionally depending upon, additional processing in the
overriding one.

****************************************************************

From: Adam Beneschan
Date: Wednesday, October 5, 2005 10:19 AM

How would this be different from

    Finalize (Parent_Type(X));

or

    Parent_Type(X).Finalize;

?

I don't think that what's under discussion here is providing a way for
overriding routines to call overridden ones.  Ada has always had that
ability (going back to Ada 83!).  I believe we're talking about ways
to either make it *mandatory* for an overriding routine to call an
overridden one, or providing a way for the compiler to generate such a
call automatically.

****************************************************************

From: Randy Brukardt
Date: Wednesday, October 5, 2005  1:00 PM

Correct. The problem is forgetting to call the parent routine, which breaks
the abstraction for some operations (like Finalize). Ways to either prevent
or detect such a mistake is what we're talking about.

****************************************************************

From: Thomas Quinot
Date: Thursday, October 6, 2005  2:50 PM

The semantics would be exactly the same, but it would be expressed in
the derived operation in a way that would be independant of the specific
name and location of the parent type.

****************************************************************

From: Martin Krischik
Date: Friday, October 7, 2005  1:10 AM

In C++ they had a similar discussion and in the end they decided not to add a
new language feature since the problem can be solved with the features
allready there. And it is the same for Ada - only we have two options open:

   package Inherited renames Parent_Package;

or

  subtype Inherited is Parent_Package.Parent_Type;

Depending if you want call Package.Operator (Type) or Type.Operator. The first
option was even possible in Ada 95.

In C++ they have added a declaration like this into the private part of every
class.

****************************************************************

From: Randy Brukardt
Date: Monday, October 24, 2005  8:50 PM

> If we really want to prevent bugs, it seems the declaration
> really belongs on the parent type's routine, rather than the
> child's routine.  Writing "overriding" instead of "extending"
> seems just as easy a mistake to make as leaving out the
> call on the parent routine.  On the other hand, if the parent
> routine indicates that it *must* be called at some point from
> the child's routine, then that would prevent the error from
> occurring.

The "bugs" I was interested in preventing was writing the call incorrectly,
or failing to put it on every path. That's why I wanted it included
automatically.

I'm not much interested in "forcing" the call to be included, because there
are cases where you might not want to actually call the parent routine. It's
better to let the client programmer decide that. Moreover, its writing the
call itself that is error-prone, not just the omission of it.

> This makes me think that this would be just the right
> thing for a pragma.  It is relatively rare, and adding it
> only makes a legal program illegal, rather than vice-versa.
> Something like "pragma Requires_Inclusion(Finalize);" on
> the declaration of a primitive would require each descendant
> that overrides the primitive to include a call on
> their parent's primitive within the body of the overriding
> routine, at a point that is executed exactly once on every call
> that completes normally (i.e. it appears once on
> every path to a "return", and not inside a loop).

Determining that an explicitly written call is on the right path, is a call
to the parent operation, and has the same parameters, seems like a rather
difficult thing to do. It would seem to me to be very much like the return
statement fiasco.

Florian Weimer wrote:

> I'm a bit concerned that the
> "extends" proposal doesn't really fit Ada because there appears to be
> a tendency in the language to avoid magic calls to user-defined
> subprograms.

You haven't done much programming with controlled types, I take it. ;-)
There's a lot of magic calls there.

I'm primarily interested in having a way to automatically call the parent
routine (without the complication of writing it, with all of the
possibilities for error that entails, or insuring its on all of the possible
paths).

A more flexible and more explicit alternative would some syntax that stands
in for a call to the parent routine. Say:

         <routine-name>'Parent(<>)

(The box would be a stand-in for the parameter list, which would be passed
unmodified. Writing the parameter list is the worst cause of errors.)

In any case, it doesn't seem that we even have an agreement on what the
problem is, so it's pretty clear that we can't design a solution.

****************************************************************

From: Bob Duff
Date: Tuesday, October 25, 2005  9:03 AM

> Determining that an explicitly written call is on the right path, is a call
> to the parent operation, and has the same parameters, seems like a rather
> difficult thing to do. It would seem to me to be very much like the return
> statement fiasco.

Which fiasco are you referring to?

****************************************************************

From: Randy Brukardt
Date: Tuesday, October 25, 2005  2:28 PM

> Which fiasco are you referring to?

The "there must be one return statement" fiasco. That's a rule which does
almost nothing for correctness (you still need a runtime check), but it
causes trouble for prototyping.

We'd likely end up with a similar rule here, because writing a rule that all
paths must be covered would be difficult (what exactly does that mean?), and
because implementations generally do path analysis in an optional
optimization phase, so to base semantic rules on that would complicate
implementations.

****************************************************************



Questions? Ask the ACAA Technical Agent