Version 1.1 of acs/ac-00279.txt

Unformatted version of acs/ac-00279.txt version 1.1
Other versions for file acs/ac-00279.txt

!standard 3.9.2(2)          16-06-06 AC95-00279/00
!class Amendment 16-06-06
!status received no action 16-06-06
!status received 16-05-14
!subject Ada OOP and final statement
!summary
!appendix

From: Pavel Zhukov
Sent: Saturday, May 14, 2016  4:40 PM

During last refactoring I hit one problem (forth time) which I'd like to bring
here.

I'm not yet another Java programmer but I kind of like OOP in some situation.
During the refactoring I combined few "sublcasses" and introduced parent class.
The problem was I forgot to remove implementation of method from one child and
it simply overrode parent's one. I though introduction of method to protect
overriding ("final")  can solve this problem and make code more controllable. If
I understand correctly this can be done in compile time (compiler error if
"final" method was overridden) and don't bring any performance degradation.

Is it bad/good idea?

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

From: Tucker Taft
Sent: Sunday, May 15, 2016  4:56 PM

The GNAT compiler has a switch that might be relevant:

-gnatyO

     Check that overriding subprograms are explicitly marked as such.

     This applies to all subprograms of a derived type that override a primitive
     operation of the type, for both tagged and untagged types. In particular,
     the declaration of a primitive operation of a type extension that overrides
     an inherited operation must carry an overriding indicator. Another case is
     the declaration of a function that overrides a predefined operator (such as
     an equality operator).

---

The explicit "overriding" or "not overriding" annotation on a subprogram can
serve a somewhat similar purpose as "final."  Alternatively, if you declare the
original subprogram as "class-wide" then overriding never occurs.  That is, use
T'Class rather than T as the parameter type.  This is the most direct equivalent
of Java's "final."

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

From: Adam Beneschan
Sent: Sunday, May 15, 2016  5:10 PM

This sounds scary... "final" in Java simply means "nobody is allowed to override
this method", but there are no other changes in semantics.  But using T'Class as
a parameter instead of T seems like it could have many other semantic
ramifications.  Some _possible_ ramifications that come to mind are the impacts
on overload resolution, ability to use the subprogram as an actual for a generic
formal subprogram, accessibility level issues if it's an "access T'Class"
parameter, etc.  I don't know whether any of those are actual issues, but I'd
want someone to go over the RM with a fine-toothed comb before feeling certain
that one could make this simple change to achieve the effect of "final".

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

From: Tucker Taft
Sent: Monday, May 16, 2016  9:07 AM

> This sounds scary... "final" in Java simply means "nobody is allowed
> to override this method", but there are no other changes in semantics. ...

The question is: what was the original goal?  You are correct that Java's final
and Ada's "class-wide" operations are not identical, so a blind replacement of
one with the other would not make sense.  But this program is written in Ada to
begin with, and Ada's OOP is sufficiently different from Java's that you can't
ever just make blind substitutions.  For example, in Ada, a call from one
primitive operation to another of a given type is by default a statically bound
call, whereas in Java, such a call is by default dynamically bound, that is, it
effectively "re-dispatches" in Java.  This difference is one reason Ada's OOP
creates less coupling across the type hierarchy than Java's.

Class-wide operations in Ada are specifically designed for an operation that is
applicable to all types in the hierarchy, and which will not be overridden for
specific types.  That is solving essentially the same problem as "final."  But
you are certainly right that the solutions are not identical.

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

From: Randy Brukardt
Sent: Tuesday, May 17, 2016  6:14 PM

...
> I'm not yet another Java programmer but I kind of like OOP in some
> situation. During the refactoring I combined few "sublcasses" and
> introduced parent class. The problem was I forgot to remove
> implementation of method from one child and it simply overrode
> parent's one. I though introduction of method to protect overriding
> ("final")  can solve this problem and make code more controllable. If
> I understand correctly this can be done in compile time (compiler
> error if "final" method was overridden) and don't bring any
> performance degradation.

