From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,6b6619eb9cada212 X-Google-Attributes: gid103376,public From: Tucker Taft Subject: Re: Help me to chose between ADA 95 and C++ Date: 1999/12/14 Message-ID: <38567806.FD1F4232@averstar.com> X-Deja-AN: 560593897 Content-Transfer-Encoding: 7bit Sender: news@inmet.camb.inmet.com (USENET news) X-Nntp-Posting-Host: houdini.burl.averstar.com References: <01bf37fb$a91afb60$0564a8c0@IS-D2D04C.test> <829rbv$a8m$1@nntp6.atl.mindspring.net> <01bf3e32$0b9dc880$022a6282@dieppe> <385112AE.7E2CFA9@rdel.co.uk> <3855e3cd_1@news1.prserv.net> <38561A6C.5DE3D901@rdel.co.uk> X-Accept-Language: en Content-Type: text/plain; charset=us-ascii Organization: AverStar (formerly Intermetrics) Burlington, MA USA Mime-Version: 1.0 Newsgroups: comp.lang.ada Date: 1999-12-14T00:00:00+00:00 List-Id: Chris Powell wrote: > > Matthew Heaney wrote: > > > > In article <385112AE.7E2CFA9@rdel.co.uk> , Chris Powell > > wrote: > > > > > I would not recommend Ada 95 for OO development. Okay, it has all the > > > benefits of Ada 83 for type safety, etc, but the syntax of its class > > > programming constructs seems to make the code long winded, obscure and > > > error prone. I can give examples if anyone is interested/disagrees. > > > > Please do so, especially any examples that you think are "error prone." > > > > Perhaps there is just a misunderstanding about how to properly declare a > > type. > > Okay, this is where I find out that I have been getting it wrong for the > last year! But here goes: Thanks for taking the time to post these examples. I will give some of the rationale for the choices made... > package body Example is > > procedure Dispatching_Method (This : access Object) is > begin > -- This call correctly redispatches to Another_Dispatching_Method > Another_Dispatching_Method (This => Object'Class > (This.all)'Access); > > -- This (probably incorrectly) calls this object's > -- Another_dispatching_Method directly, without dispatching > Another_Dispatching_Method (This => This); This choice of "static" over "dynamic" binding was very explicit in the language design. By making static binding the default, you minimize unexpected coupling between a parent type and a derived type. I'll say more below... > end Dispatching_Method; > > procedure Class_Method (This : access Object'Class) is > begin > > -- This call correctly dispatches, since this is already > -- classwide in this case > Another_Dispatching_Method (This => This); > > end Class_Method; > > procedure Another_Dispatching_Method (This : access Object) is > begin > null; > end Another_Dispatching_Method; > > end Example; > > It has proven to be a common mistake to neglect to convert an object to > its class wide type before attempting a redispatch. I would have said that a common mistake in most OO languages is to use dynamic binding between dispatching methods when static binding would have been better. This will not happen in Ada 95 because static binding between dispatching methods is the default, and you have to work a little harder and be explicit if you want redispatching. > Passing 'This' as an access parameter is common (since references to > objects are often stored by an object, but further complicates the > syntax. > > By contrast, C++ provides redispatching as the default behaviour which > makes more sense to me. If a function is declared virtual (ie > dispatches) you would normally want dispatching to occur and then have > to use special syntax to prevent dispatching. In Ada, it is the other > way round. Here we have a difference of opinion. I agree when calling from the "outside" use want dispatching to occur, and that is what happens in Ada (and C++). However, when calling from one dispatching operation to another, static binding has a lot of advantages. In particular, if you inherit one operation and override the other, the one you inherit continues to work as it used to, rather than having some unspecified change in behavior due to redispatching. The critical point to recognize is that any redispatching within a dispatching operation affects the *external* behavior of the operation upon inheritance. So you can't inherit an operation as a black box if it has internal redispatching. You basically have to look at the source of the inherited operation (i.e. this means "white box" reuse), or have unusually good documentation on exactly what redispatching is happening inside and when. I remember attending a panel session at OOPSLA several years ago where essentially all of the panel members were griping about the difficulties of doing maintenance and enhancement on class libraries when users are taking advantage of this internal redispatching (also called "self dependencies"). Users were overriding some but not all of the operations, and then relying on this overriding to subtly affect the semantics of the inherited operations. When the next version of the class library came out, these users were very annoyed if the subtle effects were different. This problem would not occur in a language like Ada 95 where the default binding between dispatching operations is static. The developer of the class library would only use redispatching if they felt it were part of the *specification* of the operation, and hence would be intending to document it. By contrast, in C++, Java, Eiffel, Smalltalk, etc., redispatching is the default (and except for C++, static binding is not even an option), so these kinds of self dependencies are produced everywhere and anywhere there are calls between dispatching operations. Quite often these were calls made out of convenience of implementation of the operation, not out of an intent to create a subtle linkage between the operations from an inheritance point of view. So it is the collective experience of class providers like these that reconfirmed (at least for me) our decision to make static binding the default, while still providing a way for the programmer to redispatch if that is what the specification of the routine requires. > ... > This is one example, perhaps a bit verbose. I can try and think of more > if anyone is interested, or please set me straight! > > Other Ada quibbles: > > C++ encapsulates methods and data in a single structure. In Ada, class > methods are defined outside the object type so do not appear to 'belong' > to the type. This is clearly a matter of taste and background. The notion of an abstract data type, like "complex numbers" and a set of operations defined along side them has a long history. Ada 83 was built around this notion of abstract data type, where a type and its operations were all defined in a package, where the package provided the encapsulation. Ada 95 was designed to allow the extension of abstract data types, providing both inheritance between abstractions and/or their implementations, and polymorphism between multiple implementations of an abstraction. This preserves the heritage of "abstraction" orientation, while providing the extensibility and dynamism associated with the object-oriented model. It also means that binary operations are very natural in Ada 95, whereas they are awkward and somewhat arbitrarily asymmetric in languages like C++ and Java (e.g. the nasty need to write "if S1.equal(S2) {" in Java, rather than "if S1 = S2 then" in Ada 95, or the awkward connection between operator "friends" in C++ and the "associated" class). Admittedly, Ada 95 is a bit less natural in the heavily "state"-oriented model, where all operations "do things" to an object, and is a bit more natural in a "value"-oriented/functional model where (abstract) operations take in one or more values of a given ADT and produce a result. Personally the problem I have with the "state"-oriented model is for various operations that have two or more objects involved, and it is not at all obvious which one is the "primary" object. With the symmetric Ada (and Common Lisp Object System/CLOS) approach, they are all operands of the operation, and no unique "primary" object need be identified. > Having to explicitly pass the object instance as a parameter (the > implicit 'this' in C++) further separates the logical association > between methods and objects. Again, the C++ syntax is useful for clearly "one-primary-operand" operations, and somewhat arbitrary for "two-or-more-significant-operand" operations. > I am a Multiple Inheritance fan, because I think I know how to use it > properly. I do not believe it (always) indicates a bad design, as some > do. Ada 95 has some constructs to allow different types of MI, but not > all provided by the more general approach of C++, and again, the syntax > is obscure involving tagged types within generics, access discrimiated > records, etc. In C++ you simply define an X as a Y and a Z. In Ada, the > syntax would have been (if allowed): > > type Object is new X.Object and new Y.Object with record .... We certainly debated multiple inheritance. I think we were reluctant to build in syntax for a feature that seemed in some cases a solution looking for a problem. Also the fact that the designers of C++ couldn't decide between "virtual" and "non-virtual" base classes, and felt obliged to support both, and that many Eiffel uses of multiple inheritance seemed to represent "uses" rather than "is-a" relationships, made us feel that multiple inheritance was still in an awkward stage of development. The one thing I would add now to Ada 95 would probably be something more akin to the Java multiple inheritance of interfaces. Inheriting implementations from multiple parents seems quite error prone, but inheriting interfaces is pretty clean. However, there is still the annoying name clash possibility, where two interfaces happen to use the same name for otherwise unrelated methods. You also can't implement an interface in multiple ways with a single type, which could be useful for things like the "observer" pattern where a single object wants to observe multiple other objects. For Ada 95, a solution built more around the notion of explicitly implementing an interface, using something like the syntax of a generic instantiation, would give more flexibility and power (including multiple implementations of the same interface), while avoiding the annoyances associated with name clashes. We have begun moves toward an Ada 200X, so some ideas like these may emerge... > > Comments? See above. > > Chris. -- -Tucker Taft stt@averstar.com http://www.averstar.com/~stt/ Technical Director, Distributed IT Solutions (www.averstar.com/tools) AverStar (formerly Intermetrics, Inc.) Burlington, MA USA