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,2ea02452876a15e1 X-Google-Attributes: gid103376,public From: bobduff@world.std.com (Robert A Duff) Subject: Re: Choice of OO primitives in Ada95 Date: 1996/02/23 Message-ID: X-Deja-AN: 140782615 references: organization: The World Public Access UNIX, Brookline, MA newsgroups: comp.lang.ada Date: 1996-02-23T00:00:00+00:00 List-Id: In article , Don Harrison wrote: >Robert A Duff writes: >:No, they don't have to be in the same package (and as you say, probably >:should not be). Could you explain what you mean -- why do you think >:that all of the above types have to be in the same package? > >I don't have an RM available but quoting you (25.1.96) on the subject of dispatching >operations in response to Arcadio A. Sincero: > >> >As a matter of fact, the only indication that TPerson's Walk "belongs to >> >it" is that TPerson's Walk has a TPerson parameter. > >> That, plus the fact that it's in the same package. > >So, if you want all of the operations to be dispatching (primitive), then the >tagged types must also be in the same package. Sorry, you misunderstood me. In Ada, a procedure can only be dispatching on *one* type. It can have several parameters of that type, and a result of that type (if it's a function). But there is a run-time check that all of those parameters have the same type tag. Ada does not have multi-dispatching, like CLOS does. But neither to any of the languages (such as Eiffel) that use the prefix notation. Now a procedure that's dispatching on type T can have other parameters of other types. It does not need to be in the same package as those other types. Same thing in Eiffel -- the operation is inside one class, and is dispatching (only) on that class, but can have parameters of some other class. So, typically, you put exactly one type in a package, and put its primitive (dispatching) operations in that same package. You can put operations that don't dispatch on that type in some other package. The one case where this doesn't work too well is when you have two types that are defined in terms of each other (e.g. T1 contains a pointer to T2, and T2 contains a pointer to T1). When this happens, you are forced to put both types in the same package, or use one of the slightly ugly workarounds that have been discussed here recently. I admit that is a language design flaw. But don't generalize that to say that all types in your entire program have to get sucked into the same package! >In the same thread, Jon S Anthony went on to say that non-dispatching operations >using tagged types were those defined in different packages to those types: > >> Just to add one more bit (completely beating it to death...), it is also >> legal to have a subprogram with operands of both types as long as it is >> not in the package where the two types are declared. Of course such a >> subprogram is not primitive and will never dispatch (assuming it has no >> other controlling operands). > >Dispatching or not dispatching depending on where an operation is defined is not >what you would call consistent. Heh? I thought you were arguing in favor of the class-based languages, which do exactly that. In Eiffel, an operation is dispatching based on which class it's in. In CLOS, you can have dispatching operations that are declared whereever you want. That can lead to some rather disorganized code, but I suppose it's necessary if you're going to have multi-methods. Anyway, I think it's best to gather the dispatching operations together with the type (either inside it, as in Eiffel, or in the same package, as in Ada). Note also that in Eiffel you can declare that a given operation cannot be further overridden ("frozen", or something like that?). That's a very similar feature to what you complain about here -- in part, it is a hint to the compiler that calling that function won't dispatch to someplace else, and in part, it's a help in understanding the program, because you can know exactly which function is being called. >A couple of other gripes: > >1) Why should you have to specify that a type is 'tagged'? Can't the compiler work >that out for itself? eg. by seeing whether the type is extended elsewhere. The >developer is forced to worry about what should be an implementation issue. No, the compiler cannot tell, because of separate compilation. It can't tell in the Eiffel case, either, for the same reason. But in Eiffel, everything's dispatching. If you're willing to do extra work at link time, then you can optimize away the tag field. But not at compile time. Note that C++ has essentially the same thing -- the "virtual" keyword. If there are no virtual functions in a class, then no tag field is necessary. In Ada, if there is no "tagged" keyword, then no tag field is necessary. (One difference between Ada and C++ is that in Ada you cannot extend an untagged type with extra record components, whereas as you *can* do that in C++ (i.e. you can extend a class that has no virtual functions. There was a big fight about that during the design of Ada 9X. I don't like the way it turned out). So, you're pretty much correct that "tagged" is not a logical necessity. It is, in fact, an efficiency hack. >2) Similarly, why should the developer have to specify that an operation dispatches >(classwide operations)? Presumably, you're aiming for quicker execution, but >compilers could perform a certain degree of optimisation eg. If it knows the type >is not extended anywhere, there is no need to dispatch. There would also be >situations where the specific variant of an inherited type is known eg. following >an explicit assignment from an entity of that type. The reason for this is to avoid what is sometimes called the "fragile base class" problem that occurs in many large OO programs. If *everything* is potentially dispatching, it's much harder to understand the program. Better to do something special if you want dispatching. Another reason is of course upward compatibility. Ada 83 subprograms did not do dispatching, and of course you don't want all your Ada 83 programs to drastically change their behavior when compiled by an Ada 95 compiler. I suppose quicker execution is part of the reason, but it's not the main thing. Another point about the design of Ada's OOP: It was essential that there be little or no distributed overhead. That is, if you don't use the feature, your program should not be slowed down by the mere existence of the feature. And if you recompile an Ada 83 program with an Ada 95 compiler, it shouldn't get slower (assuming the Ada 95 compiler is as high quality as the Ada 83 one). If dispatching always happened, there would be distributed overhead (both space and time), that could not be eliminated without link-time optimizations. - Bob