!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. ***************************************************************