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=-0.3 required=5.0 tests=BAYES_00, REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 X-Google-Thread: a07f3367d7,73cb216d191f0fef X-Google-Attributes: gida07f3367d7,public,usenet X-Google-NewGroupId: yes X-Google-Language: ENGLISH,ASCII-7-bit X-Received: by 10.66.227.72 with SMTP id ry8mr3052726pac.30.1364939303835; Tue, 02 Apr 2013 14:48:23 -0700 (PDT) Path: jv11ni528pbb.0!nntp.google.com!npeer03.iad.highwinds-media.com!news.highwinds-media.com!feed-me.highwinds-media.com!border3.nntp.dca.giganews.com!border1.nntp.dca.giganews.com!border4.nntp.dca.giganews.com!border2.nntp.dca.giganews.com!nntp.giganews.com!news.bbs-scene.org!de-l.enfer-du-nord.net!feeder2.enfer-du-nord.net!cs.uu.nl!news.stack.nl!aioe.org!.POSTED!not-for-mail From: "Dmitry A. Kazakov" Newsgroups: comp.lang.ada Subject: Re: Is this expected behavior or not Date: Fri, 29 Mar 2013 13:26:26 +0100 Organization: cbb software GmbH Message-ID: References: <1jtvzi1v65aqm.1k5ejsveno59f.dlg@40tude.net> <1hvv2kd9smnfx.6spgz9thd1mh$.dlg@40tude.net> <1raubw1sk48ca$.69rdgczvnnf.dlg@40tude.net> <2qwq2cdeuvhu$.qtnb8zyhuob9$.dlg@40tude.net> <1u72u7h5j4jg3$.wlxmaltyzqik.dlg@40tude.net> Reply-To: mailbox@dmitry-kazakov.de NNTP-Posting-Host: XRUMb5xlbonTNodERpEXEw.user.speranza.aioe.org Mime-Version: 1.0 X-Complaints-To: abuse@aioe.org User-Agent: 40tude_Dialog/2.0.15.1 X-Notice: Filtered by postfilter v. 0.8.2 X-Received-Bytes: 16084 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Date: 2013-03-29T13:26:26+01:00 List-Id: On Thu, 28 Mar 2013 16:55:34 -0500, Randy Brukardt wrote: > "Dmitry A. Kazakov" wrote in message > news:cqf0yy6q930$.1sdzw45bc0c1w.dlg@40tude.net... >> These are two data types, string and slice. Are you treating slice as an >> Ada-subtype of string? I consider slice a member of same class as string, >> yet a distinct type (in Ada sense). This is like 4.1.5, but in the same >> type hierarchy. > > I thought you might do that, but then you're going to have problems of > compatibility (Ada currently considers them the same type). An operation > like "&" cannot not allow mixed string operands (if you do allow that, you > make string literals ambiguous). Right, if strings form a hierarchy, then "&" must be primitive and a multi-method. There is no alternative to that. Even if you are going to add a completely new set of string types without slices you still will have this problem: declare X : New_String_Type; Y : A_Descendant_Of; begin ... X & Y ...; -- Is this legal? Does it raise Constraint_Error? The answers must be yes, no. And for this "&" must be a multi-method with all combinations of argument and the result types defined. Making some of them class-wide could reduce the number of combinations. And it will be ambiguous with literals and other overloaded operations returning string objects. >>> Nobody wants "type cloning". >> >> It is a very useful building block to me. Anyway it is Ada 83 legacy. > > Ada 83 derived types are virtually useless, and no one understood how they > worked. What would you do if you need a new type with same properties. Copy-paste? Example: type Position is ; type Key is some ; type Container is ...; How do you prevent Position and Key mixed in Container operations if they belong to same hierarchy? >>> I don't think a version of untagged'Class that did not work on existing >>> derived types would fly. >> >> Astonishing that you even considered that possibility. In my view they are >> completely independent things. > > You really have a strange mind. The whole idea of 'Class is to allow > different but related types to share implementations. Yes, if "implementation" means class-wide and inherited operations. The idea of T'Class is to share an interface, so that programs could be written in terms of the interface, which would make them working on all instances of the class (AKA, generic programming). We want to have this for all types including those which representations are different. If you want to be able to inherit an operation for such a type, the only way to do it is to compose old body with type representation conversion. This is the model for untagged classes. You will be free to derive from any non-limited type ignoring its representation, but then the compiler will ask you either to override an inherited primitive operation or else define corresponding conversions so that it could create one. > You're only allowed to > convert between related types, which includes derived types. That is the only way to have a class without sharing (inheriting, actually) representations. The model of Ada 95 tagged types provides a class with shared representations. This need to be augmented for the case when representations has to be different. Note that this was partially done when Java interfaces were added in Ada 2005. Java interface is a type without any representation, so there is nothing to inherit from, except for a slot for the type tag. Drop that slot, and you will have a workable model for building classes of scalar types and arrays. >> We also need "extension" that drops parent's representation altogether. >> E.g. something like: >> >> type Character is new Unsigned_8 -- or enumeration, whatever >> and Wide_Wide_Character'Interface; >> >> Character is member of Wide_Wide_Character'Class with a representation >> different from Wide_Wide_Character. > > I don't think that would work semantically, at least in the 'Class case. > Unless you expect every operation down to assigning individual components to > be dispatching, even in primitive routines defined for a specific type. Exactly! I want all that mess to become orderly defined primitive operations. This also includes attributes and aspects. There should be no special cases like T'Read, ":=" etc. There should be no magic and strange stuff like attributes, slices, ranges, aspects infesting the language, only objects and operations. > That's because you can't copy all tagged types (we have to have limited > types), and thus calling a parent operation would have to assume that > extensions have a different representation. That would both have problems of > re-dispatch and simply would be awfully expensive (you'd have to do full > program compilation to have any hope of decent code). Re-dispatch is a bad idea anyway. Yes, for untagged classes the conversions from the specific type to a class and backwards are no more for free. But these types are meant to be copyable anyway. > The only alternative would be to totally ban implementation inheritance on > such types, but as implementation inheritance is 10 times more useful than > interface inheritance, it would make the construct 95% useless. Huh, and you tell me that type cloning is useless? Cloning is all about implementation inheritance. For copyable objects implementation inheritance is much less interesting than for by-reference types. Operations (especially cross operations) need to be re-implemented anyway, at least for performance reasons. Few other operations are [conversion ->] inherited body [-> conversion]. >> Making it regular is a simplification. > > This isn't regular. All numeric types are currently members of a single > hierarchy, It is not a single hierarchy. It is a forest of similar hierarchies. They are similar just because they were constructed by the compiler rather than by the programmer. It is nominal vs. structured. Same structure does not imply equality, at least, it is meant to be so in Ada. > and in practice, the vast majority of tagged types are members of > two hierarchies (controlled and limited controlled). So what? This is equivalent to say that a type can be either copyable or else not. Yes, "copyable" is an interface. There is a countable infinity of other interfaces a type may implement. Which is why multiple inheritance is so important. But even if two types implement the same interface, e.g. can be copied, that by no means should automatically imply that these types must belong to the same hierarchy. This decision is up to the programmer. Type cloning is a valuable mechanism to insulate type hierarchies against each other. Consider a container type defined for Copyable'Class. How do you prevent mixing apples and oranges in there? Creating two new container types for copyable apples and copyable oranges? But they are still the same hierarchy. You need some fundamental operation which tells that a type breaks off the hierarchy. > More hierarchies don't buy anything (you can always move your 'Classes > around in the hierarchy to get subsets). They buy safety through type checks. >>>>> overriding >>>>> procedure Op (A : in out Der; B : in Der); >>>> >>>> This is not an overriding since the mode is different. >>> >>> It *is* an overriding in Ada 83, and it still is in Ada 2012. I agree that >>> it should not have been, but that is irrelevant because we're stuck with >>> it. >> >> Only when Der is derived using Ada 83 construct. Int'Class will be built >> using "extension." > > No way; derived types currently form a hierarchy (that's why you can > interconvert them); changing that would require fundamentally changing the > Ada model of hierarchies. To me it is a wording issue not a real problem. If you want a theoretic POV, no problem, both notions are fully compatible with each other, it is one in a more general model: Ada 83 hierarchy is a class of equivalence. Types from the hierarchy are equal in the sense that they can be used interchangeable in the operations. Each type is both a sub- and supertype of another (in non-Ada sense). The graph of types is undirected. Ada 95 tagged hierarchies are rather directed. A tagged type is only a subtype (in non-Ada sense) of another tagged type in the hierarchy. Untagged class would be an ability to have directed hierarchies of scalar types and arrays. Why there should be none? Conversely, ad-hoc supertypes would allow building Ada 83 equivalent types with all types including tagged. You would derive a new type [=subtype] and simultaneously declare it a supertype. This will effectively become a type you can use interchangeably with the parent type [and get some ambiguities, surely]. > The issue today is that you can't name those > hierarchies, that's what untagged 'Class would allow. Name it a "taxonomy" then. >>>>> Obj : Int'Class := Der'(1); >>>>> Op (A => 1, B => Obj); -- Legal?? >>>> >>>> MD is not fully implemented in Ada, but anyway, presuming that Op is a >>>> multi-method, that the mode is "in" and that 1 is Universal_Integer (is >>>> it?), the above is illegal because Op is not a method of >>>> Universal_Integer'Class. You would have to write it as: >>>> >>>> Op (A => Der'(1), B => Obj); -- Same tags >>>> Op (A => Int'(1), B => Obj); -- Different tags, Constraint_Error >>> >>> This seems wrong; an integer literal usually gets its type from context. >>> It's only a value of type Universal_Integer if there is no type given from >>> context (as in a type conversion or number declaration). I was presuming >>> that integer literals act as tag-indeterminate functions (the most sensible >>> model; if you have constants for a tagged type you usually would model them >>> as tag-indeterminate functions - for instance, the constants Zero and One in >>> a universal arithmetic package), you don't need multiple dispatch: the Ada >>> dispatching rules simply calls the correct version of the function for the >>> tag of the object. And that's what we would want to happen for integer >>> literals (that's the closest analogy to what happens for existing >>> expressions). >> >> This is one of possible interpretations of Ada semantics. You consider >> literals primitive operations, e.g. >> >> function "1" return Int; >> >> In Ada 95 such function has to be overridden, when Der extends Int. >> However, if the representation is inherited and not changed, the rule >> could be that the compiler silently overrides all literals: >> >> overriding function "1" return Der; >> >> In this case you will have 1 overloaded. Op will become ambiguous. > > Yes, this is how Ada literals work today. This isn't how its described (it's > an implicit conversion rather than overloading) but the effect is the same. > >> The alternative with different types can be discarded using some >> preference rules, e.g. domination, and thus ambiguity resolved. > > Preference rules cause Beaujolias effects, and almost never can be > tolerated. The idea that you can have an object of partially visible type was likely wrong. So the separation of "with" and "use" was an mistake too. The language should rather prevent declarations which are doomed to cause ambiguities later. > As soon as user-defined things are involved, preference rules simply don't > work. Some rules are clearly needed to choose between: function "&" (Left : String; Right : Wide_String) return Wide_String; function "&" (Left : String; Right : Wide_String) return String; The problem is not Ada design, but the nature of multi-methods with cross operations enabled. The programmer should be allowed to declare some signatures as equivalent, meaning: the compiler is free to use any without reporting ambiguity. The principle is always same, if you cannot decide it, leave to choice to the programmer. > The problem being that adding > or removing a single declaration can silently change the meaning of an > expression without causing an error. This is considered intolerable.) Not always. It is OK with overriding. My take on this that the language should prevent situations when you have an object with some of its operations invisible, especially, when these operations override the same primitive operation. I think this should be possible to do while keeping it backward compatible, because standard types are always visible anyway. > My contention remains: if you are going to allow user > "Handle.all", then you need to expose the type of Handle.all. If you don't > need to allow that, then you are not going to use 4.1.5 at all. So I fail to > see your concern. I don't need Handle.all, but I do need Handle.Foo meaning Handle.all.Foo. Publicly it is an opaque by-copy type, privately it is an access type. > Compiler checks are impossible for these things. Why so? How is it different from in-mode of an argument. The compiler routinely checks that you never update it. > Unless you want to drive > yourself nuts with OOP baloney - and it's never going to be possible to do > these sorts of checks with type checking. I don't understand why it has to be impossible to have: type File is ...; type Read_File is new File ...; prcodure Read (X : in out File; Data : out Stream_Element_Array); type Write_File is new File ...; procedure Write (X : in out File; Data : Stream_Element_Array); type Readwrite_File is new Read_File and Write_File ...; with concrete types, but well possible with interfaces. The nature of the check that the operation Write is not applied to Read_File is exactly same in both cases. It is not about checking, especially because the above construct is still possible to get through generics. It is just a stupid limitation motivated by urban legends mindlessly repeated through (using you wording) OO-baloney texts. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de