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,FREEMAIL_FROM autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,56131a5c3acc678e X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 2003-11-26 09:58:55 PST Path: archiver1.google.com!news2.google.com!news.maxwell.syr.edu!c03.atl99!news.webusenet.com!diablo.voicenet.com!cycny01.gnilink.net!cyclone1.gnilink.net!peer01.cox.net!cox.net!border3.nntp.aus1.giganews.com!intern1.nntp.aus1.giganews.com!nntp.giganews.com!nntp.comcast.com!news.comcast.com.POSTED!not-for-mail NNTP-Posting-Date: Wed, 26 Nov 2003 11:58:53 -0600 Date: Wed, 26 Nov 2003 12:58:52 -0500 From: "Robert I. Eachus" User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax) X-Accept-Language: en-us, en MIME-Version: 1.0 Newsgroups: comp.lang.ada Subject: Re: Question about OO programming in Ada References: In-Reply-To: Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit Message-ID: NNTP-Posting-Host: 24.34.214.193 X-Trace: sv3-h65SwtkTqWT7jpPaqKMk7dm4iB8XpJgNu1Z9H+JaxJm1QaqM+aHfzQA24hpfGHC+OCDBdklr93r9sx0!pOSqOxj7v3g90H/cQRKx0+7HUoGpf28mqdKFQZmWUEzOcWrz7Eg7+nxCTS2A4Q== X-Complaints-To: abuse@comcast.net X-DMCA-Complaints-To: dmca@comcast.net X-Abuse-and-DMCA-Info: Please be sure to forward a copy of ALL headers X-Abuse-and-DMCA-Info: Otherwise we will be unable to process your complaint properly X-Postfilter: 1.1 Xref: archiver1.google.com comp.lang.ada:2969 Date: 2003-11-26T12:58:52-05:00 List-Id: Ekkehard Morgenstern wrote: > Ok. What I'd like to know is if I can view Ada record types as classes of > objects. Yes. In Ada every type is a member of one or more classes of types. But for dispatching, read on. > I.e. if I declare a record, like > > type My_Type1 is > record > Field1 : Integer; > Field2: Integer; > end record; > > Can I extend the record type like > > type My_Type2 is new My_Type with > record > Field3 : Integer; > Field4 : Integer; > end record; > > such that the procedures declared to operate on type My_Type1 will operate > on the My_Type1 fields of My_Type2. To extend a record type, the parent type needs to be tagged. Of course, the parent can be explicitly derived from a third tagged type, and so on. The rule is that to be tagged, a non-derived type must include the reserved word tagged in its declaration, and a derived type must include a extension part, which may be null. > I gathered from what I read that I need to declare My_Type1 as a tagged > type. See above. > Now, I read that method dispatching (with static dispatching corresponding > to method overloading in C++, and runtime dispatching corresponding to > virtual methods in C++), would be possible only by using a class-wide type, > such as My_Type1'Class in the first parameter of a procedure or in the > return value of a function. This may be correct for C++ but it is definitely not correct for Ada. In Ada, a primitive operation can be dispatched to if it has a parameter or return type that is a tagged type. If there is more than one such parameter or result type, they must all resolve to the same specific type at the point of the call. If you want to write a dispatching operation that takes more than one tagged operand, and of different specific types you have to select one such parameter as the one that you dispatch on. The non-dispatching parameters or result type must be class wide. Inside the operation you can further dispatch on the classwide arguments. That is complex enough to deserve an example. Let's say you want to write a registration system for your state's DMV. For driver's licenses, you have an operand of type Person. To allow for special processing in some cases, you decide to make this type tagged. Since you don't want multiple copies of the same person in the system, let's make Person limited: type Person is tagged limited private; To deal with the complexities of registering truck, motorcycles, etc., you create a vehicle type: type Vehicle is tagged limited private; Of course, we need a registration type which will also have several variants, and you decide to make this type tagged as well: type Registration is tagged limited private; Since I made these limited, there will be a need for access types so that for example a registration can contain a vehicle reference and an owner reference: type Person_Ref is access all Person'Class; type Vehicle_Ref is access all Vehicle'Class; type Registration_Ref is access all Registration'Class; Whew! Got all that out of the way. Incidently, even though the _Ref types are needed in the actual record declarations, there is not necessarily a need for the user of the abstractions to be aware of the types. My usual habit is to put them in the private part to start, and move them out later if necessary. Now we can start to declare our registration function: function Vehicle_Registration (Owner: in out Person; The_Vehicle: in out Vehicle) return Registration; The compiler promptly slaps our hand. You can't have a function that dispatches on three separate classes. But in reality, that is not really what we wanted anyway. Let me work this both forward and backward. First, we don't really want to register Vehicles, we want to register cars, trucks, motorcycles, buses, boats, etc. And there may be "generic" people, but the base types for Vehicles and Registrations should be abstract. type Vehicle is abstract tagged limited private; type Registration is abstract tagged limited private; (It is sort of amazing to me that you often use one or two of abstract, tagged, limited, or private in a declaration, but if you need three, you usually end up adding the fourth.) Now in our base package we can see that we want an abstract function for registration. The type of registration returned will be determined within the specific function dispatched to, and any special cases involving people can be handled by dispatching within the actual function. So what we really wanted was: abstract function Register(Owner: in out Person'Class; The_Vehicle: in out Vehicle) return Registration'Class; Perhaps when you finish, you will end up returning a Registration_Ref, but so far it isn't necessary to make that type public. (Notice that you can always write Register(Someone, Car)'Access, if you really need to get an access value to assign to a Registration_Ref, or in some cases Register(Someone, Car)'Unchecked_Access.) Later you will write (child) packages to deal with cars, trucks, buses, and so on. In cars you will have: function Register(Owner: in out Person'Class The_Car: in out Automobile) return Registration'Class; You know that you will always return an Automobile_Registration when called with an Automobile unless there is a separate Temporary_Automobile_Registration. (You haven't decided on that issue yet.) But AT THE POINT OF THE CALL to the abstract Register, it is not known at compile time what type of Registration will be returned. There is a lot to learn in the above. Notice that we have used classwide parameters or return values for two different reasons in the declaration of Register above. The first occurs in Owner, where the owner may be any type derived from Person, and the Register function may not need to use internal dispatching on the parameter. Or, for example, registering a motorcycle may require that the Owner have a license to ride motorcycles. This can be determined by nested dispatching, or more usually by "if Owner in Motorcycle_Driver'Class then..." But the second instance, the use of a classwide result type is more interesting. This is not to cause dispatching, but to actually return an object whose class is not known at compile time. A typical usage is: procedure Something (...) is The_Registration: Registration'Class := Register(The_Owner, The_Vehicle); begin -- further operations on The_Registration, which may involve -- dispatching calls. end Something; > First of all, how do I accomplish returning a reference in Ada? What is the > default behaviour of parameter passing and return in Ada? Someone said here > that if I use an "in" parameter in a procedure or function definition, the > object will be passed by reference. What about the returing of objects? Are > they returned by copy or returned by reference? Whether an object is passed by value, value-return, or reference depends on the object's class, not on whether the parameter is in, in out, or out. In parameters can be referenced but not changed, in out parameters can be referenced and changed, and for out parameters the attributes are defined at the point of the call, but there may not be an initial value. There are tricks you can play in Ada to modify in parameters, and of course, if you have an in access value, you may be able to modify the value designated. (The may is because the view may be limited.) In general, whether a parameter is in, in out, or out is decided to reflect the expectations that a user of the subprogram should expect. > If I use a class-wide type, like My_Type1'Class, what kind of object is > that? Is it similar to Java's "object.class"? An object of a class-wide type always has a specific type. It is just that the type may not be determined until the object is created at run-time. Of course, if a class-wide object declaration is nested inside a subprogram, the type of the class-wide object--or any class-wide parameters--can change each time the subprogram is called. > What's the difference between passing a class-wide type as an in/out > parameter of a procedure, and passing an access to it? > > i.e. the difference between > > procedure Proc( Param : in out Type'Class ); > > and > > type Type_Class_Access is access Type'Class; > procedure Proc( Param : in Type_Class_Access ); Not much. Or rather, using the second form unnecessarily is a sign that you are new to Ada. There are cases where the access parameter won't cause any additional difficulties, and other cases where it will mean that you need to explicitly free the storage to avoid memory leaks. If you pass objects instead of pointers, you eliminate the potential memory leaks. This doesn't mean you shouldn't use pointers (access types) where appropriate. But in Ada a good rule is that you only use explicit access types to create data structures. And since you normally use abstract data types to implement these structures, the notation is limited to the private parts and bodies of a few packages. (And you probably chose those packages from a library instead of "rolling your own.") But sometimes you need to create your own interlinked data structures, as in the example of Persons, Vehicles, and Registrations. In those cases, you can use different data structure packages, written as generics to implement some of the data structures you need. Incidently, this is my problem with a set of data structure packages as a part of Ada. There are several ways to implement these structures in Ada, and which one you want to use depends on intimate details of the problem you are trying to solve. So a limited set of official solutions IMHO would be worse than the current situation. (A core set of data structure packages in the standard, plus an indication that compiler vendors and others are encouraged to extend the set is a different story.) > I think I know what run-time dynamic dispatching, class-wide types and > derived types refer to, but I'm not sure about implicit type conversions > that I can use. Don't cheat yourself. It is possible to subset Ada's support for object-oriented programming to match what you currently know about the subject from C++ or some other OO languages. But the Ada model is much, much richer than that. A lot of this richness involves information hiding. In Ada you constantly find yourself asking two questions: What is the minimum required information to use this subprogram, data-type, abstraction, and so on? and: Is there a different way to express this which allows more information hiding? Why this emphasis on information hiding? The converse of information hiding is coupling. The more coupling you have, the more bugs you have, the bugs are harder to find, and fixing bugs is more likely to create additional bugs. So maximizing information hiding is the easy way to bug free programming. > Like, I'd want to do something similar like this: > > C := access A; > > In C++, I can just write " C = &A" (if C is a pointer to B). How do I do > that in Ada? C := A'Access; > I tried to use the pointer-like semantics of access to class-wide types, but > I ended up with doing manual type conversions (or casting) all over. Is that > normal? Yes. Ada is trying to tell you something. In Ada the intent is that cleaner means better. If you are converting back and forth all over the place, it means that your programming model is not well thought out. Note that if you really are programming in a scope where you need to do such conversions, you can declare your own conversion functions, or overload subprograms to take both types of operands: type Bar_Ref is access Bar; ... procedure Foo(in out Bar_Ref) is begin Foo(Bar'Access); end Foo; pragma Inline(Foo); But again, if you want or need something like that, it is usually an indication of a design problem. > I thought by using this newsgroup, I could be spared from reading in the Ada > 95 Rationale. Which is very explicit, but also verbose, and I'd like to make > my learning process quicker. ;-) Quicker is possible, but tough. We used to say that learning Ada was 5% learning syntax and 95% learning software engineering. Then along came Ada 95. It cleaned up a lot of special cases to make learning how to program in Ada easier, and added support for several powerful new programming paradigms. My guess is that Ada 95 made learning the language somewhat easier, but tripled the amount of software engineering you need to know to use the whole language. You can see that above, with delegation of dispatching, multiple dispatch, and returning classwide objects. Those are just from the object-oriented extensions in Ada 95. I could probably teach a one semester course on how to use child packages in Ada, and then follow it with a semester on generic package parameters in Ada, or a semester on real-time programming in Ada, or a semester on high integrity programming, oops make that at least two semesters! I could also probably create a year long course on how to use access discriminants in Ada, but IMHO, maybe 30 people world-wide need to know all the tricks of using them. (Maybe a course on when it looks like you need to use access discriminants, but don't?) > English is a foreign language to me, and I'd rather not > read umpteen pages of Ada language theory when I can avoid it! ;-) ) Hmmm. There should be some good Ada reference material auf Deutch, but I don't know what offhand. (And at this point I'm not going to go back and rewrite the above. ;-) -- Robert I. Eachus 100% Ada, no bugs--the only way to create software.