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-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.121.201 with SMTP id lm9mr2713878pab.10.1364939980587; Tue, 02 Apr 2013 14:59:40 -0700 (PDT) MIME-Version: 1.0 Path: q9ni26178pba.1!nntp.google.com!npeer02.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!weretis.net!feeder4.news.weretis.net!nuzba.szn.dk!news.jacob-sparre.dk!munin.jacob-sparre.dk!pnx.dk!.POSTED!not-for-mail From: "Randy Brukardt" Newsgroups: comp.lang.ada Subject: Re: Is this expected behavior or not Date: Fri, 29 Mar 2013 19:49:25 -0500 Organization: Jacob Sparre Andersen Research & Innovation 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> NNTP-Posting-Host: static-69-95-181-76.mad.choiceone.net X-Trace: munin.nbi.dk 1364604569 9031 69.95.181.76 (30 Mar 2013 00:49:29 GMT) X-Complaints-To: news@jacob-sparre.dk NNTP-Posting-Date: Sat, 30 Mar 2013 00:49:29 +0000 (UTC) X-Priority: 3 X-MSMail-Priority: Normal X-Newsreader: Microsoft Outlook Express 6.00.2900.5931 X-RFC2646: Format=Flowed; Original X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.6157 X-Received-Bytes: 19166 Date: 2013-03-29T19:49:25-05:00 List-Id: "Dmitry A. Kazakov" wrote in message news:byacn0wck9qt$.norrlukaw80l$.dlg@40tude.net... > 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. Well, actually, the answer is "no, irrelevant" as it is for the existing Ada types. You can always use an explicit type conversion if you need these to match. If each type effectively has its own literals, then this works perfectly. Mixing types is precisely the sort of thing you don't want to do in a strong typing environment. I think this problem illustrates why the schemes you are thinking of will never be used -- they just add far too much complexity to the language. Maybe the 10000 foot view is simpler, but the complexity under the hood would be extremely difficult to implement. >>>> 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? Make a new declaration, using constants for the properties. But the real answer is that this never happens, so it's a bogus concern. If the properties are truly the same, it's hardly likely that the type is the same. After all, the maximum number of Apples and Oranges is unlikely to be the same (the physical properties differ). > 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? If the container operations take parameters of type Key, then you can only pass a Position to such a parameter if you use an explicit conversion. That's how it works in Ada today - there's no automatic conversion between different specific types. If you have Root_Integer'Class parameters, then you made a choice to have weak typing, so there is no problem. I think you're overthinking this... ... > 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. I don't know how one could implement "interfaces" without a tag. And there is no requirement in Ada that the representations of the tag be the same (although an implementation can impose one since there is no requirements about supporting tag positions). >>> 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. You miss my point: the effect is to make *every* operation in *every* subprogram -- including specific ones -- dispatching -- which would be way too expensive in practice (no optimization would be possible). >> 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. You are still misunderstanding. You'd *have* to have redispatch because the only way to implement inheritance would be to make all operations (even those very low-level ones) dispatching in *all* instances. There could be no statically bound operations. >> 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. The only useful inheritance is implementation inheritance, but it requires all of the types to be in the same hierarchy so that they can be interconvertable. (That doesn't necessarily mean *cheaply* interconvertable.) Interfaces are useful only in a very small % of cases, and that is such a small % that I wouldn't bother at all (certainly not more than Ada 95 abstract types). > 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]. Not all objects that have different representations are copyable. (Consider an array of some_task'class). Whatever solution one has for untagged types has to be able to deal with uncopyable objects with different representations. But banning implementation inheritance makes the whole thing useless IMHO, and supporting it is too expensive. It really kills the entire idea. >>> 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. No, you need to read the RM again. All integer types are effectively derived from Universal_Integer, and all of the rest are derived from Universal_Real. There are only two hierachies. You're just not allowed to name them, which is limiting in various ways (especially as you can use 'Pos to force run-time operations to occur for Universal_Integer). >> 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. You mean why it is so useless. If you break down every possible property of a type as an interface, you'll end up with a very expensive complex structure. The only way I know of to implement multiple inheritance is with a linked-list of tags, which isn't too bad when only a few interfaces are involved. But once you get the dozens you're talking about, it's going to cause a very significant overhead. > 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. I suspect that *all* types should belong to the same hierarchy, if you are going this OOP route. Because you need operations that work on every object, copyable or not. Remember, there are no type conversions between types in different hierarchies. > 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? Of course you have separate containers for each kind of thing that they contain. When you define a container for Copyable'Class, you are allowing anything non-limited in that container -- you've specifically decided not to enforce any sort of checking on the contents. > But they are still the same > hierarchy. You need some fundamental operation which tells that a type > breaks off the hierarchy. Yes, of course all containers ought to be members of the same hierarchy. How else would you create generic algorithms? It's unfortunate that Ada doesn't allow this because the element type makes it impossible. No idea how to work around that. >> 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. Since *every* type in a hierarchy is different and cannot be mixed with any other type unless you explicitly request it (via declaring something to be class-wide), you are gaining absolutely nothing by added hierarchies. ... >> The issue today is that you can't name those >> hierarchies, that's what untagged 'Class would allow. > > Name it a "taxonomy" then. I see, you're wasting my time again by changing the terminology on the fly. I should know better than to discuss anything substantive with you. My mistake. ... >> 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. No one has ever figured out how to do that, and in any case, it would be incompatible with Ada as it stands. We can't even do simple things like make objects overloadable because of compatibility concerns -- changing the visibility model is a non-starter. >> 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; There are no such operations predefined in Ada, and that's because you *can't* chose between them. You can of course declare them yourself (that's essentially what happens in Ada.Strings.Unbounded), but the net effect is that you then cannot use literals with such a type. Ada does not attempt to prevent you from shooting yourself in the head. :-) >> 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. That isn't quite the same thing, because the declaration in question is replacing one, not adding or removing one. And of course in a good system they'll also have the same preconditions and postconditions, meaning that the changed operation would have very similar semantics. The cases I'm thinking about have to do with ordinary object declarations being added or removed elsewhere in the program. Changing the object used silently is intolerable. Personally, however, I've concluded that the OOP cases are also very dangerous. Maybe not quite to the level of "intolerable", but pretty close. That's the curse of OOP: not being able to figure out what routine is being called (and especially why the *wrong* routine is being called). > 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. Huh? This is very common with private types. With interfaces, we enforced such a rule and it is rather limiting. Claw uses hidden operations on window objects to keep the mechanisms of the message loop hidden away. Otherwise, we'd have no way to ensure that our objects are getting the messages as intended, and it would be impossible to make anything work reliably. These routines have to be dispatching, as the responses to different messages obviously depend on the kind of object they are associated with. So they have to be inherited and overriddable, but not changable by clients (who might be doing their own overriding of public operations for their own purposes). In general, designers of good libraries need to be able to hide the mechanics of the implementation from clients (both for understandability and to prevent people from screwing them up). Without being able to hide operations, there is no hope of that. >> 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. In this case, you're probably not using 4.1.5, because it is only about .all (and writing into .all specifically). If you are using 4.1.5 (to allow writing into Foo), you're exposing an access to the type of Foo, not to the file type. >> 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. You can do this sort of thing in exceedingly simple cases. But it simply gets too complex the more properties that you try to decompose on. And every such property is making all of your dispatching calls slower and slower. Moreover, most interesting properties are not binary like the file mode. They tend to depend on the *values* of parameters, and often on combinations of values. It just is too complex to do. > 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. Implementability is hardly an "urban legend". If the resulting code is too slow to use, then it's pretty silly to structure it that way, even if it is theoretically possible. Randy.