!standard 4.06(01) 02-01-23 AC95-00026/01 !class amendment 01-12-28 !status received no action 02-01-23 !subject User-defined type conversion !summary !appendix From: Randy Brukardt Sent: Friday, December 28, 2001 7:24 PM For the four thousandth time, I'm writing something like: function String_of (Name : in Face_Name) return String; -- Returns a string representation of the face name Name. and I can't think of a decent name. It really should be "String", because its a conversion, but that won't work for those using use clauses. Similarly, the conversion from String to Face_Name really ought to be called "Face_Name": function Face_Name_of (Name : in String) return Face_Name; Shouldn't we be allowed to define conversions between composite types that wouldn't otherwise have them? That is especially useful for private types. Or better yet, "constructors" a-la C++? Ada already allows overloading of type conversions and subprogram calls, provided that the declarations are not in the same declarative region: package Font is FACE_SIZE : constant := 32; type Face_Name is array (1 .. FACE_SIZE) of aliased Interfaces.C.Char; package Nest is function Face_Name (Name : in Standard.String) return Font.Face_Name; function String (Name : in Font.Face_Name) return Standard.String; end Nest; end Font; with Font; procedure Test is use Font, Font.Nest; Value : Font.Face_Name; -- Can't say "Face_Name" here, a use clause conflict. begin Value := Face_Name (Name => "Arial"); -- Calls Font.Nest.Face_Name. declare Name : String := Nest.String (Value); begin ... end; end Test; Note that a single parameter positional call to Face_Name is ambiguous with a type conversion, but otherwise it allowed in Ada. (A type conversion essentially resolves as a single parameter function which takes "any type".) OTOH, "String ()" is always a type conversion (except in Font.Nest), because String is declared in Standard, and thus is directly visible. Thus, it is possible to provide this capability without changing resolution at all. The main problem is to eliminate restrictions on where "constructors" can be declared. We want to be able to declare constructors with the type they construct. This could be done without any incompatibilities as follows (warning, this is a rough proposal, not a fully fleshed out one). Define a type declaration as "partially overloadable". Define a "partially overloadable" declaration to never be a homograph of an "overloadable" declaration. The effect would be to allow subprograms ("callable entities") to overload types in the same declarative region, but otherwise leave the rules alone. The changes would only make currently illegal programs legal, so there would not be any compatibility problems. We'd also probably want to allow one "partially overloadable" to be use visible simultaneously with one or more overloadable entities. (That would allow "Value : Font_Name;" in the example above; the fact that it isn't legal is weird). Again, only illegal programs would be made legal. This wouldn't solve my original problem for people who use use clauses. I doubt that problem can be solved, mainly because of the "always visible" nature of types in Standard. That means that the conversion/constructor would never be directly visible. However, it would come close to solving the problem for user-defined types. The only problem that I see with this is that it wouldn't allow positional single parameter calls (such a call would always be ambiguous with a type conversion). A preference rule for a subprogram call over a type conversion would work, but those always seem to have Beaujolais problems. Perhaps not in this case (as the overloading is limited). This requires more study. Anyway, is this idea worth pursuing? **************************************************************** From: Robert Dewar Sent: Saturday, December 29, 2001 6:10 AM I must say that I find the name To_String to be perfectly fine, and in fact preferable to the use of String in this context. **************************************************************** From: Robert Duff Sent: Monday, December 31, 2001 10:37 AM > Ada already allows overloading of type conversions and subprogram calls, > provided that the declarations are not in the same declarative region: No, that's not right. There is never a place where a type X and a function X are simultaneously directly visible. One or the other can be directly visible, or neither. ... > Value := Face_Name (Name => "Arial"); -- Calls Font.Nest.Face_Name. No, that's currently illegal for the same reason, namely ``-- Can't say "Face_Name" here, a use clause conflict.'' > Note that a single parameter positional call to Face_Name is ambiguous with > a type conversion, but otherwise it allowed in Ada. No, that's not right. There is never an ambiguity between a function name and a type name, because it is impossible for them to both be directly visible. > This could be done without any incompatibilities as follows (warning, this > is a rough proposal, not a fully fleshed out one). I don't see how you can avoid incompatibilities (and/or Beaujolais effects and/or other error-prone rules). Eg: procedure Main is package P is type X is private; type Y is new X; end P; package Q is function X(Param: P.Y) return P.X; end Q; package body P is Y_Var: Y; use Q; X_Var: X := X(Y_Var); -- Legal? end P; The above is legal Ada 95. The "X(Y_Var)" is a type conversion. Function X is not visible at that point. It seems that function X would be visible in the new proposal, thus causing an incompatibility. Or is there a preference rule? If we prefer type_conversions over function calls, the above is error-prone. If we prefer function calls over type_conversions, it is also error-prone, and in addition, the above program changes its run-time behavior. A preference either way is error-prone, because how do we know which one the user *meant*? Furthermore, I think such a preference rule causes Beaujolais. Now the above example is questionable, because the type_conversion is legal. Your goal of course is to define a function that can be used when a normal type_conversion would *not* be legal. So perhaps you want some rules saying the function is only allowed (or only visible, or only resolvable-to) in cases where the type_conv is not legal. But the type_conv rules are not currently Name Resolution rules, and for good reason: they're way too complicated. I wouldn't want to change that. In other words, the current resolution rule for type_conversions is that they accept any type. Unless you change that, you could never call the function, because the type_conv would always be an "acceptable interpretation". But if you *do* change that, you're adding an awful lot of complexity. > Define a type declaration as "partially overloadable". Define a "partially > overloadable" declaration to never be a homograph of an "overloadable" > declaration. > > The effect would be to allow subprograms ("callable entities") to overload > types in the same declarative region, but otherwise leave the rules alone. > The changes would only make currently illegal programs legal, so there would > not be any compatibility problems. > > We'd also probably want to allow one "partially overloadable" to be use > visible simultaneously with one or more overloadable entities. (That would > allow "Value : Font_Name;" in the example above; the fact that it isn't > legal is weird). Again, only illegal programs would be made legal. I skept. This is one case where I wouldn't believe any such pronouncments without seeing the actual RM wording. (And you know I often say fiddling with wording is a waste of time. ;-)) > This wouldn't solve my original problem for people who use use clauses. I > doubt that problem can be solved, mainly because of the "always visible" > nature of types in Standard. That means that the conversion/constructor > would never be directly visible. However, it would come close to solving the > problem for user-defined types. > > The only problem that I see with this is that it wouldn't allow positional > single parameter calls (such a call would always be ambiguous with a type > conversion). A preference rule for a subprogram call over a type conversion > would work, but those always seem to have Beaujolais problems. Perhaps not > in this case (as the overloading is limited). This requires more study. > > Anyway, is this idea worth pursuing? I might relax the rules along the lines you suggest if I were designing a language from scratch. (E.g., why not make *everything* overloadable? That's a simple rule.) But in the context of Ada, I have to say no, this idea is not worth pursuing. Meddling with the visibility rules in order to solve a minor naming issue is not a good idea. "Partial overloading" is an extra concept that will cause user's brains to hurt. And compiler writers will have to change some of the more subtle parts of their front ends. I suggest you call it "To_String" or whatever, and not try to change the language. Actually, in the case of to/from String, I often use "Image" and "Value" as the function names, by analogy with the attributes that do the same thing for predefined types. **************************************************************** From: Randy Brukardt Sent: Monday, December 31, 2001 12:44 PM > > Ada already allows overloading of type conversions and subprogram calls, > > provided that the declarations are not in the same declarative region: > > No, that's not right. There is never a place where a type X and a > function X are simultaneously directly visible. One or the other can be > directly visible, or neither. OK, rephrase that to "Ada compilers already..."... > > package Font is > > FACE_SIZE : constant := 32; > > type Face_Name is array (1 .. FACE_SIZE) of aliased Interfaces.C.Char; > > package Nest is > > function Face_Name (Name : in Standard.String) return Font.Face_Name; > > function String (Name : in Font.Face_Name) return Standard.String; > > end Nest; > > end Font; > > > > with Font; > > procedure Test is > > use Font, Font.Nest; > > Value : Font.Face_Name; -- Can't say "Face_Name" here, a use clause > > -- conflict. > > begin > > Value := Face_Name (Name => "Arial"); -- Calls Font.Nest.Face_Name. > > No, that's currently illegal for the same reason, namely ``-- Can't say > "Face_Name" here, a use clause conflict.'' I compiled this program on GNAT and ObjectAda before posting it, and both accepted the above call. That does imply that care would be needed here, because we generally don't want to enforce a rule more restrictive than what compilers already do (irrespective of what the language says -- see the prefix of 'Access discussion). > I don't see how you can avoid incompatibilities (and/or Beaujolais > effects and/or other error-prone rules). Eg: > > procedure Main is > > package P is > type X is private; > type Y is new X; > end P; > > package Q is > function X(Param: P.Y) return P.X; > end Q; > > package body P is > Y_Var: Y; > use Q; > X_Var: X := X(Y_Var); -- Legal? > end P; > > The above is legal Ada 95. The "X(Y_Var)" is a type conversion. > Function X is not visible at that point. Of course. No rule ever would make a use visible item visible instead of a directly visible one. Either both items have to be directly visible (ignoring hiding), or both have to use visible. (Thus the "String" business is unsolvable. But the constructor case is still interesting...) Thus any (existing) program change would be caused by adding a construction-style function in the same scope as the type. (That's not currently legal). So I don't think any existing programs would change behavior. >... > I might relax the rules along the lines you suggest if I were designing > a language from scratch. (E.g., why not make *everything* overloadable? > That's a simple rule.) > > But in the context of Ada, I have to say no, this idea is not worth > pursuing. Meddling with the visibility rules in order to solve a minor > naming issue is not a good idea. "Partial overloading" is an extra > concept that will cause user's brains to hurt. And compiler writers > will have to change some of the more subtle parts of their front ends. > > I suggest you call it "To_String" or whatever, and not try to change the > language. > > Actually, in the case of to/from String, I often use "Image" and "Value" > as the function names, by analogy with the attributes that do the same > thing for predefined types. Careful! Next thing you know, someone will be asking for user-definable 'Image and 'Value attributes. :-) **************************************************************** From: Randy Brukardt Sent: Monday, December 31, 2001 3:36 PM > > Value := Face_Name (Name => "Arial"); -- Calls Font.Nest.Face_Name. > I compiled this program on GNAT and ObjectAda before posting > it, and both accepted the above call. Someone complained to me that they couldn't compile the program with GNAT, so I did some further investigation. Apparently, I was misled by the compiler's error handling: my test program was a B-Test, and no errors were reported for the call on either GNAT or ObjectAda. However, with everything but the call removed, the call does in fact report an error. Therefore I withdraw the entire "constructor" based proposal; it doesn't solve the problem without way too much mess. My original idea was user-defined conversions; that would look something like: for Face_Names'Conversion use To_Face_Name; for Face_Names'Conversion use To_String; -- The reverse conversion. The details would need some thought, but I believe it could be made to work. (And it doesn't have any effect visibility). **************************************************************** From: Robert Duff Sent: Tuesday, January 1, 2002 9:38 AM > My original idea was user-defined conversions; that would look something > like: That's idea sounds easier to make work. But I still don't think it's worth the trouble. I don't see a big difference in readability between String(X) and To_String(X). Now how about user-defined *implicit* conversions? **************************************************************** From: Randy Brukardt Sent: Friday, January 4, 2002 5:16 PM > That's idea sounds easier to make work. > > But I still don't think it's worth the trouble. I don't see a big > difference in readability between String(X) and To_String(X). Well, if we allow overriding existing conversions as well as adding new ones, it would allow getting rid of conversions that you don't want. It could be very useful for user-defined numeric types (such as unit conversions). As far as readability goes, I'm sure that you aren't claiming that it harms the readability. But the big benefit is writing code that depends on canned libraries. For those, you have to go rummaging around to figure out what the name of the conversion routine might be. (It's unlikely that those libraries use the same convention that you do.) Certainly Ada emphasizes readability over writeability, but there certainly isn't any reason we can't make Ada easier to write (as long as doing so doesn't harm readability). > Now how about user-defined *implicit* conversions? Well, I think you need an *explicit* conversion before you can talk about *implicit* conversions. But I'm not sure how that would work (other than the obvious case of Universal integer and universal real). And it seems to me that implicit conversions may introduce incompatibilities as well (it would have a substantial impact on resolution). But if you have something specific in mind, I'd be happy to look at it at the same time. **************************************************************** From: Robert Dewar Sent: Friday, January 4, 2002 5:22 PM I actually find that adding user defined conversions is likely to harm readability. If i see x := string(b); then (assuming string has not been redefined, which seems a reasonable assumption), I know this is an ordinary type conversion without having to look all over the place to see if a conversion called string has been defined. If I see x := To_String (b); or x := Image (b); then I know this is a user defined function, and I can go look at it. <> Sounds like it is the *writer* who has to do the rummaging, e.g. to find out whether the name is To_String or Image. Indeed if we add user defined conversions, the rummaging just gets incrementally worse, since now we look not only for user defined functins but also user defined conversions. I really don't see how the reader is much affected, except perhaps negatively as postulated above, by this alledged "rummaging". I really find this very odd (Randy's insistence on the value of this feature). We certainly never had any users who expressed any interest in such a capabilility (and we have users who are not at all shy about saying what they want :-) **************************************************************** From: Randy Brukardt Sent: Friday, January 4, 2002 6:30 PM > I actually find that adding user defined conversions is likely to harm > readability. If i see > > x := string(b); > > then (assuming string has not been redefined, which seems a reasonable > assumption), I know this is an ordinary type conversion without having > to look all over the place to see if a conversion called string has > been defined. > > If I see > > x := To_String (b); > > or > > x := Image (b); > > then I know this is a user defined function, and I can go look at it. Maybe, but that could be true at most for the predefined types, and since the only predefined types that get used are String and Character, it doesn't seem a major difference. Certainly, if you see X := File_Kind (B); you don't know if you have a function call or a type conversion without looking up File_Kind. Indeed, I generally look at type conversions as a function call with funny resolution. I don't see them as fundamentally different, just predefined. Perhaps that is why I see this issue differently than you. This is really no different than overriding an operator in Ada. Certainly, someone could redefine "+" to be multiply, but that would break the abstraction. The same holds here. Usually, you don't need to know exactly what the expression does, be it a type conversion, an unchecked conversion of some sort, or a call to the National Archives. :-) Of course, when debugging, you have check every item carefully. But this wouldn't make it appreciably harder. > I really don't see how the reader is much affected, except perhaps negatively > as postulated above, by this alledged "rummaging". They aren't. This feature is useful in two ways: Making better abstractions by better matching operations to functions (and, as a side effect, providing a way to eliminate predefined functionality that is inappropriate). Making it easier to write programs based on "canned" abstractions. > I really find this very odd (Randy's insistence on the value of this > feature). We certainly never had any users who expressed any interest > in such a capabilility (and we have users who are not at all shy about > saying what they want :-) I asked several Ada users (at least one of whom is here) before publicly floating this idea. (I was concerned that it was too complicated for the benefit.) They all thought it was worth investigating. However, the silence here now that I have done so is deafening. Perhaps they encouraged me to investigate it for the amusement value in seeing me float such a dumb idea? :-) **************************************************************** From: Robert Dewar Sent: Friday, January 4, 2002 6:59 PM <> Sometimes I think you are in a different world. If I grep customers code for the predefined types, I will find zillions of uses of Boolean (I bet that is true even of your code :-) I will also find lots and lots of uses of Float, Integer etc. And I don't see why what I say applies only to predefined types anyway. >>I asked several Ada users (at least one of whom is here) Do we really have Ada users here? In my experience the answer is close to no :0-) **************************************************************** From: Tucker Taft Sent: Sunday, January 6, 2002 8:37 PM I don't find the cost/benefit ratio very favorable for this idea. On the other hand, user-defined subtype conversions (analogous to the "sliding" conversions provided for array subtypes of the same length) is something we talked about in the past, and could be quite useful to deal with discriminated types, and arrays for that matter. Now that we have this nice notion of "operational attributes," it would certainly seem natural to use that feature for defining additional capabilities like this. Ideally any place that implicit array subtype conversion happens now one could do a user-defined subtype conversion. However, that is difficult to support because there is no easy way to pass in the constraints of the target, unless it already exists an object. That means that only an assignment statement to an existing object would work well. Hence, this would devolve into a user-defined conversion-on-assignment-statement procedure. That's probably OK, because when passing parameters or initializing a stand-alone object, it is easy enough to have the parameter or object take its constraints from its initial value. (That still leaves unhandled the case of initializing a component, but you can't have everything!) Hence, the proposal would be something like: procedure Asgn_Cnvt(Left : in out T; Right: in T); for T'Assignment_Conversion use Asgn_Cnvt; This would be invoked when the left-hand side of an assignment statement is a variable of type T, and the normal assignment statement would raise a Constraint_Error due to mismatched discriminant or length. I considered just having an "Assignment_Statement" attribute, but it is much more useful and efficient if the generated code checks for the constraint/length mismatch, and only invokes the user-defined procedure in that case. Note that this procedure would be invoked even for controlled types that have their own finalize/adjust routines. The finalize/adjust would be invoked when the constraints match, while this would be invoked when they mismatch. For controlled types, it also might be nice to have an ability to special-case assignment statements (and perhaps other usage contexts) even when the constraints match. This is probably better handled by adding another primitive operation to the Controlled type. For example: procedure Assignment_Statement( Left : in out Controlled; Right : in Controlled); If overridden for a particular controlled type C, this would be invoked when the constraints match and the left-hand side of the assignment is C, instead of the usual finalize/copy/adjust. This would allow the reuse of levels of indirection allocated for the left-hand side, rather than having them released as part of a call on Finalize. Alternatively, this matching-constraint assignment could be specified using an operational attribute, and then it would allow assignment statements to be user-defined for any kind of type, even when constraints match. I can't see much value for a non controlled type, but because this is only for assignment statements, it could be relatively easily implemented without requiring any of the cleanup lists or other per-object overhead associated with controlled types. **************************************************************** From: Robert Duff Sent: Sunday, January 6, 2002 10:25 AM > Do we really have Ada users here? In my experience the answer is close > to no :0-) I am an Ada user, among other things. **************************************************************** From: Pascal Leroy Sent: Monday, January 7, 2002 4:05 PM > >>I asked several Ada users (at least one of whom is here) > > Do we really have Ada users here? In my experience the answer is close to no :0-) I believe that I am the user to whom Randy is alluding. He floated this idea to me, and it looked appealing. It is clear that there are difficulties, but somehow I would like to give more consideration to the "implicit conversion" idea. But then I'm not sure if I qualify as an Ada user in Robert's view... (Although I'd say that if there are no Ada users on the Ada Comment list, we have a problem.) > x := To_String (b); > > or > > x := Image (b); Examples that don't have any context are not too convincing, one way or another. Take a concrete example, that of Ada.Strings.Unbounded. You typically have to write something like: S := Ada.Strings.Unbounded.To_String ("hello"); This can be shortened somewhat with renamings or use clauses (but remember that many people object to use clauses) but complicated expressions quickly become unreadable. For instance: package ASU renames Ada.Strings.Unbounded; Hello : constant array (Language) of ASU.Unbounded_String := (English => ASU.To_String ("hello"), French => ASU.To_String ("bonjour"), ... Italian => ASU.To_String ("buongiorno")); All these function calls are noise for the reader, much like explicit conversions are in some other contexts. For all practical purposes, a string and an unbounded string are really similar abstraction, with a 1-to-1 mapping. So I should be able to write: S := "hello"; Hello : constant array (Language) of ASU.Unbounded_String := (English => "hello", French => "bonjour", ..., Italian => "buongiorno"); This is considerably more readable. Of course, it could be misused to produce obfuscated programs, but many features of Ada have that property, so I cannot get too excited. What is important here is a good programmer is able to produce a source text which is much easier to read. **************************************************************** From: Robert Dewar Sent: Monday, January 7, 2002 10:17 AM Pascal, I am afraid you do not qualify as an Ada user :-) If you are an Ada user, then so am I (I have written probably half a million lines of commercially used code in Ada), but we are too contaminated with our language lawyer hats :-) In fact I think ada comment should NOT be used for general discussions o ideas, but rather for submissions of ideas, and the discussions should take place elsewhere, either on CLA for general chat, or on the ARG list for serious language work. There is a danger of comment becoming an Ada chit chat list, in which case we will need to constitute another separate formal list for language suggestions and comments on the standard. **************************************************************** From: Arnaud Charlet Sent: Monday, January 7, 2002 5:13 AM > All these function calls are noise for the reader, much like explicit > conversions are in some other contexts. For all practical purposes, a string > and an unbounded string are really similar abstraction, with a 1-to-1 mapping. > So I should be able to write: > > S := "hello"; > > Hello : constant array (Language) of ASU.Unbounded_String := > (English => "hello", French => "bonjour", ..., Italian => "buongiorno"); You can already achieve a similar level of readability by overloading functions "-" or "+", which would give you: Hello : constant array (Language) of ASU.Unbounded_String := (English => -"hello", French => -"bonjour", ..., Italian => -"buongiorno"); Which is as readable as your version, and makes it clear that an operation occurs. **************************************************************** From: Pascal Leroy Sent: Tuesday, January 8, 2002 3:13 AM I have heard this argument many times before, but sorry, I don't buy it. Readability is not only based on the number of characters it takes to write something (otherwise C would be the most readable language on this planet ;-). It is essential for the syntax of a construct to make it clear what the underlying semantics are. I don't see this in the above code fragment. I think it's useful to discuss "-" and "+" separately. I suspect that you chose "-" above because it is syntactically less obtrusive than "+", but it is completely inappropriate here. Your example is akin to declaring a "+" operator with multiplicative semantics. Unary "-" has the intuitive meaning of 'complementarity' or 'negation'. When I am reading the above code fragment, I would expect the strings to be somehow 'negated' (whatever that means). So I would have to go to the specification of "-", where hopefully I would find a big blinking comment saying "beware, this is merely a conversion operation"; or worse, if the specification doesn't have such a comment, I would have to look at the body of "-" to understand what is going on. So much for readability! The problem here is that the intent of the writer of the code is not at all obvious to the reader, because of the poor choice of identifiers. The case of "+" is different, because "+" conveys the meaning of an identity function. So yes, in Ada 95 this is a reasonable workaround, although it still looks kludgy to me (you will notice for instance that the authors of the predefined units, in their infinite wisdom, did not use this coding style, but provided functions like To_String). The question here is, why do I have to write this silly + character? What does it buy me? > Which is as readable as your version, and makes it clear that an operation > occurs. I don't understand the fuss about "making it clear that an operation occurs". We have many instances in Ada 95 where it is not explicit that an operation occurs (controlled types, user-defined storage pool) and that's perfectly fine. Once you trust that the abstraction you are using was written in a reasonable way, you may assume that each assignment will do the right thing (e.g. in terms of storage management) and concentrate on the meaning of the code you are reading. For this reason, controlled types (and others things) make Ada 95 more readable than Ada 83. User-defined implicit conversions would play the same role in the sense that they would remove clutter from client code. **************************************************************** From: Arnaud Charlet Sent: Tuesday, January 8, 2002 1:15 PM > I think it's useful to discuss "-" and "+" separately. It is interesting to note that if I remember correctly, adding a new kind of operator that users could redefine to perform conversions was discussed during the design of Ada 95, but the outcome was that since there were already "-" and "+", it wasn't worthwhile. Now maybe things have changed since then, that's possible. > The question here is, why do I have to > write this silly + character? What does it buy me? Or the question could be "does adding this new capability bring us significantly, given that there are already reasonable (even if you will always find arguments to disagree) options ? It is a matter of point of view, really. Again, it would be nice to have the point of view of real Ada users here, which we haven't got so far. **************************************************************** From: Simon Wright Sent: Monday, January 14, 2002 3:33 AM > Again, it would be nice to have the point of view of real Ada users > here, which we haven't got so far. In our experience, untidy user-defined conversions occur most often when trying to produce strings. So a user-defined 'Image would be handy (no more than that): function My_Image (This : My_Type) return String; for My_Type'Image use My_Image; No feeling either way on constructors. -- Simon Wright Email: simon.j.wright@amsjv.com Alenia Marconi Systems Voice: +44(0)23 9270 1778 Integrated Systems Division FAX: +44(0)23 9270 1800 ****************************************************************