!standard 3.08(12) 03-09-17 AC95-00081/01 !class amendment 03-09-17 !status received no action 03-09-17 !status received 03-09-13 !subject Components of an array type !summary !appendix !topic Proposal for a "(null array)" aggregate !reference RM95-4.3.3(2) !from Niklas Holsti 03-09-26 !keywords null arrays, array aggregates, private types !discussion Summary The expression '(null array)' should be allowed as an array aggregate and understood to mean an array of the type and dimensions expected in the context, with null index ranges. Existing similar constructs 1. '(null record)' for record aggregates with no components. 2. The expression "" for null strings. Expected impact on compilers Mainly syntactic sugar. Possibly a new place where overloading resolution is needed, but then the existing array-aggregate notations can also include overloaded parts and need resolution. Motivation Currently, a null array aggregate must be written with an explicit specification of the null index range and the value of the non-existent array component. For example, given type Vector_Type is array (Positive range <>) of Float; a null Vector_Type aggregate can be written as (1 .. 0 => 0.0) The drawbacks of this are: a) The index range is usually irrelevant, but must be given. b) The component value is certainly irrelevant, but must be given. Point (b) is particularly troublesome when the component type is a private type or a private formal generic parameter. Examples follow. Example 1: An array with a private component type. Assume the following package declaration: package Secrets is type Secret is private; type Secret_List is array (Positive range <>) of Secret; procedure Publish (My_Secrets : in Secret_List); private ... end Secrets; A client of Secrets that wants to publish an empty list of secrets must currently declare a dummy variable with an apparently undefined initial value and probably irrelevant index bounds: Null_List : Secret_List (1 .. 0); ... Publish (My_Secrets => Null_List); The proposal would allow simply Publish (My_Secrets => (null array)); which gets rid of the irrelevant index bounds 1 .. 0 and the apparent undefined value of the Null_List variable, and their potential to confuse the human reader and analysis tools. In the current standard, the provider of the private type may try to help its clients to create empty lists of secrets by defining a dummy constant secret value: package Secrets is ... No_Secret : constant Secret; ... private ... end Secrets Then, a client can publish an empty secrets list by Publish (My_Secrets => (1 .. 0 => No_Secret)); However, in the private part of the Secrets package declaration there must be a definition of No_Secret, of the form No_Secret : constant Secret := ; The is often an aggregate, and often this aggregate uses private types defined in other packages, forcing also these packages to provide dummy constants of these types, etc. The contagion spreads... Example 2: Formal generic type parameter Assume the following declaration of a package that implements flexible but bounded one-dimensional arrays, generic in the index type, component type and a normal (fixed-length) array type: generic type Index is (<>); type Component is private; type Vector is array (Index range <>) of Component; package Bounded_Vectors is type Bounded_Vector (Max_Length : Natural) is private; ... function To_Vector (Item : Bounded_Vector) return Vector; -- Converts the bounded vector to an ordinary vector. ... end Bounded_Vectors; Depending on the services provided in the package, there may be several places in the package body where a null Vector is needed, but again a null array aggregate cannot be written because it needs to have a dummy Component value, which is not available. The body must resort to apparently undefined null Vector variables, as in Example 1. If a new formal parameter for a dummy Component is added to the generic formals, the instantiation must provide a concrete value. If the type is actually private even at the instantiation point, or contains private components, the contagion spreads as in Example 1. Minor things The qualified form Some_Array_Type'(null array) should also be allowed. The issue of multi-dimensional arrays may need more thought. It seems more likely that the actual index ranges are significant for multi-dimensional null arrays than for single-dimensional null arrays; it may be significant to know which dimensions are null ranges. This can be expressed by the ordinary aggregate notation. The '(null array)' notation should be usable as a subaggregate, which allows the index range of the "top" dimensions to be specified. For example, a two-dimensional array aggregate with the first index range 3 .. 7 and the second index range null could be expressed as (3 .. 7 => (null array)) The analogous aggregate with the first index range null and the second index range 3 .. 7 cannot be expressed with the '(null array)' form, but has to be written in the currently accepted form and must specify both index ranges and a dummy component value. Potential problems 1. Although the index range of a null array is usually irrelevant to the application program, clearly '(null array)' must have some First and Last attributes. What should they be? Can this be solved in the same way as for an array aggregate in positional notation? 2. If the index type contains only a single value (for example, an enumeration with a single literal) it is not clear to me if null arrays can exist. Perhaps '(null array)' should be illegal or cause a bounded error in such cases. **************************************************************** From: Adam Beneschan Sent: Friday, September 26, 2003 3:08 PM > !topic Proposal for a "(null array)" aggregate > !reference RM95-4.3.3(2) > !from Niklas Holsti 03-09-26 > !keywords null arrays, array aggregates, private types > !discussion > > Summary > > The expression '(null array)' should be allowed as an array aggregate > and understood to mean an array of the type and dimensions expected > in the context, with null index ranges. It seems to me that this proposal has come up before, possibly before the Ada 95 standard was written, and was rejected. Personally, I think it's a good idea. I do find myself writing things like (1 .. 0 => blah-blah-blah) often, or defining a *variable* with a null range (which I can't define as a constant, even though it is a constant), and it seems silly. > Potential problems > > 1. Although the index range of a null array is usually irrelevant > to the application program, clearly '(null array)' must have > some First and Last attributes. What should they be? Can this > be solved in the same way as for an array aggregate in > positional notation? It probably can be solved in the same way as for the null string literal. For string literals, 4.2(10) says "The bounds of this array value are determined according to the rules for positional_array_- aggregates (see 4.3.3), except that for a null string literal, the upper bound is the predecessor of the lower bound. 4.3.3(26) says that in such a case, the lower bound is "that of the corresponding index range in the applicable index constraint, if defined, or that of the corresponding index subtype, if not". The index range of a null array could be determined in the same way. (If (null array) is allowed to refer to an entire multi-dimensional array, this would apply to each index range.) The fly in the ointment here is that it doesn't handle cases in which the lower bound of the index subtype has no predecessor. In fact, this is already the case with string literals: procedure Test89 is type Str is array (Integer range <>) of Character; procedure Proc (S : Str) is begin ... end Proc; begin Proc (""); end Test89; This program is illegal, because for the array represented by the null string literal, the language rules say the lower bound is Integer'First and the upper bound is the predecessor of the lower bound, which does not exist. (See also AI95-138.) This feature is unlikely to cause a problem for string literals, since the predefined String type has an index range of Positive. However, it would cause serious problems for a (null array) type, since it would be much more common to use this for an array whose index subtype is Integer (or some other integer type) or an enumeration type. If (null array) is added to the language, I'd propose modifying 4.3.3(26) so that if there is no applicable index constraint, the lower bound is that of the corresponding index subtype, except that in the case of a null array aggregate or null string literal, if the lower bound of the corresponding index subtype has no predecessor, then the lower bound of the aggregate is the *successor* of the lower bound of the corresponding index subtype. If, in this case, the lower bound has no successor, Constraint_Error would be raised, as specified in 3.5(22). (We could make 4.9(34) apply too, but we'd have to modify the rest of 4.9 to indicate when (null array) would be considered a "static expression".) This would take care of the next issue: > 2. If the index type contains only a single value (for example, > an enumeration with a single literal) it is not clear to me > if null arrays can exist. Perhaps '(null array)' should be > illegal or cause a bounded error in such cases. More likely, "should be illegal or cause Constraint_Error to be raised". **************************************************************** From: Randy Brukardt Sent: Monday, September 29, 2003 7:55 PM AI-287 gives us <> to mean the default value of a component. So (1..0 => <>) is a null array. It's a minor pain to look up the index subtype (but I usually just use 1..0 and see if the compiler chokes), but it's not that big of a deal. The alternative is writing a number of pages of RM rules to make these useful (and then having people confused by the rules, whatever they are). Hard to say if it is worth it. **************************************************************** From: Tucker Taft Sent: Monday, September 29, 2003 8:16 PM Good point. That seems to answer the need for a null array adequately. Interestingly, "(null record)" can presumably be represented with "(others => <>)" obviating the need for the special "(null record)" syntax as well. **************************************************************** From: Gautier de Montmollin Sent: Tuesday, September 30, 2003 5:07 AM # AI-287 gives us <> to mean the default value of a component. So # (1..0 => <>) # is a null array. It's a minor pain to look up the index subtype (but I # usually just use 1..0 and see if the compiler chokes), but it's not that big # of a deal. But it is still a tricky workaround for a missing expression (with the exception of the String type). Try to imagine you just discover Ada now... # The alternative is writing a number of pages of RM rules to make these # useful (and then having people confused by the rules, whatever they are). # Hard to say if it is worth it. If the (null array) corresponds, say, to an array expression with one dimension having a null range, then it *should not* take pages. On the other hand, if it you have to write pages, it means that the null array, including in the present workaround form(s), is an undocumented feature of Ada95 which should be defined in the next standard! In both cases it is worth... **************************************************************** From: Randy Brukardt Sent: Tuesday, September 30, 2003 5:48 PM Gautier de Montmollin said: > But it is still a tricky workaround for a missing expression (with the > exception of the String type). Try to imagine you just discover Ada now... The standard way to write array literals is tricky??? > # The alternative is writing a number of pages of RM rules to make these > # useful (and then having people confused by the rules, whatever they are). > # Hard to say if it is worth it. > > If the (null array) corresponds, say, to an array expression with one > dimension having a null range, then it *should not* take pages. Please go back and read Adam's message; he explains why the rules used for string literals or array aggregates won't work for (null array). > On the other hand, if it you have to write pages, it means that the null > array, including in the present workaround form(s), is an undocumented > feature of Ada95 which should be defined in the next standard! In both cases > it is worth... The existing rules are fine for the existing forms; there's no problem with the RM. (null array) is quite different. **************************************************************** From: Gautier de Montmollin Sent: Tuesday, September 30, 2003 9:10 PM # > But it is still a tricky workaround for a missing expression (with the # > exception of the String type). Try to imagine you just discover Ada now... # The standard way to write array literals is tricky??? The standard way to write *empty* array literals *is* tricky. I did search in vain in the RM a mention of it (how do they compare ? do they have an address ?) or at least an example. Is it documented somewhere ? [...] # Please go back and read Adam's message; he explains why the rules used # for string literals or array aggregates won't work for (null array). You are right. I just waked up dreaming of Matrix'((null array)) and things like (others=>(null array)), (null array). Yes, it will take lines. But yes, it is worth the effort. Ada should have a proper expression for an empty set (mathematically speaking)! **************************************************************** From: Nick Roberts Sent: Tuesday, September 30, 2003 10:37 PM If we introduce (null array) and define it as strictly equivalent to (A..B => <>) where A is S'First and S is the expected subtype and B is S'Pred(A), that shouldn't be too difficult to define. It would not be permitted for multidimensional arrays. RM95 4.2 (10) could be very slightly simplified to define a null string literal as equivalent to (null array). It would have the consequence that if A turns out to be the first value of the underlying type (so it has no predecessor), the construct is illegal (and raises Constraint_Error). I think this would be just right; in such cases you must supply the bounds explicitly. You must supply the bounds for multidimensional arrays. For example: type Day is (Mon, Tue, Wed, ..., Sun); -- RM95 3.5.1 (14) subtype Weekday is Day range Mon..Fri; -- ibid (16) subtype Weekend is Day range Sat..Sun; type Basic_Pay is array (Day range <>) of Money; type Piece_Pay is array (Weekday range <>) of Money; type Overtime_Pay is array (Weekend range <>) of Money; No_Basic: Basic_Pay := (Tue..Mon => <>); -- (null array) would be illegal No_Piece: Piece_Pay := (Tue..Mon => <>); -- (null array) would be illegal No_Overtime: Overtime_Pay := (null array); -- legal (bounds Sat..Fri) No_Name: String := (null array); -- exactly the same as "" It would be syntactic sugar, but we could say that Ada has a sweet tooth. I think it's interesting to note that it would be pointless to declare any of the above four variables constant; no assignment to any of them is possible. If we did, the 'constant' would, in a way, be syntactic sugar. It's hard to avoid. Also, if we were to declare: type Day is (Mon, Tue, Wed, ..., Sun, 'X'); ... then we could legally write: No_Overtime: Overtime_Pay := ""; in Ada 95, showing, I think, how syntactic sugar can always be misused. **************************************************************** From: Pascal Leroy Sent: Wednesday, October 1, 2003 6:28 AM Nick proposed: > If we introduce (null array) and define it as strictly equivalent to (A..B > => <>) where A is S'First and S is the expected subtype and B is S'Pred(A), > that shouldn't be too difficult to define. If S is a modular type, this would not have the effect that you intend, I think, unless you and I have very different notions of what a null array is. More seriously, a construct that would raise Constraint_Error more often than not, and would do so not to indicate the run-time violation of some invariant, but because of definitional problems, would be actively harmful in my opinion. I see this thread as unimportant, and I'd rather let that sleeping dog lie. But if we were to ever pursue this proposal, we should properly solve the issues related to multidimensional aggregates and to index bounds. **************************************************************** From: Nick Roberts Sent: Wednesday, October 1, 2003 9:10 AM > If S is a modular type, this would not have the effect that > you intend, I think, unless you and I have very different > notions of what a null array is. Ahem, yes. Well, it goes to show that the most difficult thing about defining things is not making mistakes. > More seriously, a construct that would raise Constraint_Error > more often than not, and would do so not to indicate the run- > time violation of some invariant, but because of definitional > problems, would be actively harmful in my opinion. I contend that the conditions that would cause C_E to be raised would be unusual in practice. I think you'll find, in working code, that most unconstrained array types (which have 'range <>' for the indices) have index subtypes of or based on Standard.Positive. I think multidimensional arrays could be reasonably easily accommodated, but this would add a little more definition work. > I see this thread as unimportant, and I'd rather let that > sleeping dog lie. Woof! > But if we were to ever pursue this proposal, we should > properly solve the issues related to multidimensional > aggregates and to index bounds. Pascal, you seem to rebuff the more elaborate suggestions by saying they involve too much work, and then rebuff the simpler suggestion by saying it's insufficiently elaborate. If you think that there is a need for a new construct that provides null arrays for all kinds of array, how can you then argue that there is no need for a new construct that provides null arrays for some kinds of array? It doesn't make sense. **************************************************************** From: Adam Beneschan Sent: Wednesday, October 1, 2003 11:04 AM > I contend that the conditions that would cause C_E to be raised would be > unusual in practice. I think you'll find, in working code, that most > unconstrained array types (which have 'range <>' for the indices) have index > subtypes of or based on Standard.Positive. I think multidimensional arrays > could be reasonably easily accommodated, but this would add a little more > definition work. In one medium-sized Ada program I wrote, I found that when it declared 178 unconstrained array types, 158 used Natural or Positive as the index type (I use Natural much more often than Positive), and 20 used something else. A few of these 20 were enumeration types, and some were other integer types for which I didn't bother to declare a "positive" subtype or the like. But I agree with Pascal that if this syntactic sugar were to be added, it would be best to make it work for all cases (in fact, I think it would be "best" if the null string literal were legal for string types where the index subtype is a modular type [AI-138], since not having it work seems to be a rather surprising result). Even though most of the array types I defined in my program wouldn't cause a problem, it would still be annoying to have things not work in the other 20 cases where they would, and to have to define fake subtypes of enumeration types just to make this work so that the compiler would get the bounds of a (null array) right in a case where I couldn't care less what the bounds are. The fact is, in most cases where I use a positional array aggregate in a statement (i.e. not in an initializer), I really don't care what bounds the compiler gives the array; the routine it's being passed to is just going to use Arr'First and Arr'Last anyway. The same would apply to a null array, and I wouldn't appreciate having the language force me to care about something irrelevant. Anyway, that's just my feeling as an Ada programmer. I realize there are costs involved in making things work the "best" way. **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 1, 2003 1:28 PM Adam Beneschan wrote: Consider type Index is (One); type Str is array (Index range <>) of Character; type Num is array (Index range <>) of Integer; Are we supposed to be able to write Null_Str : constant Str := ""; ? Other messages in this thread seem to imply that this is legal. I ask because GNAT 3.15p rejects this: test_str.adb:7:31: null string literal not allowed for type "Str" defined at line 4 test_str.adb:7:31: static expression raises "Constraint_Error" so I'm wondering if this is an error in GNAT or a misunderstanding of the ARM. **************************************************************** From: Adam Beneschan Sent: Wednesday, October 1, 2003 1:53 PM No; and under the current rules this would be illegal even if Index had more than one value, e.g.: type Index is (One, Two, Five); So GNAT is correct. Furthermore, unless I missed something, I don't think anyone has implied that this is legal. What I've said is that perhaps the rules should be changed to make this legal (big emphasis on "perhaps"). However, even with the change to 4.3.3(26) I mentioned as a possibility, this could *not* make your above example legal, in which Index is an enumeration with just one element. Since there is only one possible value for Index, there is no way that an array of type Str or Num can have a 'Length other than one. Hope this clarifies things, **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 1, 2003 2:39 PM > No; and under the current rules this would be illegal even if Index > had more than one value, e.g.: > > type Index is (One, Two, Five); Three, Sir! > > So GNAT is correct. Furthermore, unless I missed something, I don't > think anyone has implied that this is legal. Nick Roberts wrote > Also, if we were to declare: > > type Day is (Mon, Tue, Wed, ..., Sun, 'X'); > ... > > then we could legally write: > > No_Overtime: Overtime_Pay := ""; > > in Ada 95, showing, I think, how syntactic sugar can always be misused. But I see now that Overtime_Pay is indexed by a subtype of Day excluding Mon, so this is a different case. > What I've said is that perhaps the rules should be changed to make > this legal (big emphasis on "perhaps"). However, even with the change > to 4.3.3(26) I mentioned as a possibility, this could *not* make your > above example legal, in which Index is an enumeration with just one > element. Since there is only one possible value for Index, there is > no way that an array of type Str or Num can have a 'Length other than > one. It seems to me that "range <>" should alway include a null range. The problem is not the concept, but that the language has no way to express that null range. It would be nice to be able to write a zero-length value for any unconstrained array type. The question is if it's worth the effort. > Hope this clarifies things, Yes, thanks. I was working from memory, and misremembered. ****************************************************************