!standard 4.3.3(2) 18-01-22 AI12-0248-1/01 !standard 4.3.5(0) !class Amendment 18-01-22 !status work item 18-01-22 !status received 18-01-15 !priority Low !difficulty Easy !subject Null array and empty container aggregates !summary ** Missing !problem The way to specify a null array in Ada requires specifying a low and high bound and an expression, none of which help in understanding that this is a null array. Hence, we would prefer a single notation, analogous the "" for empty strings, that can be used with any array type, or perhaps more generally, any container type, to represent an empty/null array or an empty container. !proposal Based on various suggestions from comp.lang.ada and elsewhere, we propose to allow (null array) as a representation for a null array, and (null T) as a representation for a null container, a null array, or a null record, where T is an array type, a null record type, or a type with an Aggregate aspect specified (see AI12-0212-1). As part of this AI, we also propose a modest change to the semantics for "" so that it doesn't inevitably raise Constraint_Error if the low bound of the index subtype is the same as the low bound of the index type. For "", (null array), and (null T) where T is an array type, we propose to specify that the bounds are Index_Subtype'First .. Index_Subtype'First-1, unless Index_Subtype'Base'First = Index_Subtype'First. In this latter case, we propose the bounds are Index_Subtype'First+1 .. Index_Subtype'First. This change means that these constructs would only raise Constraint_Error if Index_Subtype'Base'First = Index_Subtype'Base'Last, which is a bit of a pathology. In the rare case when Index_Subtype'First is dynamic, there would be some modest overhead to check against Index_Subtype'Base'First (which is always static) to determine the bounds to use, though it would be quite similar to the current check that the high bound is not less than Index_Subtype'Base'First. Again, this is pretty much of a pathology. For types with the Aggregate aspect specified, (null T) would simply return an anonymous object initialized from the result of evaluating, or calling, the specified Empty constant/function (see AI12-0212 for a description of the proposed Aggregate aspect). !wording ** TBD !discussion ** TBD. !ASIS [Probably some ASIS routine would be needed for the new syntax possibilities.] !ACATS test ACATS B and C-Tests are needed to check that the new capabilities are supported and illegal cases detected. !appendix !topic Using "(null array)" to denote a null (empty) array !reference Ada 2012 RM4.3.3 !from Niklas Holsti 18-01-15 !keywords null array aggregate !discussion Currently, the only way to write a null (empty) one-dimensional array aggregate is to write an index range where the lower bound is greater than the upper bound. Except for strings, that is, where the null string literal "" can be used. If the index type of the array is not exactly known (e.g. in a generic context), or where the programmer does not want to depend on the actual bounds of the index type, the null array aggregate must be written rather heavily in the form (Index_Type'First .. Index_Type'Pred (Index_Type'First)) => <>) or (if Index_Type'First equals Index_Type'Base'First) in the form (Index_Type'Succ (Index_Type'First) .. Index_Type'First) => <>) Following both past and recent discussion on comp.lang.ada, I suggest that the syntax for array aggregates should be extended to allow the form "(null array)" to mean a null array of the type expected by the context. It should also be possible to qualify by the expected type, as in A'(null array) where A is some one-dimensional array type (usually unconstrained, but perhaps constrained to hold only empty arrays). In the comp.lang.ada discussion, an alternative suggestion was made, that an empty pair of parentheses, "()", should mean a null array. However, (null array) is more explicit, and similar to the existing (null record). For determinism, it would be good to define the index bounds of (null array). However, if we choose the same rules as for "", which corresponds to the first aggregate form, above, (null array) would raise Constraint_Error whenever the index type has the property that Index_Type'First = Index_Type'Base'First, which is very often the case when the index type is an enumeration type or a modular type. The second form, with lower bound Index_Type'Succ (Index_Type'First), therefore seems a better choice, although it means that "" and (null array) would not be exactly equivalent as string expressions, the former having the bounds 1..0 (for the standard String type) and the latter having the bounds 2..1. On the other hand, the second form of the aggregate raises Constraint_Error if Index_Type'First = Index_Type'Base'Last. It seems, then, that a more complex rule is needed, for example that the first of the above forms is used if Index_Type'First > Index_Type'Base'First, and the second form is used otherwise. This would make "" and (null array) equivalent. (Perhaps this more complex rule should also be used for "", which currently cannot be used for string types where Index_Type'First = Index_Type'Base'First.) In any case, if Index_Type'Base contains only one value, and thus does not permit a null range, (null array) must raise Constraint_Error, as would any attempt to evaluate a null array aggregate using existing syntax. The same notation, (null array), might be used for multi-dimensional arrays, defined to correspond to an aggregate with all index ranges null, choosing the index bounds in the same way as for a one-dimensional (null array). However, this would make (null array) raise Constraint_Error if some array dimension has an index type that does not permit a null range, while an array aggregate, of the ordinary form, could create a null array of the same type, without raising Constraint_Error, as long as some dimension of the array allows a null index range. Perhaps multi-dimensional (null array) could be defined to return an array where index ranges are null for those dimensions where this is possible (raising Constraint_Error if there are no such dimensions), and are singleton ranges for the other dimensions (that do not permit null index ranges). **************************************************************** From: Tucker Taft Sent: Monday, January 15m 2018 3:02 PM Thanks for the proposal. It looks good to me, and I like your suggestion to handle the special cases where Index_Subtype'First = Index_Type'First without raising an exception. Generalizing that to "" makes a lot of sense to me as well. The multidimensional array case seems less important, as multidimensional arrays are rarely null in my experience. If they are felt to be worth supporting, I could imagine using "null array" for each dimension that you want to be null, such as: (null array, null array) or (X, null array) The advantage of the above is that the number of dimensions is then quite visible in the source. But in any case, this seems like an option, and not as important as the single-dimensional case, where null arrays do arise quite often. Note that I would still prefer a notation that would generalize to empty containers, such as "(<>)" but I understand there are some objections to that as well. **************************************************************** From: Luke A. Guest Sent: Monday, January 19, 2018 4:35 PM > The multidimensional array case seems less important, as multidimensional > arrays are rarely null in my experience. If they are felt to be worth > supporting, I could imagine using "null array" for each dimension that you > want to be null, such as: > > (null array, null array) > > or > > (X, null array) > Why not (others => null array)? **************************************************************** From: Tucker Taft Sent: Monday, January 15, 2018 6:16 PM > Why not (others => null array)? Conceivably "(others => (null array))" would make sense for a two-dimensional array, but only if in a context where an applicable index constraint is present, and that is probably rarely the case when trying to create a null array. But you are right, (X, null array) or (null array, null array) doesn't really make sense either, since multidimensional arrays are not represented by a separate value for each dimension. I guess "(null array)" by itself would make sense if the first dimension can be null. If the first dimension cannot be null, then something like "(1 => (null array))" might make sense. Seems like it might be best to steer clear of multidimensional null arrays for now... **************************************************************** From: Jeff Cousins Sent: Monday, January 15, 2018 7:30 PM This is quite appealing, null array is quite clear what it means, just uses existing key words, and avoids the nasty low index greater than high index kludge. **************************************************************** From: Randy Brukardt Sent: Monday, January 15, 2018 11:19 PM > Thanks for the proposal. It looks good to me, and I like your > suggestion to handle the special cases where Index_Subtype'First = > Index_Type'First without raising an exception. Generalizing that to > "" makes a lot of sense to me as well. I'm not surprised that it looks good to Tucker -- he has a homework item to create a proposal on this line, so you've (Niklas) essentially done his homework for him. :-) > Note that I would still prefer a notation that would generalize to > empty containers, such as "(<>)" but I understand there are some > objections to that as well. I wonder if we could accommodate that by adding the form: (null subtype_mark) as an empty aggregate. That would allow: (null Vector) and (null Ordered_Map) The named type would have to be that of the aggregate as a whole. (I'd guess this would be allowed for any composite type for which an aggregate is allowed, so we'd allow arrays and records to be named this way as well.) This form seems consistent with the existing aggregate forms, and it is consistent with Ada's intention that empty things are written explicitly (and not simply by omission). I haven't been able to convince myself that this wouldn't cause some syntax problem, as (null) is a parenthisized access value. But I can't think of a problem off-hand, and we have similar syntax for null exclusions. Of course, "Empty_Map" is shorter than "(null Ordered_Map)", but there is definitely an advantage to being able to write everything consistently. **************************************************************** From: Tucker Taft Sent: Tuesday, January 16, 2018 11:02 AM > I wonder if we could accomodate that by adding the form: > > (null subtype_mark) > > as an empty aggregate. That would allow: > > (null Vector) > and > (null Ordered_Map) Yes, that seems like a useful generalization. > The named type would have to be that of the aggregate as a whole. (I'd > guess this would be allowed for any composite type for which an > aggregate is allowed, so we'd allow arrays and records to be named > this way as well.) Agreed. This might be a preferable way to handle multi-dimensional null array aggregates. E.g. "(null Matrix)" **************************************************************** From: Randy Brukardt Sent: Tuesday, January 16, 2018 3:56 PM > Yes, that seems like a useful generalization. Glad you liked the idea, but now you'll have to write it up as part of your homework. :-) **************************************************************** From: Tucker Taft Sent: Tuesday, January 16, 2018 4:15 PM Got it. **************************************************************** From: Niklas Holsti Sent: Wednesday, January 17, 2018 3:07 PM > I wonder if we could accomidate that by adding the form: > > (null subtype_mark) > > as an empty aggregate. That would allow: > > (null Vector) > and > (null Ordered_Map) Another syntax suggested on c.l.a was based on a new attribute for array types, such that subtype_mark'Null would mean a null array of this subtype. That would easily extend to containers: Vector'Null, etc. > This form seems consistent with the existing aggregate forms, and it > is consistent with Ada's intention that empty things are written > explicitly (and not simply by omission). I hope that the simple (null array) would still be allowed, because I think that null arrays are used mostly in two contexts where naming the subtype seems to me unnecessary. These two contexts are: - as the default value of a subprogram parameter, as in procedure Foo ( List : in List_Type := (null array); ... ); where the repetition of the subtype seems unnecessary: List : in List_Type := (null List_Type); - as the actual value of a parameter in a subprogram call, as in Other_Package.Foo (List => (null array), ...); where it seems to me unnecessary to force the client to name the parameter type: Other_Package.Foo (List => (null Other_Package.List_Type), ...); **************************************************************** From: Luke A. Guest Sent: Wednesday, January 17, 2018 10:26 AM ... > That would allow: > > (null Vector) > and > (null Ordered_Map) Why can't we overload null? You already have the left hand side of the assignment, so you know the type. The compiler when it sees "null" (no subtype mark) should then call a special null function which returns a null object. This could be implemented with the new expression functions which can be inlined. I just don't see the need for all this extra wordage for something you already know. -- overloaded function null returns subtype_mark is... function null returns Vector is... function null returns Ordered_Map is... **************************************************************** From: Randy Brukardt Sent: Wednesday, January 17, 2018 3:31 PM > -- overloaded > function null returns subtype_mark is... > function null returns Vector is... > function null returns Ordered_Map is... I don't see the point of this, either: every container already has an empty object named "Empty_xxx" (and it should have been named "Empty", IMHO). We don't need any shorthand specifically to get empty items. However, there was a desire from certain parties to have empty aggregates for these types. That implies aggregate syntax, which (null) is not and cannot be (it is a parenthesized expression). I was trying to find an acceptable solution for that desire -- ignoring the need part of it. Overloading null would take a lot of implementation work, because the current implementations of null are super-simple (generally, it just generates a zero value) and supporting anything else would require adding a lot of new mechanism. (Especially as a function call can be indexed, selected, and so forth -- the null node doesn't have those components in our implementation, and I'd expect that to be true in most implementations.) **************************************************************** From: Tucker Taft Sent: Wednesday, January 17, 2018 3:51 PM I also think merely "null" would suggest the wrong semantics. "Null" in my view means the object doesn't exist at all, whereas here we have a "real" array (or container), which just doesn't happen to have any elements. Those are quite different in my view. I realize that you could get used to anything, but the intuition, at least to me, of "null" is the wrong semantics. Also, as mentioned by Randy, this would be painful from an implementation point of view, and could in unusual cases create ambiguities, where you had two overloaded functions, one that took a pointer, and one that took an array. Currently these are distinguishable by the overloading rules, since aggregates and null are for non-overlapping classes of types. If "null" suddenly becomes legal for non-access types, you could create new ambiguities where before there were none. **************************************************************** From: Tucker Taft Sent: Friday, January 19, 2018 1:50 PM Here is an initial AI on the "(null array)" and "(null T)" notation discussed recently on comp.lang.ada and ada-comment. [This is version /01 of the AI - ED.] **************************************************************** From: Randy Brukardt Sent: Monday, January 22, 2018 9:17 PM You didn't provide wording (or a summary or discussion for that matter), so I can't tell whether it was omitted on purpose, but you didn't mention the Legality Rule that for "(null T)" - T has to be a subtype of the type of the aggregate. The type of an aggregate has to be determined from context regardless of the contexts, so if the contents identify type T1 and the type of the aggregate is T2, T1 made better be the same as T2. I suspect this is obvious to language lawyers (you're not proposing to change resolution), but not necessarily to casual readers. You also probably should mention that (null T) for a record type is equivalent to (null record) -- unless you had some other meaning in mind. [If T is a constrained record subtype such that the only needed components are discriminants, one could imagine it meaning that the discriminants are initialized from the constraints -- but that's probably not worth the wording headache.] **************************************************************** From: Randy Brukardt Sent: Monday, January 22, 2018 9:47 PM Additionally, you mentioned in e-mail about allowing "(null T)" for multi- dimensional arrays. While "(null array)" would only be for single-dimensional arrays. If that's still your intent, it should be mentioned, and a few words given about what it actually means for a multi-dimensional array (all bounds null required?? -- since only one dimension needs to be null to have a multi- dimensional array with no components). **************************************************************** From: Tucker Taft Sent: Wednesday, January 24, 2018 6:41 AM This AI is really more of a place holder, to allow ARG to discuss it before we commit to detailed wording. **************************************************************** From: Randy Brukardt Sent: Thursday, January 25, 2018 1:39 AM The Ada-Comment thread was enough to create a placeholder. I was expecting a write-up that covered all of the issues (there aren't a lot), even if wording was omitted. Or, better, just provided the wording -- it takes nearly as long to think about the issues as it does to write the wording for this "trivial" AIs. If the AI doesn't mention multi-dimensional arrays, I think there is little chance anyone will bring it up during the meeting. That's the kind of corner case that it likely to be interesting. (People rarely look at the mail threads during meetings, there isn't time.) ****************************************************************