!standard 7.5(2,1/2) 07-05-04 AC95-00142/01 !class confirmation 07-05-04 !status received no action 07-05-04 !status received 07-04-30 !subject Limited constants considered limiting !summary !appendix From: Randy Brukardt Date: Monday, April 30, 2007 6:25 PM I've been thinking about the use of limited types, and in particular the construction of packages using limited types. I've noticed a ramification of the rules that seems somewhat unintuitive, although I'm unconvinced its wrong. I thought I'd toss it out here and let the rest of you comment on it. For non-limited types, the advice has always been to declare constants with values that are commonly used or have special meanings, rather than spreading them all over the place. That has generally proved to be good advice. That was difficult to do in Ada 95. To do that for limited types, the only option was to declare a function; that had to return an existing object, so that had to be declared in the body. And that object could not be constant nor directly initialized, so it had to be initialized component-by-component in the package body's elaboration part. That's four separate places. Yikes! The Amendment eliminates the initialization issues and the need to have an existing object. It also appears to allow useful constants -- but those constants are much more limited than the function that they would replace. That's because a constant is not one of the things allowed in limited initialization expressions, while a function call is. That means that such a constant cannot replace a function (or aggregate) in initializing a larger object. For instance, taking an example from Claw: package Claw.Registry is type Key_Type is new Limited_Controlled with private; HKey_Current_User : constant Key_Type; function HKey_Local_Machine return Key_Type; ... end Claw.Registry; type Application_Key_Type is record Key : Key_Type; Company : Unbounded_String; Application : Unbounded_String; Version : Unbounded_String; end record; Janus_Ada : constant Application_Key_Type := (Key => HKey_Current_User, -- Illegal! Company => "RRS", Application => "Janus-Ada", Version => ""); CBuild : constant Application_Key_Type := (Key => HKey_Local_Machine, -- OK. Company => "RRS", Application => "CBuild", Version => ""); The issue, of course, is that a limited object's value usually includes it's object identity. Since a constant is an object, it's value includes that identity, and thus it can't be composed into a surrounding composite object. From this, I have to conclude that designers need to avoid using limited constants unless they *only* want the constant to be used as a marker to operations. Otherwise, they should use a less limiting function (since it is usually the best practice to not constrain your clients unnecessarily). I found this fact rather counter-intuitive, although that may be mainly because we've never had limited constants to reason about in the past. In any case, it seems like something that book authors ought to be mentioning. **************************************************************** From: Tucker Taft Date: Monday, April 30, 2007 7:59 PM The most fundamental property of a limited type is that it can't meaningfully be copied. If it could then one could make the type non-limited, and make the type Controlled and provide an "Adjust" routine if appropriate to do whatever fixups are required upon copying. The example you give below seems "unintuitive" to me only because the type you make limited, Key_Type, doesn't seem to fit the definition of a limited type, since it seems like it *is* meaningful to copy it. Can you explain the reason why you want it to be limited? **************************************************************** From: Randy Brukardt Date: Monday, April 30, 2007 8:21 PM A Key is very similar to a File (in that you open it, close it, read from it, etc.), and we followed a similar design to file handling for that reason. It also simplified the implementation (which otherwise would need messy reference counts or worse in order to determine when to free keys). But the use of the type seems irrelevant to me. The issue exists for any limited type that you might want to use as a component of a larger data structure. Or are you claiming that such types don't exist? If you replace "Key_Type" by "Ada.Text_IO.File_Type" in my example, you get a similar result. You wouldn't want Standard_Input to be a constant, because you then couldn't initialize a larger data structure with it (rather than opening a file with the component as a parameter). And there wouldn't be any other (language-defined) way to set such a component to Standard_Input (there is no language-defined way to open it). Maybe you find the ability to initialize a component in this way as odd, but it seems natural enough to me. I suppose you could claim that File_Type shouldn't be limited, but that train left a very long time ago... **************************************************************** From: Matthew Heaney Date: Monday, April 30, 2007 8:56 PM > Maybe you find the ability to initialize a component in this way as odd, but > it seems natural enough to me. Couldn't you pass the key constant to a copy constructor, e.g. function New_Key (Key : Key_Type) return Key_Type; That way you still wouldn't need to make the key type non-limited. **************************************************************** From: Randy Brukardt Date: Monday, April 30, 2007 9:13 PM Sure, but it still requires a function (a copy constructor) that you wouldn't necessarily have. And then you might as well forget the constant altogether in favor of a constructor function, because that does both jobs (providing a name for direct use, and providing a way for initialize-in-place). **************************************************************** From: Tucker Taft Date: Monday, April 30, 2007 9:07 PM I'm afraid I still don't quite get what it means to "open" a Key. But perhaps you are saying that it is fine to copy a Key when it is *not* open, as it just represents a "name." Once it is open, it has more state which would be awkward to allow copying, but it is OK to copy when it is closed. Perhaps the same could be said of a File_Type, though they don't contain much information of interest when closed. In any case, the model seems to be that either you allow copying or you don't in Ada for a given type, and that isn't state dependent. If you wanted to make it state dependent, you would have to make it non-limited Controlled and have Adjust fail or somehow reset the state if the current state is incompatible with copying. Another approach is to use two different types, a non-limited one which holds the copyable information content, and a limited one that need only exist while the object is in its non-copyable state. I don't see any solution for your concern without major revamping of the limited type model. **************************************************************** From: Randy Brukardt Date: Monday, April 30, 2007 10:11 PM > I'm afraid I still don't quite get what it means to > "open" a Key. It associates a name with the key, and opens the associated resource. There are also some predefined keys that are always open (they form the root of the registry). It works exactly like a typical file system; thus the mapping is so apt. > But perhaps you are saying that it is > fine to copy a Key when it is *not* open, as it just > represents a "name." Once it is open, it has more state > which would be awkward to allow copying, but it is OK > to copy when it is closed. Perhaps the same could be > said of a File_Type, though they don't contain much > information of interest when closed. I'm not talking about copying anything in the limited type! I don't know where you get that idea from; obviously I'm not communicating something. The constants/functions represent the predefined keys, which you don't have to open. In that sense, they work exactly like Standard_Input and Standard_Output. You can have as many of them as you need, just as with null (and Standard_Input and Standard_Output); all you are doing is setting the mapping of the limited object to the predefined handle (as opposed to opening a new one). All I'm interested in is naming those predefined keys, preferably to provide maximum functionality. I found it odd that a function would allow creating new instances of the predefined keys, while it is not possible to do that with a constant, even if both simply use the same aggregate. I've always thought of constants and functions as interchangable in the absense of parameters and/or code; apparently that is not true. > In any case, the model seems to be that either you allow > copying or you don't in Ada for a given type, and that > isn't state dependent. If you wanted to make it state > dependent, you would have to make it non-limited Controlled > and have Adjust fail or somehow reset the state if > the current state is incompatible with copying. Of course, but I'm not copying anything in this example! (Certainly not at the client level; what happens at the implementation level is irrelevant.) I'm creating objects, and it's weird that you can't use constants to create objects, but an absoletely equivalent function can be used. These things are essentially equivalent in my view: constants are more of a name for a value than an object that can mutate. So I'm now beginning to think that allowing limited constants was a mistake, because they have none of the expected behavior of a constant (especially given that they can mutate). Too late... ... > I don't see any solution for your concern without major > revamping of the limited type model. We do that frequently, so that doesn't bother me. But I fail to see what "copying" you are talking about, so I think you're solving the wrong concern. I realize that there may be some case where the object identity is so strong that you would not want any predefined constructors, but you'd still want predefined objects, and the current scenario would work for that. But I've never run into such a case. Much more typically, the underlying implementation could support copying if you really wanted it (that's even true for most implementations of tasks, and surely for protected objects without entries -- the latter really shouldn't have been limited in the first place). The fact remains that if you have something predefined that could be employed as an initial state, then it has to be defined as a function. If, OTOH, you have something that could be employed as an object of its own, it could be defined as a function or as a constant. That means that the only time that you would ever want to use a constant is if that predefined value cannot be used as an initial state. Package designers need to understand that. In any case, I don't think any language redesign is warranted. (Just because I've never seen a need for a limited constant that wasn't an also an initializer, doesn't mean that it doesn't exist.) But I do think that more education is necessary: limited types are very unlike non-limited types, and nothing that we've done in the Amendment has changed that in the least. **************************************************************** From: Tucker Taft Date: Tuesday, May 1, 2007 8:01 AM > ... > I'm not talking about copying anything in the limited type! I don't know > where you get that idea from; obviously I'm not communicating something. That is probably true. From what I see, you are clearly trying to create a new object at least part of which is a *copy* of an existing limited constant object. A limited constant object is first and foremost an object. If you define its value with an aggregate I can see how you might think of it as a kind of "macro" for the aggregate. However, it might contain a task or a protected component or a "<>" initialized component with a non-trivial "Initialize" procedure, so it is not trivial to know how to create yet another object using the constant as a template. Furthermore, even though it is declared a constant, its initialization might allocate non-constant heap objects that are pointed-to from the constant. Copying such a constant object might quite easily create undesirable sharing. In this particular case, there is nothing inside your limited constant object that has any difficult-to-copy properties, but I don't see how the language could know that easily. An aggregate is different from a constant, in that it is an executable construct that builds a new object. A constant already is an object, and trying to use it as a template for how to build another one isn't well defined when the type is limited, since there is no well-defined "copy" operation. **************************************************************** From: Pascal Leroy Date: Wednesday, May 2, 2007 4:07 AM > I've always thought of constants and functions as interchangable > in the absense of parameters and/or code; apparently that is not true. That has never been true, Randy. Among other things, functions are overloadable and they are inherited. This often makes them preferable to deferred constants. And of course, prior to Ada 2005, you didn't have limited constants but you had functions returning limited types (I realize that the return-by-reference mechanism was not ideal, but as you pointed out it was good enough for the predefined files in Text_IO and friends). ****************************************************************