The problem here is with the default for overriding in Ada, which was necessary
for compatibility (since Ada 95 didn't have overriding indicators) but otherwise
is wrong (overriding should always be declared explicitly).

We had intended originally that there be a restriction such that overriding
would only be allowed if it was explicitly given with an overriding indicator.
No indicator would be an error, and the problem you noted about accidental
overriding couldn't happen. We didn't define that restriction mainly because we
couldn't decide how it should work with generic instantations.

Since GNAT has such a switch, perhaps we ought to revisit defining such a
restriction?? Virtually all Ada programs would be better off with such a
restriction in place, so it ought to be portable. (Ada has other "optional"
settings that really ought to be used in virtually all programs, like
Detect_Blocking. History prevents us from making these the default, sadly.)

"Final" to me is in the wrong place. The author of a library cannot really know
how a client is going to use it (Ada programmers are forever coming up with ways
to use Ada features in ways that the designers didn't anticipate). To restrict
how clients can use it is the height of hubris, and I really see no good reason
to encode that hubris in the language. (This is the same reason that pretty much
all "object" parameters to OOP subprograms should be "in out", even if "out"
isn't needed in the current version.)

It is the client that decides what needs to be overridden. Refactoring doesn't
really change that (even though the client and the root are being written at the
same time).

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

From: Christoph Grein
Sent: Wednesday, May 18, 2016  2:49 AM

> (This is the same reason that pretty much all "object" parameters to
> OOP subprograms should be "in out", even if "out" isn't needed in the
> current version.)

Hm, I like the indictation of data flow direction, although tagged parameters
are transferred via reference. The Rosen (Jean Pierre?) trick, however,
undermines this with selfreferencing objects providing variable views to
constants. Ada 2012 has made this legal for all limited  and tagged objects (for
in-parameters, this trick has long been in use). Thus constant is lie for
limited private objects. One more of the few places where Ada lies.

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

From: Randy Brukardt
Sent: Wednesday, May 18, 2016  2:27 PM

> > (This is the same reason that pretty much all "object" parameters to
> > OOP subprograms should be "in out", even if "out" isn't needed in
> > the current version.)
> Hm, I like the indictation of data flow direction, although tagged
> parameters are transferred via reference.

That's my point, actually: you cannot know the data flow that makes sense for
your clients. You know the data flow that exists for your actual routine, but
extenders may be different. Reducing what an extender might do is bad.

That happened to use repeatedly during the development of Claw. And of course,
for Ada 95, we couldn't fix it for functions.

For instance, we had an extension of an edit box that included a buffer. The
buffer needs to be updated even after reading operations, so we had to change
various parameters to be in out.

Another example is that the iterator parameters in the Ada 2012 iterator
interfaces are "in". That makes it difficult to include state in the iterator
object, which has made using the iterators much less flexible than intended.
It's bad enough that there are now proposals on the table which would avoid
using the iterator interfaces altogether.

> The Rosen (Jean Pierre?) trick, however, undermines this with
> selfreferencing objects providing variable views to constants. Ada
> 2012 has made this legal for all limited  and tagged objects (for
> in-parameters, this trick has long been in use).
> Thus constant is lie for limited private objects. One more of the few
> places where Ada lies.

That's the "work-around" to making the parameters "in out" in the first place.
It makes the code unnecessarily complicated, and of course, not everyone knows
about it.

Since "in" is a lie anyway, why even use it for controlling [tagged] parameters?
All that does is make it harder for extenders.

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

From: Christoph Grein
Sent: Thursday, May 19, 2016  6:38 AM

> ...
>
> Another example is that the iterator parameters in the Ada 2012
> iterator interfaces are "in". That makes it difficult to include state
> in the iterator object, which has made using the iterators much less
> flexible than intended. It's bad enough that there are now proposals
> on the table which would avoid using the iterator interfaces altogether.

Hm, although this would mean an incompatibility, wouldn't it then be a good idea
to change the iterator interface parameter to in out?

Especially so as this is quite new and there is only on Ada 2012 implementation
around.

I guess this would be only a slight incompatibility. (There were more severe
ones, e.g. changing Ada 95's return by reference to build in place.)

>> The Rosen (Jean Pierre?) trick, however, undermines this with
>> selfreferencing objects providing variable views to constants. Ada
>> 2012 has made this legal for all limited  and tagged objects (for
>> in-parameters, this trick has long been in use).
>> Thus constant is lie for limited private objects. One more of the few
>> places where Ada lies.
> That's the "work-around" to making the parameters "in out" in the
> first place. It makes the code unnecessarily complicated, and of
> course, not everyone knows about it.
>
> Since "in" is a lie anyway, why even use it for controlling [tagged]
> parameters? All that does is make it harder for extenders.

Hm, I understand all these arguments but am sure opinions vary widely in the Ada
community. And let me cite from the famous McCormick paper Software Engineering
Education: On the Right Track: "Parameter modes that reflect the problem rather
than the mechanism." This was one of the features why Ada succeeded while C
failed. (Sadly, no one believes it and Ada is forever constrained to a niche
market.)

But this discussion has strayed far from the original point: Final.

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

From: Randy Brukardt
Sent: Thursday, May 19, 2016  1:36 PM

> Hm, although this would mean an incompatibility, wouldn't it then be a
> good idea to change the iterator interface parameter to in out?
> Especially so as this is quite new and there is only on Ada
> 2012 implementation around.
> I guess this would be only a slight incompatibility. (There were more
> severe ones, e.g. changing Ada 95's return by reference to build in
> place.)

I agree, but some people dislike having to think in terms of cursors at all, so
they would like an even more abstract way to iterate. Ergo, more proposals in
that area. (Moral: one size, no matter how flexible, does not fit all.)

...
> > Since "in" is a lie anyway, why even use it for controlling [tagged]
> > parameters? All that does is make it harder for extenders.
> Hm, I understand all these arguments but am sure opinions vary widely
> in the Ada community.
> And let me cite from the famous McCormick paper Software Engineering
> Education: On the Right Track: "Parameter modes that reflect the
> problem rather than the mechanism." This was one of the features why
> Ada succeeded while C failed. (Sadly, no one believes it and Ada is
> forever constrained to a niche market.)

John of course is right for *non-controlling* parameters, which are the vast
majority of them. It is only the controlling parameters (and not non-primitive
parameters or class-wide parameters) where one has to worry about future
extenders. (For the other parameters, a change in mode generally means that the
routine does something different and shouldn't be considered the same and thus
shouldn't be overriding anyway.)

> But this discussion has strayed far from the original point: Final.

So let's bring it back. To me, "final" might make sense for types rather than
subprograms, because there are types that it doesn't make sense to extend. It
also avoids the semantic problem with "final": if the routine is going to be
inherited by child types, then it clearly should have been class-wide (which
allows it to properly adjust to extensions). [As Tuck points out, Java "final"
plays the role of class-wide routines in Ada, since Java always dynamically
binds calls.] Otherwise, it is likely to inappropriately implement the extension
(as it will not call the child type's enhanced operations). If the routine isn't
inherited, then of course we have the problem of dispatching to it - where does
it go? If, of course, there are no extensions, then there are no problems. (And,
as a corollary, you can use any mode you like. :-)

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

From: Christoph Grein
Sent: Friday, May 20, 2016  6:34 AM

> I agree, but some people dislike having to think in terms of cursors
> at all, so they would like an even more abstract way to iterate. Ergo,
> more proposals in that area. (Moral: one size, no matter how flexible,
> does not fit all.)

What can be more abstract than Ada 2012's

   for Element of [reverse] Container loop

(Yes , the cursors are still there but need not be written.)

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

From: Randy Brukardt
Sent: Friday, May 20, 2016  10:19 PM

Some iterations don't easily lend themselves to having a cursor at all; while
the user of the abstraction need not see cursors, the creator of the abstraction
has to deal with them in Ada 2012 even if they don't make sense.

(I'm not particularly convinced, but that's the argument.)

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

From: Cyrille Comar
Sent: Friday, May 20, 2016  4:43 AM

>>> (This is the same reason that pretty much all "object" parameters to
>>> OOP subprograms should be "in out", even if "out" isn't needed in
>>> the current version.)
>> Hm, I like the indictation of data flow direction, although tagged
>> parameters are transferred via reference.
> That's my point, actually: you cannot know the data flow that makes
> sense for your clients. You know the data flow that exists for your
> actual routine, but extenders may be different. Reducing what an
> extender might do is bad.

Let me challenge politely this last statement... reducing the among of errors
people can do is 'good', right? Liskov principle shows that ir is not only
better but necessary to reduce what extenders could do in order to give a
reasonable semantics to dispatching calls...

Your example is telling: changing the parameter passing mechanism of a primitive
operation is a change of contract between callers and callee and cannot be done
casually while defining a new extension. It requires, at least,  a careful
review of all the existing dispatching calls to see if the environment was not
taking advantage of the declared parameter passing mechanism (e.g. using a
constant as an actual). So it is good that the language reduces what an extender
can do on its own and pushes the "extender" to become a "global modifier" when
what he wants to do locally can have significant non-local consequences...

I undertand your point that it would have been better for you, in the context of
Claw, to only use in-out for controlling parameters. But this is only your
choice as a designer and it is not clear that it is always the best choice: in
effect, in order to open the possibilities of extension in the future (by making
the controlling parameter in out) you create specific obligations to all current
users (e.g. you cannot call your primitives on constants).... so you make a
specific choice in the balance of interest between callers and callee... as soon
as you have a balance, there is no 'good' or 'bad' anymore but all gradations
between the two...

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

From: Randy Brukardt
Sent: Friday, May 20, 2016  10:34 PM

> Let me challenge politely this last statement... reducing the among of
> errors people can do is 'good', right?

Surely. But in this case, the Rosen technique makes it just as possible for
people to make the "errors". So all you're doing is giving your readers a false
impression.

> Liskov
> principle shows that ir is not only better but necessary to reduce
> what extenders could do in order to give a reasonable semantics to
> dispatching calls...

Sure, with preconditions and constraints and predicates. The parameter mode (in
Ada at least) is a fraud though; it's always effectively "in out" since it isn't
the least bit hard to modify the object (just use the Rosen technique in an
extension if the original author thought "in" meant something). There is no way
for the author of a tagged type to assume that any object is unchangable (since
any extender could make it a variable). (Unless, bringing this discussion back
on topic, Ada had a type final which prevented extensions. :-)

> Your example is telling: changing the parameter passing mechanism of a
> primitive operation is a change of contract between callers and callee
> and cannot be done casually while defining a new extension. It
> requires, at least,  a careful review of all the existing dispatching
> calls to see if the environment was not taking advantage of the
> declared parameter passing mechanism (e.g. using a constant as an
> actual). So it is good that the language reduces what an extender can
> do on its own and pushes the "extender" to become a "global modifier"
> when what he wants to do locally can have significant non-local
> consequences...

It's unfortunate that Ada even allows nominally constant tagged objects, because
they're never really constant. That confuses the heck out of the uninitiated
(and certain ARG members).

> I undertand your point that it would have been better for you, in the
> context of Claw, to only use in-out for controlling parameters. But
> this is only your choice as a designer and it is not clear that it is
> always the best
> choice: in effect, in order to open the possibilities of extension in
> the future (by making the controlling parameter in out) you create
> specific obligations to all current users (e.g. you cannot call your
> primitives on constants).... so you make a specific choice in the
> balance of interest between callers and callee... as soon as you have
> a balance, there is no 'good' or 'bad' anymore but all gradations
> between the two...

I read the above as you actually agreeing with me, since there is no such thing
as a truly constant tagged object. :-) Anything that discourages using "tagged
constants" is a good thing, as it makes it much less likely that some reader
will expect properties that don't exist. And you can't "reason" in a dispatching
call based on a "constant" parameter, because any extension can change it
whether or not the mode says it can. So why lie to readers?? Why make it harder
for extenders??

(If we were starting from scratch, this probably would be different, either with
no Rosen trick [so constants of tagged types really would be possible], or no
declared constants of tagged types (so no "in" mode, no keyword constant, etc.).
Too late for that, though.)

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

From: Christoph Grein
Sent: Monday, May 23, 2016  6:10 AM

In my last Ada course for advanced programmers, I got big eyes, protests and
what not, when I told the attendants about nonconstantness of constants. One of
them held the strong opinion that there must be something wrong if one needs
recourse to tricks like Rosen's. There's not much I can say against it.

Why is the mode in out for Open and Close, but in for Read and Write on files?
For an answer, one would have to exhume Jean Ichbiah, I think.

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

From: Tucker Taft
Sent: Monday, May 23, 2016  7:36 AM

> In my last Ada course for advanced programmers, I got big eyes,
> protests and what not, when I told the attendants about nonconstantness of constants.

Note that this only applies to objects where there is necessarily a variable
view at some point in the life of the object, e.g. during Initialize and
Finalize (for controlled) or at the point of the type definition (for limited).
If we had a good way of preventing this variable view from being retained, we
might have tried to do that.  But that is very hard given the ability to
"squirrel away" such a view.  So we tried to "circumscribe" it as much as
possible.  Note also that we didn't create this problem in Ada 2012, but rather
documented it so programmers would be aware of it.

> ... One of them held the strong
> opinion that there must be something wrong if one needs recourse to tricks
> like Rosen's. There's not much I can say against it.
>
> Why is the mode in out for Open and Close, but in for Read and Write
> on files? For an answer, one would have to exhume Jean Ichbiah, I think.

This reflects a design where the (internal) File object's "state" represents the
open-ness of the File object, rather than the content of the (external) file.
Agreed that this choice is debatable, but it is consistent across the I/O
packages.

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

From: Christoph Grein
Sent: Tuesday, May 24, 2016  5:18 AM

> Note that this only applies to objects where there is necessarily a variable
> view at some point in the life of the object, e.g. during Initialize and
> Finalize (for controlled) or at the point of the type definition (for
> limited).

Constant objects of this type are mutable at any time:
type Rec is new Ada.Finalization.Controlled with record
  Ref: access Rec;  -- variable view; Initialize produces self-reference
  -- other components
end record;

> This reflects a design where the (internal) File object's "state" represents
> the open-ness of the File object, rather than the content of the (external)
> file.  Agreed that this choice is debatable, but it is consistent across the
> I/O packages.

But the read and write pointer is not a part of the external object.

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

From: Tucker Taft
Sent: Tuesday, May 24, 2016  8:17 AM

> Constant objects of this type are mutable at any time:
> type Rec is new Ada.Finalization.Controlled with record
>   Ref: access Rec;  -- variable view; Initialize produces self-reference
>   -- other components
> end record;

This is consistent with what I tried to say, namely that if you have a variable
view at some point in the life of an object, you can "squirrel away" that view
for later use. Clearly this doesn't happen "by mistake."  The programmer is
doing this on purpose.  This is vaguely analogous to the ability to "cast away
const-ness" in C, though hopefully a bit more disciplined (or less, depending on
your point of view!).

>>> ... Why is the mode in out for Open and Close, but in for Read and
>>> Write on files? For an answer, one would have to exhume Jean Ichbiah, I think.
>> This reflects a design where the (internal) File object's "state"
>> represents the open-ness of the File object, rather than the content
>> of the (external) file.  Agreed that this choice is debatable, but it is consistent across the I/O packages.
> But the read and write pointer is not a part of the external object.

As I indicated, a choice was made to consider the "open-ness" of the object the
only relevant part of the state reflected in the File parameter.  You are right
that the read and write pointer seem to be in limbo between the File parameter
and the external file. The presumed implementation model implies a level of
indirection, where the File parameter represents a reference, which is
essentially "null" when the file is closed, and non-null when the file is open.
Everything else is buried in some mysterious netherland whose state is not
reflected in the mode of the File parameter.  A similar approach was taken for
task-type parameters, where you can do pretty much anything to an IN parameter
of a task type.

I agree that this model has problems, but I would argue that it is a symptom of
any language that has pointer parameters whose mode has no effect on the ability
to update the pointed-to object.  Personally I am on the side of having a
language with no explicit pointers in the surface syntax or semantics, but that
is a very different approach!

Also note that we added access-const in Ada 95, but that has its own issues, in
that the mode of access is buried in the type, rather than the parameter mode.
Not an ideal place for it in cases like file handles.

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

From: Robert Leif
Sent: Tuesday, May 24, 2016  12:30 PM

In development of CytometryML (http://www.cytometryml.org/) in XML Schema
Definition Language (XSD) 1.1, my style is fake the creation of datatypes in an
Ada specification. The XSD1.1 restriction is roughly equivalent to a generic.
XSD also has range checking. Can you create a generic instead of using a
pointer?

Parenthetically, one does not appreciate how good a job the Ada community is
doing, until one works with other communities. For instance, XHTML5 does not
really exist. There is no simple means of using XML datatypes in an "XHTML 5"
web page. The Ada error messages in the GNAT Ada compiler were much more helpful
than the error messages in XSD validators.

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

Questions? Ask the ACAA Technical Agent