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-27 14:12:25 PST Path: archiver1.google.com!news2.google.com!news.maxwell.syr.edu!small1.nntp.aus1.giganews.com!border1.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: Thu, 27 Nov 2003 16:12:23 -0600 Date: Thu, 27 Nov 2003 17:12:21 -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-Czx7GCFHC6FqsUBvso3/25WcFsAOMB+WdlyD2kyIL8G3V0HxC0fXVIeApWC9ugwDOhjmPhwV1fzLqmS!hTHp3NeqVL7Ai1JnsrOE7hCfN4PD2Hy3sAiXFgzy3R9wAb+nscFGWvqOVzM5Mw== 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:3007 Date: 2003-11-27T17:12:21-05:00 List-Id: Ekkehard Morgenstern wrote: > But I have a few more questions, so it'd be great if you could answer them. > :-) Ludovic Brenta and Jeffery Carter did a pretty good job of that, but there were a couple comments I wanted to add. > This is really a great feature! So I can practically test if any object is a > member of a particular class, or set of classes? As answered elsewhere, actually a set of types, which is what a class is in Ada. But you can do more than that. You can test for membership in any subtype. The canonical example is "if Today in Weekday..." where Weekday is declared as "subtype Weekday is Day range Monday..Friday;" or if necessary for some reason you could check for membership in a range: "if Today in Monday..Wednesday..." Again, in Ada you choose the most appropriate way to communicate the meaning of the code to the reader, and let the compiler figure out how to do what you want. > I still wonder if The_Registration is actually a reference, or is it the > object returned by Register()? Don't wonder, this is information hiding at work. You expect the implementor of the Registration type to export an abstract data type (ADT) and operations on it that match your expectations, and the documentation. Whether a Registration is an access type, a record type, a controlled record type or even an object of type Ada.Strings.Unbounded.Unbounded_String should not matter to any user of the package. This turns out to be another way of saying that Ada allows you to enforce that any code outside the declaring package (and possibly child packages) that depends on how Registration is represented is a bug. So if you need to change the representation later, the changes are all localized. > I.e. if I wanted only a reference to the object, would I have to use an > access to Registration'Class? See above. The package might export a reference type to be used in implementations of other data structures. But again, what is usually exported in Ada is the reference SEMANTICS. Whether the ADT Registration_Ref is a registration number, an access type, or an index into a database need not be revealed to the user. It is just a way to refer to a Registration. In Ada, the expectation is that you hide as much information as possible. This makes development, debugging, and support of the application much easier. > Can I return an object of type Node'Class, or do I have to return an access > to it? You can do either. But the usual is to return an object of generic formal type Node, then the actual can be whatever you want, including Registration'Class. > But what about return values from functions? Or does the return value in a > function correspond to an "out" parameter in a procedure? No, but the difference is pretty subtle. A function can return an object of an unconstrained subtype, or classwide type, and the function itself will determine what to return. In the case of an out procedure parameter, any constraints on the object are determined by the caller. So if a function returns a String, the size of the String (technically its bounds) can be determined by the inside the function: function Weird(X, Y: Integer) return String is begin return Integer'Image(X*(Y/GCD(X,Y)); end Weird; With an out parameter, the procedure can access the attributes of the object passed by the caller: procedure Blank(S: out String) is begin S := (others => ' '); end Blank; Of course you can explicitly pass attribute values to a string: function Blank(S: in Positive) return String is begin return String'(1..S => ' '); end Blank; X: String(1..Length) := Blank(Length); > BTW, how do I use the Finalization package? I'd like to use things like > constructors and destructors sometimes to be able to initialize / cleanup > objects when they're created or destroyed. Good idea. But the real answer to your question is that you should only use controlled types directly with more experience. Otherwise use an existing data structure library to do things. (Or if necessary modify existing code to do what you need.) Getting the "corner cases" of memory management is tricky irrespective of the language used, so using a well tested package means you won't have to track down very subtle memory leaks. (The usual problem cases are when an exception occurs during the creation or freeing of a data structure.) > I'm really surprised how short my source code has become. It's just the way > programming's meant to be that I can concentrate on the key tasks at hand. > :-) Exactly. Programming in Ada should be just as hard as getting the algorithms right--and no harder. > What I also find very useful is the renaming of packages / procedures etc. > Does that have any impact on dispatching or inheritance (in the case of > renaming procedures)? Not that you should worry about. There are again subtle cases where renaming will cause static dispatching to replace dynamic dispatching, or where renaming makes it easier for the compiler to generate good code. But in general use renaming when it makes your work easier, and expect the semantic analysis phase of the compiler to eliminate any potential overhead. I often use renaming to give more meaningful names to operations and data types in generic instances. > BTW, can I instantiate a generic package in a procedure body (initialized > with a value passed as a parameter)? (I didn't try yet) Yes you can. There are some cases involving static nesting levels that can cause problems. For example, packages that create controlled types need to be instantiated at library level, either in package specifications or bodies. If you run into a case where this is a problem, the solution is to make the procedure a generic procedure, then pass the actual controlled type as a generic formal type parameter. This leads to one of the most powerful aspects of Ada generics: Generic instantiation occurs at run-time, not at compile time. So it is perfectly legitimate to instantiate a generic inside a loop, and expect each instance to be different. (Or more to the point, similar to the previous instance only by happenstance.) Note that for a generic subprogram, there are three or more "freezing" points where actuals are matched to formals. (These are different from freezing points in Ada for aspects of a type.) The first occurs at compile time, the second occurs when, at run-time, the generic specification is elaborated, and the third occurs when the instance is called. Why "or more"? A generic package may contain other generic declarations, creating a cascade of such parameter freezing points. This can be a very powerful tool if used right. > The problem with arrays is that they need to be preallocated to a particular > size, which can consume a lot of memory. Lists and Nodes occupy only the > memory that they actually use. In Ada it is trickier than that. ;-) Ada array sizes can be dynamic--determined at run-time--and still be the exact size needed. Or you can use data structures like doubling lists. In a doubling list every time it gets full, you allocate a new array twice the size, and copy all the current data over. For a list that only grows, such a structure never takes as much as twice the space needed, and the "extra" copying averages to between one and two copies per entry. Or another structure I have used for sparse array implementation. You manage a pool of nodes all allocated from the same heap, but instead of allocating one at a time, you allocate an array of 1000 or so. Since you never try to return a part of one of these allocated chunks, it minimizes the allocation and freeing overhead. Note again what makes all this possible in Ada. The chunk management layer was hidden from the sparse matrix implementation, the sparse matrix implementation was hidding from the algorithms that used it. So instead of making the overall coding job impossibly complex, I had three separate sections of code, all of which were just as complex as they needed to be. I could test the algorithms using a non-sparse matrix package, and test the sparse matrix software with an implementation that allocated nodes individually. Then when all the code was complete and tested, I could combine it and get the real-time performance that I needed. > How do I avoid casting access types between derived classes? By declaring the access type as "access all Foo'Class;" if that is what it should be. Again, if you find yourself writing type conversions outside very special circumstances it indicates that you haven't thought the design through correctly. Of course, sometimes you need to convert floating-point values to fixed-point or integer, or vice-versa. And in the bodies of operations on derived types you may want to call the parent operation, but as I said, there are a few contexts where type conversions are expected. Oh, and note that in Ada, class trees tend to be "bushier." In the Registration example, there might be one parent Registration type and all other members of the class would be derived from it directly. There are some cases involving mix-ins where you have a sequence of derivations, but usually only the ultimate parent and child are visible outside the defining package. > Yes, but you don't really need to know about all these things to program in > Ada, and that's really a good thing. You can get along well with basic > constructs, and the more intricate details of the language can be postponed > to be used later when necessary. Exactly, and it was intended that way. There are two subsets of Ada that people learn, the "Pascal superset," everything needed to write Pascal programs in Ada, and then the "Ada subset," basically the Pascal superset plus packages, attributes, generic instantiations, and exceptions. Then the other separable parts of Ada, OO, programming in the large, tasking, writing generic units, etc. can be learned in any order. > I hope I can get back to you with my questions when I have to write an Ada > compiler and run-time system. You have already been warned. Don't. Porting GNAT or some other existing Ada compiler will save you a huge amount of grief. A usable Ada compiler required about a man-decade of work back in the Ada 83 days. By now GNAT, Ada Magic, Rational, and so on would take several man-centuries to duplicate. The front-end, semantic analysis, and machine independent optimization phases are each several times the size of the code generator, and if you port an existing code generator, you will find that the tools are designed to support just that. -- Robert I. Eachus 100% Ada, no bugs--the only way to create software.