!standard 3.6.1(3) 18-01-12 AI12-0246-1/01 !class Amendment 18-01-12 !status Hold by Letter Ballot (9-1-1) - 18-05-07 !status work item 18-01-12 !status received 18-01-03 !priority Very_Low !difficulty Medium !subject Fixed first indices for variable-length arrays !summary Add a syntax to define a subtype of an array that specifies the first index of an array value, without specifying the length of the array. !problem Ada supports arrays with arbitrary first indices (e.g., 1980 in "type Ada_Versions is array (1980 .. 2100) of Some_Type"). Subprogram parameters are *index-aware*, i.e., the subprogram inherits the caller's knowledge about the index. If the intended functionality of the subprogram depends on the first index, this is a benefit of Ada, where many other programming languages need complex workarounds. But frequently, a subrogram's intended functionality is independent from the first index, i.e., it is *index-agnostic*. In such cases, it is difficult to specify and verify that the subprogram's behaviour never depends on the array index, and proper black box testing in Ada would require a lot of additional test cases, compared to languages with a default first index (Java, C++, ...). This puts Ada's (otherwise well deserved) reputation as a language with exceptional support for the development of safe and secure systems into question. Example: The package Ada.Strings.Fixed has several functions of the form function Index (Source : String; Pattern : String; [other parameters]) return Natural; to search for the substring Pattern in the string Source. I.e., if P is a substring of S then S(Index(S, P) .. P'Length-1) = S. This is index-aware for Source, but index-agnostic for Pattern. For concreteness, consider P1: String(1..3) := "abc"; P2: String(Positive'Last-2 .. Positive'Last) := "abc". Whatever the value of Source is, the functionality requires Index(Source, P1, ...) = Index(Source, P2, ...), and proper black box tests should include test cases for this behaviour. !proposal (See Summary.) !wording Add in 3.6.1: index_semiconstraint ::= (expression .. <>); ** TBD. (The original proposal is seriously incomplete and incorrect, so I've omitted it here - Editor.) !discussion This defines a new kind of subtype, the semiconstrained constraint. (Hopefully, a better name is invented.) This will need definitions of compatibility and safisfies (??) as well as definitions of sliding. The original proposal suggested that perhaps only Base'First should be supported as the index, but that seems wrong with the proposed syntax. !examples subtype One_Based_String is String (1 .. <>); function Index (Source : String; Pattern : One_Based_String; [other parameters]) return Natural; subtype One_Based_Array is Some_Array (1 .. <>); procedure Sort (S: in out One_Based_Array); !ASIS [Not sure. It seems like some new capabilities might be needed for subtypes, but I didn't check - Editor.] !ACATS test ACATS C-Tests are needed to check that the new capabilities are supported. !appendix !topic Renaming a view conversion for nontagged types !reference Ada 2012 RM 8.5.1(4), 3.3(12), 4.6(5/2) !from Author Christoph Grein 17-11-30 !keywords renaming, view conversion !discussion From a discussion in comp.lang.ada: 8.5.1(4) The renamed entity shall be an object. 3.3(12) [An object is a] view conversion of another object. 4.6(5/2) A type_conversion whose operand is the name of an object is called a view conversion if both its target type and operand type are tagged, or if it appears in a call as an actual parameter of mode out or in out; other type_conversions are called value conversions. What are the reasons for the renaming of an untagged vew conversion to be illegal? Would it be an option for Ada 202x to change this? I cannot see a fundamental difference between the legal view conversion in a subprogram call and the illegal renaming of a view conversion. procedure Do_Stuffs (Data: in out General_Real_Array) is subtype One_Based is General_Real_Array (1 .. Data'Length); procedure Use_1_Based (X: in outOne_Based) is separate; Shifted:One_Basedrenames One_Based (Data); -- illegal because not tagged begin Use_1_Based (One_Based (Data)); -- view conversion end Do_Stuffs; **************************************************************** From: Tucker Taft Sent: Thursday, November 30, 2017 11:53 AM Untagged "view" conversions only exist in one context, namely as actuals for [in] out parameters. So any other use of a conversion for an untagged type is necessarily considered a value conversion. On the other hand, I agree that it would be convenient to be able to rename a "sliding" of an array. The whole distinction between "object" and "value" in the Ada reference manual seems to be less and less useful. A more relevant distinction is what is a constant vs. what is a variable, and even that seems to be more complex than necessary. It would seem to be easier to define what is *variable* rather than what is constant, since the default tends to be constant, for things like loop parameters, choice parameters, etc. 20-20 hindsight! In any case, the question would still remain that if you were allowed to rename a conversion of an untagged variable, would you end up with a variable or a constant. I presume your goal is to end up with a variable, that has different bounds. Given that such renamings are illegal now, there is the option if we were to allow them, to define the semantics the way we want them, and in particular consider a renaming as another context where you can end up with a view conversion, even for an untagged type. One issue is for conversions that might involve a change in representation, since the renaming would probably need to be implemented in a somewhat "lazy" fashion, not actually creating the object in the new representation unless it is passed as a parameter. You might also have to disallow the use of 'Address or 'Access. In any case, thanks for the suggestion! I presume it will end up on the Ada Rapporteur Group docket at some point, but I can't promise anything given some of the subtleties of representation change... **************************************************************** From: Randy Brukardt Sent: Thursday, November 30, 2017 4:07 PM ... > What are the reasons for the renaming of an untagged vew conversion to > be illegal? > Would it be an option for Ada 202x to change this? AI12-0226-1 already proposes to allow the renames of *value* conversions (and, indeed, the current plan is to allow the renaming of any expression). However, most such renames would be the renames of a constant. > I cannot see a fundamental difference between the legal view > conversion in a subprogram call and the illegal renaming of a view > conversion. > > procedure Do_Stuffs (Data: in out General_Real_Array) is > > subtype One_Based is General_Real_Array (1 .. Data'Length); > > procedure Use_1_Based (X: in outOne_Based) is separate; > > Shifted:One_Basedrenames One_Based (Data); -- illegal because > not tagged > > begin > > Use_1_Based (One_Based (Data)); -- view conversion > > end Do_Stuffs; The fundamental difference is that a view conversion of an untagged type only can occur in parameter passing. There is no such thing is a view conversion in any other context (unless the type is tagged). Tucker also explained that. Since everything (both in the language and in the implementation) assume view conversions are limited to parameter passing, changing that could be quite expensive. Additionally, conversions of untagged types can include a representation change (like float to fixed, or from different record representations). Allowing those to occur unfettered could cause big issues with code expense and accuracy (for fixed and float types). As I sent on comp.lang.ada: Some_Seconds : Duration; Flt_Sec : Float renames Float(Some_Seconds); Flt_Sec := ; -- Hidden rounding occurring here. The hiding of rounding in some assignments would be against the basic design of Ada, where such things are always explicit. Anyway, the array case would not (so far as I can tell) be a problem. But we'd have to assign some unnatural limitations on such a feature (the above example shouldn't be allowed, for one example), so it's not clear that it could be made to work sanely. **************************************************************** From: Randy Brukardt Sent: Thursday, November 30, 2017 6:14 PM ... > The whole distinction between "object" and "value" in the Ada > reference manual seems to be less and less useful. Jeff seems to think that you were assigned the AI that intends to eliminate this difference for expressions (AI12-0226-1). (Eliminating it in general seems misguided, IMHO, as there is a difference between a value and a memory location holding a value and that difference matters in describing language semantics. But that doesn't mean that the difference has to result in Legality Rules!) I had thought it was left with me, which is a problem since I don't know of an inexpensive way to accomplish the change. Since you're much better at that than me, and I don't mind having less homework, I've followed Jeff's notes rather than mine and assigned it to you. >In any case, thanks for the suggestion! I presume it will end up on >the Ada Rapporteur Group docket at some point, but I can't promise >anything given some of the subtleties of representation change... The object vs. value thing is already in your homework. You probably could consider this as well. (If I was doing this, I would simply ban representation change cases somehow -- perhaps leaving them as value conversions -- so that only the simple cases could be treated as a variable. Not as regular, but Ada has been very paranoid about implicit changes of representation -- 13.1(10) being the ultimate expression of that -- and as I showed in my earlier message, the effect of a general version of view conversion renaming would be to distribute implicit change of representation all over the program.) **************************************************************** From: Jeffrey R. Carter Sent: Friday, December 1, 2017 4:41 AM > The object vs. value thing is already in your homework. You probably > could consider this as well. (If I was doing this, I would simply ban > representation change cases somehow -- perhaps leaving them as value > conversions -- so that only the simple cases could be treated as a variable. > Not as regular, but Ada has been very paranoid about implicit changes > of representation -- 13.1(10) being the ultimate expression of that -- > and as I showed in my earlier message, the effect of a general version > of view conversion renaming would be to distribute implicit change of > representation all over the program.) Probably not backwards compatible, but something like C : constant T renames ; -- Value conversion OK V : T renames ; -- Only a view conversion allowed with a renamed conversion being a view conversion if it doesn't involve a change of representation, might have worked. Currently, "constant" can't appear in a renaming, and whether a renaming is constant or not depends on what is being renamed. I always found that confusing. **************************************************************** From: Christoph Grein Sent: Friday, December 1, 2017 9:41 AM ... > The fundamental difference is that a view conversion of an untagged > type only can occur in parameter passing. There is no such thing is a > view conversion in any other context (unless the type is tagged). > Tucker also explained that. I know this. The question was "Why?" > Since everything (both in the language and in the implementation) > assume view conversions are limited to parameter passing, changing > that could be quite expensive. > > Additionally, conversions of untagged types can include a > representation change (like float to fixed, or from different record representations). > Allowing those to occur unfettered could cause big issues with code > expense and accuracy (for fixed and float types). This might bee an answer to "why". > As I sent on comp.lang.ada: > > Some_Seconds : Duration; > > Flt_Sec : Float renames Float(Some_Seconds); > > Flt_Sec := ; -- Hidden rounding occurring here. But: Isn't this the same effect here? procedure P (X: in out Float) is begin X := ; end P; P (Float (Some_Seconds)); **************************************************************** From: Randy Brukardt Sent: Friday, December 1, 2017 2:00 PM No, because Float is a by-copy type. That means the only conversions occur at the point of the call, and that is explicitly shown there by the type conversion. More generally, any type that can have such a view conversion in parameter passing is either by-copy or can be (and will be) passed by-copy. So the conversion is always explicitly given at the exact place where the conversions (and thus rounding) are happening. Code reviewers should be tipped off by the explicit conversions that there is an issue. This even extends to derived subprograms. 13.1(10) prevents any representation clauses which would have the effect of changing representation automatically for derived subprograms. In the earlier case, the type conversion is implicit on the assignment; the explicit type conversion can be far away and not at all obvious to a code reviewer. This is the sort of thing that rules like 13.1(10) are intended to prevent. Obviously, we could dump this principle -- it's not one that I particularly care for -- but if we were to do that, I'd be much more interested in repealing 13.1(10) [since it prevents many useful things] than the renames case (which seems to be useful only in this one weird case). **************************************************************** From: Bob Duff Sent: Friday, December 1, 2017 2:23 PM > case (which seems to be useful only in this one weird case). Note that the renames being requested isn't even a real type conversion -- it's converting between two subtypes of the same array type. There's no change of rep in that case. **************************************************************** From: Tucker Taft Sent: Friday, December 1, 2017 2:37 PM Perhaps we could always allow renaming of untagged conversions, but indicate that the view declared by the renaming is a variable only when the operand and result type have a common ancestor, and no rep-changing representation items. This would be consistent with our allowing renaming of tagged conversions, since such conversions are only permitted when there is a common ancestor, and you can never change the rep of the parent part. So the rule wouldn't be tagged versus untagged, but rather "blood relatives" vs. "in-laws." I suppose at that point we should allow such conversions anywhere a variable is required -- i.e. consider them view conversions in all contexts, not just when passed as a parameter. **************************************************************** From: Christoph Grein Sent: Saturday, December 2, 2017 4:02 AM > More generally, any type that can have such a view conversion in > parameter passing is either by-copy or can be (and will be) passed > by-copy. So the conversion is always explicitly given at the exact > place where the conversions (and thus rounding) are happening. Code > reviewers should be tipped off by the explicit conversions that there is an issue. Do you really want to say that here (your example) the parameters are passed by copy? Procedure Really_Do_Stuff (Indexed : in out Indexing; Offseted: in out Offset ) is Begin -- Stuff using Indexed and Offseted!! null; End Really_Do_Stuff; Begin Really_Do_Stuff (Indexing (Data), Offset(Data)); -- Parameter passing is the only way in Ada 83-2012 -- to get a view conversion of an untagged object. End Do_Stuffs; **************************************************************** !topic fixed first indices for variable-length arrays !reference Ada 2012 RM 3.6 !from Stefan Lucks 03.01.2018 !keywords keywords related to topic !discussion Consider a specification such as procedure Sort(Data: in out Some_Array(Positive range <>)) Unlike most other modern languages, Ada does not fix the first index Data'First of Data. E.g., one could write a sorting procedure sorting in increasing order if Data'First is odd, and in decreasing order if Data'First is even. Even if we assume no such kind of poor software engineering, a technique to specify subprograms to hide the callers bounds from the called is missing in Ada. As a matter of fact, a proper black box test for an Ada implementation of Sort would need to include test cases with identical Data, except for the lower bound Data'First: A: Some_Array(1 .. 3) := (1, 2, 4); B: Some_Array(2 .. 4) := (1, 2, 4); C: Some_Array(Positive'Last-2 .. Positive'Last) := (1, 2, 4); ... Sort(A); Sort(B); Sort(C); if not ((A=B) and (B=C)) then raise ... Such test cases are not necessary (and even syntactically impossible to write down) in languages with fixed lower bounds for arrays. It would be very helpful -- and should be easy to implement -- to add subprogram specifications, which hide the lower and upper bounds from the caller (but not the length, of course). Below is a proposal for such an addition to the Ada language. !problem Ada supports arrays with arbitrary first indices (e.g., 1980 in "type Ada_Versions is array (1980 .. 2100) of Some_Type"). Subprogram parameters are *index-aware*, i.e., the subprogram inherits the caller's knowledge about the index. If the intended functionality of the subprogram depends on the first index, this is a benefit of Ada, where many other programming languages need complex workarounds. But frequently, a subrogram's intended functionality is independent from the first index, i.e., it is *index-agnostic*. In such cases, it is difficult to specify and verify that the subprogram's behaviour never depends on the array index, and proper black box testing in Ada would require a lot of additional test cases, compared to languages with a default first index (Java, C++, ...). This puts Ada's (otherwise well deserved) reputation as a language with exceptional support for the development of safe and secure systems into question. Example: The package Ada.Strings.Fixed has several functions of the form function Index (Source : String; Pattern : String; [other parameters]) return Natural; to search for the substring Pattern in the string Source. I.e., if P is a substring of S then S(Index(S, P) .. P'Length-1) = S. This is index-aware for Source, but index-agnostic for Pattern. For concreteness, consider P1: String(1..3) := "abc"; P2: String(Positive'Last-2 .. Positive'Last) := "abc". Whatever the value of Source is, the functionality requires Index(Source, P1, ...) = Index(Source, P2, ...), and proper black box tests should include test cases for this behaviour. !proposal Add a syntax for to specify the first index of an array parameter, without specifying the length of the array. Examples: function Index (Source : String; Pattern : String(1 .. <>); [other parameters]) return Natural; procedure Sort (S: in out Some_Array(1 .. <>)); This nicely generalises to multi-dimensional arrays, which can even be index-agnostic with respect so some dimensions, and index-aware with respect to other dimensions: type Matrix is array(Positive range <>, Positive range <>) of Character; subtype I_Matrix is Matrix(Positive range 1 .. <>, Positive range 1 .. <>); subtype I_Matrix_1 is Matrix(Positive range 1 .. <>, Positive range <>); subtype I_Matrix_2 is Matrix(Positive <>, Positive range 1 .. <>); function From_Matrices(M: I_Matrix; M1: I_Matrix_1; M2: I_Matrix_2) return ...; There is not much need for *arbitrary* First indices. The entire purpose of this proposal is to allow specifications, where the caller's array bounds are hidden from the calla. To simplify implementations and avoid performance issues, it may be prudent to restrict the choice for Parameter'First to one single value, namely to Base_Type'First (e.g., to 1 if the base type is Positive, as in our examples above). !wording RM 3.6: replace index_subtype_definition ::= subtype_mark "range" "<>" by index_subtype_definition ::= subtype_mark "range" ("<>" | expression ".." "<>") where expression evaluates to a constant of type T for T being the type determined by the subtype mark. To ease implementations and avoid possible performance issues, the standard may restrict the expression to the constant T'First. **************************************************************** From: Bob Duff Sent: Wednesday, January 3, 2018 2:05 PM > Consider a specification such as > > procedure Sort(Data: in out Some_Array(Positive range <>)) That's not legal syntax. I'm not sure what you intended. But anyway, you can do things like this: type T is array(Positive range <>) of Character with Predicate => T'First = 1; -- OK, OK, Dynamic_Predicate ;-) You might even be able to get your friendly compiler writer to avoid storing the value 1 in every object of type T. There was also a proposal for Ada 9X to allow discrimants, but that was ultimately rejected. Something like: type T(Last: Natural) is array(Positive range 1 .. Last) of Character; X: T(Last => 10); -- a 10-character string **************************************************************** From: Tucker Taft Sent: Wednesday, January 3, 2018 2:12 PM I agree that allowing the low bound of an unconstrained array subtype to be specified would be nice. In fact, you can already almost do it with a dynamic subtype predicate: subtype S1 is String with Dynamic_Predicate => S1'First = 1; Unfortunately, this does not result in "sliding" as a result of the subtype conversion inherent in parameter passing. So one possibility would be to "officially" recognize this idiom and define a conversion to such a subtype to result in sliding. The alternative would be to interpret the syntax you suggested: subtype S1 is String(1..<>); as a short-hand for the above Dynamic_Predicate, but with the added effect of causing sliding. Not a compelling need, but seems like a nice-to-have. **************************************************************** From: Jeffrey R. Carter Sent: Wednesday, January 3, 2018 2:38 PM > Unlike most other modern languages, Ada does not fix the first index > Data'First of Data. E.g., one could write a sorting procedure sorting > in increasing order if Data'First is odd, and in decreasing order if > Data'First is even. Even if we assume no such kind of poor software > engineering, a technique to specify subprograms to hide the callers bounds > from the called is missing in Ada. You mean, "Unlike most poorly designed languages ..." Not fixing the lower bound of arrays is one of Ada's advantages. The 1st system I worked on was FORTRAN that stored data for years starting in 1600. It would have been a lot simpler and clearer if the array indices could have had lower bounds of 1600. Ada has always had a way to write a type with the features you want: type Implementation is array (Positive range <>) of Element; type List_From_1 (Length : Natural) is record Value : Implementation (1 .. Length); end record; procedure Sort (List : in out List_From_1); Ada 12 also has the possibility of defining an array type with a dynamic predicate that the lower bound is fixed. **************************************************************** From: Randy Brukardt Sent: Wednesday, January 3, 2018 8:11 PM ... > The alternative would be to > interpret the syntax you suggested: > > subtype S1 is String(1..<>); > > as a short-hand for the above Dynamic_Predicate, but with the added > effect of causing sliding. Not a compelling need, but seems like a > nice-to-have. Of these two options, I much prefer the second, because having some magic semantics associated with just a few special Dynamic_Predicates is uncomfortable. (Indeed, this is precisely the reason that we have separate Static and Dynamic predicates.) Having a subtype that automatically incurs sliding seems perfectly reasonable. Of course, whether it (or any further solution to this "problem") is really needed is hard to say. Ada programming have been living with this issue since Ada 80 (that is, for 40 years), and certainly any decent Ada unit test framework will be helpful in generating the extra test cases needed. (And note that this does little to help the real problem in passing unconstrained arrays, dealing with null array parameters.) **************************************************************** From: Stefan Lucks Sent: Thursday, January 4, 2018 3:49 AM > You mean, "Unlike most poorly designed languages ..." Not fixing the > lower bound of arrays is one of Ada's advantages. I agree. Mostly. Not having index-aware arrays (i.e., only having arrays starting with a fixed lower bound) is a disadvantage of those other languages. (Not that this is a sufficient reason to call them "poorly designed"!) But not being able to hide the lower bound of an array in a subprogram is a disadvantage of Ada. > Ada has always had a way to write a type with the features you want: > > type Implementation is array (Positive range <>) of Element; > > type List_From_1 (Length : Natural) is record > Value : Implementation (1 .. Length); end record; > > procedure Sort (List : in out List_From_1); This is not the feature I want! > Ada 12 also has the possibility of defining an array type with a > dynamic predicate that the lower bound is fixed. That seems to be essentially the same feature! I want subprogram specifications which *hide* the caller's lower bound from the subprogram. Whatever the caller's lower bound is, it silenty slides to 1 (or whatever) inside the subprogram and slides back when the subprogram returns. In some sense, I want to have the cake and eat it. The cake is arbitrary lower bounds, when this matches the problem space, and eating it is fixed lower bounds in subprograms who's functionality is index-agnostic (such as Sort, or the Pattern parameter in Find ...). **************************************************************** From: Stefan Lucks Sent: Thursday, January 4, 2018 4:30 AM > Having a subtype that automatically incurs sliding seems perfectly > reasonable. Great! > Of course, whether it (or any further solution to this "problem") is > really needed is hard to say. "Really needed"? Which language modification would be "really needed", after about 40 years Ada bing used with success? "Improving the language"? Most certainly, yes. Which is, what language modifications are about, anyway. One of the benefits of Ada is the separation from specification and implementation (quite in contrast to some "the code is the spec" ideology I observed in other language commumities). Not being able to write specifications such that array bounds are hidden is clearly an issue. I anticipate that this modification would be rather easy for compiler writers. If so, it would be worth doing it. On the other hand, if you compiler writers tell me that solving this issue would cause a major problem for compiler writers, I would withdraw my proposal. > (And note that this does little to help the real problem in passing > unconstrained arrays, dealing with null array parameters.) Hu? There is the lack of a proper syntax for null array constants, but I don't understand why you consider that problem more urgent. **************************************************************** From: Bob Duff Sent: Thursday, January 4, 2018 4:33 PM > I anticipate that this modification would be rather easy for compiler > writers. If so, it would be worth doing it. On the other hand, if you > compiler writers tell me that solving this issue would cause a major > problem for compiler writers, I would withdraw my proposal. I'm a compiler writer. Nothing is simple. But I don't think this one would cause major problems. > > (And note that this does little to help the real problem in passing > > unconstrained arrays, dealing with null array parameters.) > > Hu? Hu's on first. ;-) > There is the lack of a proper syntax for null array constants, but I > don't understand why you consider that problem more urgent. I don't know what Randy was referring to. "Super-null arrays", perhaps (i.e. where 'Last /= 'Pred('First))? **************************************************************** From: Tucker Taft Sent: Thursday, January 4, 2018 5:15 PM >> There is the lack of a proper syntax for null array constants, but I >> don't understand why you consider that problem more urgent. > > I don't know what Randy was referring to. "Super-null arrays", > perhaps (i.e. where 'Last /= 'Pred('First))? There is some distaste in the ARG for specifying a null array by having to write, e.g. "(1..0 => blah)" when you really just want to write "()". We might legalize empty parentheses for this purpose. Then they might be usable for other things like empty containers, empty sets, etc. **************************************************************** From: Randy Brukardt Sent: Thursday, January 4, 2018 5:51 PM This is off topic for this thread, but this subject came up on comp.lang.ada recently, and there was decent support for (null array) which would be similar to (null record). A couple of people noted that they would not like "()". Personally, I find "()" inconsistent with the rest of the language; "(null array)" is much more consistent (Ada rarely allows complete omission of things without a placeholder). **************************************************************** From: Tucker Taft Sent: Thursday, January 4, 2018 7:50 PM Ichbiah hated "()" apparently, given the lengths Ada 83 went to avoid it. But it seems like the obvious syntax. The annoyance about "(null array)" is that it doesn't generalize to container aggregates. Maybe "(<>)" ;-) **************************************************************** From: Randy Brukardt Sent: Thursday, January 4, 2018 8:21 PM I don't see any real need for an empty container aggregate, given that there is an empty container constant ("Empty_Vector"; "Empty_List", etc.) in each container package. About the only advantage of having an empty container aggregate is for the use-adverse, but I'd find it weird if you suddenly had sympathy for that segment of the programming community. ;-) **************************************************************** From: Randy Brukardt Sent: Thursday, January 4, 2018 6:14 PM > > Of course, whether it (or any further solution to this > > "problem") is really needed is hard to say. > > "Really needed"? Which language modification would be "really needed", > after about 40 years Ada bing used with success? Not many, which is my point. My preference is to avoid "creeping featurism" in Ada; I'd like to see safe-by-construction parallelism (which is impossible in current Ada), but beyond that, nothing much is needed or desired. Otherwise, the language could get overloaded by cool gee-gaws, each one of which is justifiable in a vacuum but as a whole make the language harder to read, understand, and implement. > "Improving the language"? Most certainly, yes. Which is, what language > modifications are about, anyway. Here I'm not sure. This might be a language improvement for newbies that don't understand Ada arrays very well, but I've never found it hard to implement routines "correctly" (that is, in a bound-independent manner). One just has to spend a moment of thought on the topic. > One of the benefits of Ada is the separation from specification and > implementation (quite in contrast to some "the code is the spec" > ideology I observed in other language commumities). Not being able to > write specifications such that array bounds are hidden is clearly an > issue. Don't see this, as the specification here is that the bounds can be anything. Surely the implementation should follow that? Your asking for a new kind of array subtype with partially specified bounds. That's relatively easy to define in language terms, but it will be rather hard to understand to language newbies (as they have to understand "sliding" in order for it to make sense). > I anticipate that this modification would be rather easy for compiler > writers. If so, it would be worth doing it. On the other hand, if you > compiler writers tell me that solving this issue would cause a major > problem for compiler writers, I would withdraw my proposal. Doesn't matter how hard or easy it is, per se. Every change requires work for implementers, extra cognitive load for readers (in this case, an additional kind of subtype to understand), and so on. We have to limit the number of changes in order to avoid overloading the language with gee-gaws to the point that it becomes difficult to maintain just because of the number of rarely used things that might be encountered. (One loses a lot of productivity every time one has to look up what something means.) > > (And note that this does little to help the real problem in passing > > unconstrained arrays, dealing with null array parameters.) > > Hu? > > There is the lack of a proper syntax for null array constants, but I > don't understand why you consider that problem more urgent. The most difficult problem with most routines taking unconstrained arrays is that they fall over when given a null array. Most of the usual tricks don't work with them, and most routines ought to start with an explicit check for a null array (but few actually do). Moreover, the bounds of null arrays can literally be anything (any value of the index type is allowed), and in some cases, have to be anything: type Unsigned is mod 2**8; type An_Array (Unsigned range <>) of Float; Empty : constant An_Array := (2 .. 1 => 0.0); Note that the lower bound of Empty *cannot* be 0, because there is then no upper bound that is less than the lower bound. So, with your proposed subtype: subtype Slider is An_Array (0 .. <>); A call passing any null array is going to have to raise Constraint_Error. That might be OK for some uses, and a serious problem in others. My overall point being that this proposal does little to help dealing with null arrays, and even introduces some new problems with null arrays. It's definitely not a panacea for dealing with unconstrained arrays. P.S. Although all of the above is negative, I'm actually on the fence about this idea, because it seems to me to be a clever way to leveraging Ada's existing mechanisms (sliding) to reduce (but clearly not eliminate) an annoyance. (Even though the annoyance is usually self-inflicted.) I often decide to punt on the variable bounds thing by simply requiring that calls use a lower bound of 1 (in Ada 2012, that would be a precondition/predicate, but I've generally just started the routines with "if Param'First /= 1 then raise Program_Error; end if;"). **************************************************************** From: Jeffery R. Carter Sent: Friday, January 5, 2018 5:05 AM > Ichbiah hated "()" apparently, given the lengths Ada 83 went to avoid > it. But it seems like the obvious syntax. The annoyance about "(null > array)" is that it doesn't generalize to container aggregates. Maybe > "(<>)" ;-) Actually, Ada 80 required "()" to call a subprogram without any actual parameters. IIUI, these were removed in Ada 83 as a result of public comments on Ada 80 that were heavily opposed to them. **************************************************************** From: Jeffrey R. Carter Sent: Friday, January 5, 2018 5:56 AM > type Unsigned is mod 2**8; > type An_Array (Unsigned range <>) of Float; > > Empty : constant An_Array := (2 .. 1 => 0.0); > > Note that the lower bound of Empty *cannot* be 0, because there is > then no upper bound that is less than the lower bound. Looking at this makes me reconsider how to represent bounds for an array object. It makes sense to specify bounds with a range because people are bad at counting things. Since computers are good at counting things, perhaps it makes less sense to require that 'Last be defined for all array objects. Given a way to specify a null array without specifying bounds, then for any unconstrained array type we could have a canonical representation of an object of the type as a lower bound, a length, and, if the length > 0, a set of component values. "Last is undefined and raises an exception if the length = 0. Any actual implementation is acceptable so long as it behaves "as if" it were the canonical implementation. It would make sense in such cases for 'First of a null array object to be I'First, where I is the index subtype, since I may only have a single value. Then Empty'First could and would be Unsigned'First = 0, and such a value could be passed with sliding to the proposed new subtypes that result in sliding so the lower bound is I'First. Allowing a "degenerate" representation of a null array where 'First /= I'First would also allow passing to subtypes that require sliding to a lower bound /= I'First. This isn't the way Ada does it, but if Ada had always had concepts along these lines it seems a lot of these concerns would not exist. **************************************************************** From: Randy Brukardt Sent: Friday, January 12, 2018 11:02 PM > !topic fixed first indices for variable-length arrays In writing this up, I noted a number of issues with the proposal given here. These aren't necessarily relevant to the *idea*, but in case anyone reads this original idea. ... > Consider a specification such as > > procedure Sort(Data: in out Some_Array(Positive range <>)) Bob noted that this isn't legal syntax. Ada, in fact, requires that parameter types are given by subtype_marks, and not subtype_indications. Thus no sorts of constraints are allowed (although there are special cases for anonymous access types and for null exclusions). There is a semantic reason behind this rule: since most subprograms are given in two parts, and Ada allows constraints to be dynamic, matching would be impossible for dynamic constraints (as they would be evaluated separately and might even end up representing different sets of values). ... > Examples: > > function Index (Source : String; Pattern : String(1 .. <>); > [other parameters]) return Natural; > > procedure Sort (S: in out Some_Array(1 .. <>)); As noted above, subtypes that might be dynamic are not allowed in subprogram specifications. I suppose we might decide to change this somehow, but I surely would not want to do that for JUST this case. And you should not want to load up your proposal with unnecessary new capabilities -- that's the most certain way to get a proposal rejected. So these should read something like: subtype One_Based_String is String (1 .. <>); function Index (Source : String; Pattern : One_Based_String; [other parameters]) return Natural; [BTW, making a change like this to Ada.Strings is, unfortunately, quite incompatible. That's because renaming and 'Access require subtype conformance, and String and One_Based_String do not conform. Making such a change anyway would mean any existing renaming or 'Access of Index would be illegal because of the subtype mismatch.] subtype One_Based_Array is Some_Array (1 .. <>); procedure Sort (S: in out One_Based_Array); > !wording > > RM 3.6: > > replace > > index_subtype_definition ::= subtype_mark "range" "<>" > > by > > index_subtype_definition ::= subtype_mark "range" ("<>" | expression > ".." "<>") > > where expression evaluates to a constant of type T for T being the > type determined by the subtype mark. This is the definition of an unconstrained array type. This change, if made, would require this to only be used in type declarations: type New_String is array (Natural range 1 .. <>) of Character; which does not appear to be what you want. This would naturally be described as an array constraint (in 3.6.1), except for the obvious problem that this concept doesn't constrain anything. Indeed, there is nothing like this currently in Ada. That means a proper proposal would have to define an whole new kind of "semi-constrained" subtypes (for the lack of a better name). Tucker suggested in e-mail making it equivalent to a Dynamic Predicate + sliding, but I don't quite see how this helps, as one still needs to know how make array checks for assignments, indexing, and the like. That would require a significant amount of wording. (Besides, equivalence rules very often turn out to be wrong in some subtle way, and often end up making more work than getting it right in the first place.) > To ease > implementations and avoid possible performance issues, the standard > may restrict the expression to the constant T'First. I hope you mean "a static expression with the value of T'First" (but that is wrong for a multi-dimesional array, you want T'First for the appropriate dimension). But this seems like a bad idea given the syntax. If you have an arbitrary expression in the syntax, you should allow it to be arbitrary (with appropriate compatibility checks). Indeed, there is no reason to define this as static, either, as that is limiting and unnecessary. Compilers have no problem doing math to figure the upper bound necessary! If you really don't want to allow arbitrary lower bounds, then the syntax needs to not include the bound at all. (Compare this syntax to that for unsigned integers.) **************************************************************** From: Stefan Lucks Sent: Monday, January 15, 2018 9:17 AM >> Consider a specification such as >> >> procedure Sort(Data: in out Some_Array(Positive range <>)) > > Bob noted that this isn't legal syntax. Well, I know that, but I already had my new syntax in the mind, when I wrote that. I apologize for not type-checking the above example. >> Examples: >> >> function Index (Source : String; Pattern : String(1 .. <>); >> [other parameters]) return Natural; >> >> procedure Sort (S: in out Some_Array(1 .. <>)); > > As noted above, subtypes that might be dynamic are not allowed in > subprogram specifications. I suppose we might decide to change this > somehow, but I surely would not want to do that for JUST this case. > And you should not want to load up your proposal with unnecessary new > capabilities -- that's the most certain way to get a proposal rejected. > > So these should read something like: > > subtype One_Based_String is String (1 .. <>); > function Index (Source : String; Pattern : One_Based_String; > [other parameters]) return Natural; > > subtype One_Based_Array is Some_Array (1 .. <>); > procedure Sort (S: in out One_Based_Array); Well, I am certainly not a language lawyer, and this is pretty much what I want. >> To ease >> implementations and avoid possible performance issues, the standard >> may restrict the expression to the constant T'First. > > Compilers have no problem doing math to figure the upper bound > necessary! Well, I did anticipate such problems (and potential performance issues), which was precisely my reason to propose these constraints at all. Of course, there is no reason for to give the author of the spec the freedom to choose arbitrary first indices. But if such a constraint would complicate the implementation of my proposal, I am happy to withdraw that clause. > If you really don't want to allow arbitrary lower bounds, then the > syntax needs to not include the bound at all. (Compare this syntax to > that for unsigned integers.) That makes sense. **************************************************************** From: Stefan Lucks Sent: Monday, January 15, 2018 10:36 AM > subtype One_Based_String is String (1 .. <>); > function Index (Source : String; Pattern : One_Based_String; > [other parameters]) return Natural; > > subtype One_Based_Array is Some_Array (1 .. <>); > procedure Sort (S: in out One_Based_Array); > Indeed, there is nothing like this currently in Ada. That means a > proper proposal would have to define an whole new kind of "semi-constrained" > subtypes (for the lack of a better name). Well, the problem is the lack of hiding array bounds from subprograms with an index-agnistic functionality. Perhaps, introducing a new kind of subtype, which then might be used everywhere, is overkill. So I'll revise my proposal. Tucker Taft mentioned the idea to define this as a subtype with a Dynamic_Predicate -- but that would still be a new kind of subtype. Revised proposal (by exampe): function Index (Source : String; Pattern : String; [other parameters]) return Natural with Sliding => Pattern; procedure Sort (S: in out Some_Array) with Sliding => Sort; procedure Matrix_Operation(M: in out Multi_Dim_Array) with Sliding => M; The semantic of this proposal is as follows: Within the execution of Index and Sort, the following would hold, regardless of the callers' parameters: Index: Pattern'Last = Positive'Last (slide index) Sort: S'Last = 'Last (slide index) Matrix_Operation: (slide indices for all dimensions of Multi_Dim_Array) Discussion: The purpose of Sliding is to hide the callers' index bound from the subprogram, and to allow subprogram specs with this kind of hiding. For that purpose, it does not matter if we slide the first index to somewhere, or the last index. Also, there is no need to choose a target where to slide an upper or lower bound. And finally, there is no need for a new kind of subtypes. So this proposal avoids anything, and still fixes the problem at hand. At a first look, it may seem more intuitive to slide the first index to some fixed point, such as to the first possible value. The notation might, e.g., be as follows: function Index (Source : String; Pattern : String; [other parameters]) return Natural with Sliding => Pattern'First = 1; procedure Sort (S: in out Some_Array) with Sliding => Sort'First = ; But specifying a given value for Sort'First gives more options than neccessary for the problem at hand. More importantly, these additional options -- and specifying the first index at all -- may lead to additional constraint checks and the risk of raising a Constraint_Error. With the original proposal, there was an issue with null arrays. It would stick with the new proposal, if we would allow the specifications to set the first index. E.g., the function P below raise a Constraint_Error if Something is the empty array: type T is (A, B, C); type Arr is array(T) of Whatever; procedure P(Something in out Arr) with Sliding => Something'First=A; We can avoid this, by extending T and rewriting the type Arr to a new type Secure_Arr: type T is (Previous, A, B, C); subtype S is T range A .. C; type Secure_Arr is array(S) of Whatever; procedure P(Something in out Secure_Arr) with Sliding => Something'First=A; That seems to be fine, but it requires a nontrivial revision of the specification. If not for the sliding -- why do we need the type Secure_Arr at all? So let us stick with the old T and Arr and write P as follows: type T is (A, B, C); type Arr is array(T) of Whatever; procedure P1(Something in out Arr) with Sliding => Something'First=B; In this case, the indices of the empty array would range from B to A, and empty arrays would be fine. But now, the implementation of P would have to raise Constraint_Error whenever P is called with an array of length 3. All in all, the idea to slide the last index to the largest possible value seens to be preferable. > If you really don't want to allow arbitrary lower bounds, then the syntax > needs to not include the bound at all. (Compare this syntax to that for > unsigned integers.) Exactly! **************************************************************** From: Jean-Pierre Rosen Sent: Monday, January 15, 2018 11:26 AM I've been following this thread, and TBH I don't see the need for this feature. Yes, you have to learn that in Ada, bounds can be pretty much anything, and that a subprogram that handles unconstrained arrays must use the 'First and 'Last attributes instead of assuming a lower bound of 1. Big deal! OTOH, if an implementation stores the bounds of the array in a dope vector on top of the array, and passes the array by reference, a view with different bounds is pretty much impossible. Note that renaming a conversion (to get sliding) is not allowed (I just tried), certainly for the same reason. In short, it appears to me as a big trouble for little gain. **************************************************************** From: Randy Brukardt Sent: Monday, January 15, 2018 11:31 PM ... > OTOH, if an implementation stores the bounds of the array in a dope > vector on top of the array, and passes the array by reference, a view > with different bounds is pretty much impossible. Note that renaming a > conversion (to get sliding) is not allowed (I just tried), certainly > for the same reason. It can't be that hard to implement, as Ada 83 has always had a way to do this (at the cost of a nested subprogram): That is, transform: subtype Half_Bound is String (1 .. <>); -- Not really Ada. procedure Do_It (Arg : in out Half_Bound; ...) is -- Decls. begin -- Operations. end Do_It; into: procedure Do_It (Arg : in out String; ...) is -- Decls. subtype Half_Bound is String (1 .. Arg'Length); procedure Really_Do_It (Arg : in out Half_Bound; ...) is begin -- Operations. end Really_Do_It; begin Really_Do_It (Half_Bound(Arg), ...); end Do_It; For an "in" parameter (and Ada 95), you don't even need the nested subprogram, but then you have to wrap every use of Arg with a type conversion. If Ada 2020 ends up adopting AI12-0226-1 (and so far, the existing write-up seems like the minimum that we'll do), then renaming such a type conversion will be legal and result in a *constant* view of the array. So the "in" case could be as simple as: procedure Do_It_Too (Arg : in String; ...) is -- Decls. subtype Half_Bound is String (1 .. Arg'Length); My_Arg : Half_Bound renames Half_Bound(Arg); begin -- Operations, using My_Arg rather than Arg. end Do_It_Too; So I don't think that implementation costs should make a lot of difference either way. (Janus/Ada always keeps the bounds separately from the data, so this is never a problem for us, but clearly the implementation has to be able to do this today to implement the first two examples. So there is nothing really new here.) Tucker may be looking at the possibility of allowing a renames like the above to represent a *variable* rather than a constant; in which case the solution in Do_It_Too would be available for any unconstrained parameter. Perhaps that is enough to solve the problem without adding too much additional stuff. **************************************************************** From: Randy Brukardt Sent: Monday, January 15, 2018 11:47 PM ... > The semantic of this proposal is as follows: Within the execution of > Index and Sort, the following would hold, regardless of the callers' > parameters: > > Index: Pattern'Last = Positive'Last (slide index) > > Sort: S'Last = 'Last (slide index) > > Matrix_Operation: (slide indices for all dimensions of > Multi_Dim_Array) Interesting idea, but it seems to me that setting the bound to 'Last would pretty much eliminate much of the value of the proposal. Usually, I want to make 'First = 1, because then the index and the position number are the same, which simplifies operations on the parameter. I think we'd just not worry about the null array case (meaning that this wouldn't work for arrays indexed by enumerations or modular types, but we've lived with that for a long time already). Anyway, I had a different suggestion in my reply to Jean-Pierre. Is that rename (combined with a normal subtype constraint) is enough for you to solve the problem? It probably would be the least new mechanism. **************************************************************** From: Stefan Lucks Sent: Tuesday, January 16, 2018 1:45 AM > [...] Yes, you have to learn that in Ada, bounds can be pretty much > anything, and that a subprogram that handles unconstrained arrays must > use the 'First and 'Last attributes instead of assuming a lower bound > of 1. Big deal! Please recall my initial post and the problem statement there. The entire proposal is not about aiding the implementer of the subprogram, and allow the impementer to write a constant instead of either 'First or 'Last. The problem I am trying ot address is to write subprogram specs with an explicit index-agnostic use of array parameters, where the functionality is index-agnostic (as in the Sort and Index examples I gave). The two default approaches toward subprogram specs with index-agnostic array parameters seem to be the following: 1. You ignore it and assume the implementer of the subprogram did the right thing. Usually, this assumption holds, but if not, you just have a bug. 2. You write proper (black-box) tests, with identical inputs, except for shifting the bounds. Ada has a well-deserved reputation for being exceptionally well-suited for safe and secure systems. But this issue -- either the risk of specific Ada bugs, which would be hard even to write in other languages (which are limited to index-agnostic arrays), or the need for additional test cases (compared to such languages) -- clearly weakens Ada in that area. **************************************************************** From: Stefan Lucks Sent: Monday, January 15, 2018 2:22 AM > Interesting idea, but it seems to me that setting the bound to 'Last > would pretty much eliminate much of the value of the proposal. > Usually, I want to make 'First = 1, because then the index and the > position number are the same, which simplifies operations on the parameter. If "with Sliding => Parameter" would mean sliding Parameter'First to the first possible index and sliding Parameter'Last as required by the length, this would be fine with me. It somehow seems more natural to shift 'First to First, rather than 'Last to Last. > I think we'd just not worry about the null array case (meaning that > this wouldn't work for arrays indexed by enumerations or modular > types, but we've lived with that for a long time already). I don't worry much about the null array in such cases. As you pointed out, this is not really new. > Anyway, I had a different suggestion in my reply to Jean-Pierre. Is > that rename (combined with a normal subtype constraint) is enough for > you to solve the problem? It probably would be the least new mechanism. You mean the following snippet from that other post? procedure Do_It_Too (Arg : in String; ...) is -- Decls. subtype Half_Bound is String (1 .. Arg'Length); My_Arg : Half_Bound renames Half_Bound(Arg); begin -- Operations, using My_Arg rather than Arg. end Do_It_Too; I like it. Clearly, this eases to verify that Arg is used in an index-agnostic way, e.g., when you do a code review. HOWEVER, the *spec* seems to be procedure Do_It_Too (Arg : in String; ...); Thus, without reading the implementation, the caller of Do_It_Too could still not be sure if Arg is used in an index-agnostic way or not. Even though I like it, it does not solve the problem. Also, if the compiler already implements that, it seems to be straightforward to implement my proposal. I could assume a slightly tweaked syntax, allowing formal parameter specifications to refer to the length of the actual parameter, e.g. procedure Do_It_Also(Arg : in String(1 .. Arg'Actual'Length)); Of course, "with Sliding" (or perhaps "with Index_Normalisation" or whatever you prefer) would not need to tweak the syntax at all, except for the new "Sliding" aspect. **************************************************************** From: Stefan Lucks Sent: Tuesday, January 16, 2018 2:51 AM Taking the previous comments into consideration, I suggest the following (again, just by example): function Index (Source : String; Pattern : String; [other parameters]) return Natural with Sliding => Pattern'First; -- Pattern'First=1 -- Pattern'Last defined by the actual parameter's length -- sinde the index type of Pattern is Positive, -- Pattern="" translates to Pattern'Last=0 function Index (Source : String; Pattern : String; [other parameters]) return Natural with Sliding => Pattern'Last; -- Pattern'First defined by the actual parameter's length -- Pattern'Last = Positive'Last procedure Sort (S: in out Some_Array) with Sliding => S'First -- Pattern'First='First -- Pattern'Last defined by the actual parameter's length -- depending on -- calling Sort(null array) may raise a constraint error procedure Sort (S: in out Some_Array) with Sliding => S'Last -- Pattern'Last='Last -- Pattern'First defined by the actual parameter's length -- If has more than one element, -- calling Sort(null array) is not a problem The semantic of this proposal is as defined in the comments. Of course, "Sliding" can changed into something else, such as "Index_Normalisation" or so. **************************************************************** From: Jean-Pierre Rosen Sent: Tuesday, January 16, 2018 3:01 AM > Ada has a well-deserved reputation for being exceptionally well-suited > for safe and secure systems. But this issue -- either the risk of > specific Ada bugs, which would be hard even to write in other > languages (which are limited to index-agnostic arrays), or the need > for additional test cases (compared to such languages) -- clearly > weakens Ada in that area. Of course, if a language has more functionalities, it needs extra tests. In C, you don't need to test for exceptions raised by subprograms, because C has no exceptions. To me, it's not different than saying that in C you don't have to check for the case where the lower bound is not 0, because in C all arrays are indexed from 0. If a subprogram announces that it handles any array, then you have to test for variable lower bounds. If a subprogram is written to work only with arrays whose lower bound is 1, you can express that: procedure P (S : in String) with Pre => S'First = 1; and then the caller can do the sliding, and the tester needs not test cases with various lower bounds. So, I see your proposal as "nice to have", since there are acceptable other means, and having a special syntax improves only ease of writing. **************************************************************** From: Stefan Lucks Sent: Tuesday, January 16, 2018 4:08 AM > Of course, if a language has more functionalities, it needs extra tests. > In C, you don't need to test for exceptions raised by subprograms, > because C has no exceptions. But you need exactly the same number of test cases in C as in Ada, because in C you will test for the error return codes instead of the exceptions. > To me, it's not different than saying that in C you don't have to > check for the case where the lower bound is not 0, because in C all > arrays are indexed from 0. The difference is, that Ada needs additional test cases for this reason. > If a subprogram announces that it handles any array, then you have to > test for variable lower bounds. If a subprogram is written to work > only with arrays whose lower bound is 1, you can express that: > > procedure P (S : in String) with Pre => S'First = 1; > > and then the caller can do the sliding, and the tester needs not test > cases with various lower bounds. Ageed! While this puts some burden on the implementer, this certainly solves the problem. Of course, if you do this with the index-agnostic array parameters in the Ada standard library, you will break many of legacy problems. But really important is the ease of reading. Which of the following three snippets is more easy to read? Which describes the meaning of the code more clearly? 1. Sort(My_Array); -- specified according to my proposal 2. declare subtype Half_Bound is Array_Type (My_Array'First .. My_Array'Length); My_Array_Alias : Half_Bound renames Half_Bound(My_Array); -- syntax from Randy begin Sort(My_Array_Alias); end; 3. declare function Normalise_First_Index is new Generic_Shift_First_Index (Array_Type, Index_Type, Index_Type'First); begin Sort(Normalise_First_Index(My_Array)); end; I didn't run syntax checks. Regardless of potential typos, I think the code makes makes the issue your approach suffers from quite clear. ;- **************************************************************** From: Jeffrey R. Carter Sent: Tuesday, January 16, 2018 10:49 AM >   1. You ... assume the implementer of the subprogram did the >      right thing. Usually, this assumption holds, but if not, you just >      have a bug. [or] > >   2. You write proper (black-box) tests, with identical inputs, except for >      shifting the bounds. This is true of every subprogram. **************************************************************** From: Randy Brukardt Sent: Tuesday, January 16, 2018 3:55 PM I don't understand this obsession over a handful of potentially extra test cases. > > Of course, if a language has more functionalities, it needs extra tests. > > In C, you don't need to test for exceptions raised by subprograms, > > because C has no exceptions. > > But you need exactly the same number of test cases in C as in Ada, > because in C you will test for the error return codes instead of the > exceptions. Most Ada exceptions come from constraint checks that no C program is going to do. There are no array bounds checks, range checks, or the like in C, and those are not going to appear in any error code, either. You need a lot of extra test cases to check all of the possibilities exceptions on the parameters, compared to the typical C function. > > To me, it's not different than saying that in C you don't have to > > check for the case where the lower bound is not 0, because in C all > > arrays are indexed from 0. > > The difference is, that Ada needs additional test cases for this > reason. And many other reasons. Indeed, any sort of useful Ada coverage (or, for that matter, coverage for any language) requires thousands of tests for many combinations of parameters. It is impractical to generate all of those tests by hand. Luckily (especially with Postcondition contracts), it is relatively easy to generate those tests for parameters of elementary types and all language-defined types. As such, those tests can (and should be) generated by a tool. That works because one doesn't need to check the answers exactly for such tests; if it meets the postcondition and the subprogram has no untoward behavior (unexpected exceptions, traps, or faults) then it can be considered passing. One does need a few tests to check that actual answers, and those clearly have to be written by hand. But the vast majority of failures in Ada programs come from check failures (especially when the code is designed to use constraints and predicates to limit unintended results), so eliminating them is the primary goal of extensive testing. (It also helps provide security by verifying that all of the boundary cases have been handled.) I don't know the state of unit testing tools (I never do that personally, as none of my large projects have really allowed it), but such mostly automated testing tools should exist. If they don't, that is hardly the fault of the Standard. > > If a subprogram announces that it handles any array, then you have > > to test for variable lower bounds. If a subprogram is written to > > work only with arrays whose lower bound is 1, you can express that: > > > > procedure P (S : in String) with Pre => S'First = 1; > > > > and then the caller can do the sliding, and the tester needs not > > test cases with various lower bounds. > > Ageed! While this puts some burden on the implementer, this certainly > solves the problem. Of course, if you do this with the index-agnostic > array parameters in the Ada standard library, you will break many of > legacy problems. But really important is the ease of reading. Changes to the Ada library are unlikely unless 100% compatible (which this is not). Moreover, it's irrelevant, since any Ada implementer has already done the needed tests, so there is no benefit to anyone. The caller should not care if the bounds matter to the implementation! (Only the implementer should be doing unit tests anyway.) > Which of the following three snippets is more easy to read? > > Which describes the meaning of the code more clearly? > > 1. Sort(My_Array); -- specified according to my proposal > > 2. declare > subtype Half_Bound is Array_Type > (My_Array'First .. My_Array'Length); > My_Array_Alias : Half_Bound renames Half_Bound(My_Array); > -- syntax from Randy > begin > Sort(My_Array_Alias); > end; > > 3. declare > function Normalise_First_Index > is new Generic_Shift_First_Index > (Array_Type, Index_Type, Index_Type'First); > begin > Sort(Normalise_First_Index(My_Array)); > end; Both of you are insane. :-) The caller does not care what the implementation of the bounds is, and therefore the obvious answer is 1., but without any specification, because it's not relevant to the caller. Any specification to the user (as a precondition, subtype, or aspect) buys them nothing but extra complications to worry about. I was interested in the proposal only because it made correct implementation easier; I thought that any such proposal makes it harder for the reader of the program. I sometimes "rebase" parameters to be one-based, something like: Local_Pattern : constant String(1..Pattern'Length) := Pattern; to avoid having to worry about "weird" bounds, but that has a performance cost that I would like to eliminate. The renames pattern I showed would eliminate this cost without any further complication to the language or the reader. No one cares about saving 5 unit tests out of 300 (which is what this would do), and which would provide no other benefit to the client (they can use whatever bounds they like either way). **************************************************************** From: Stefan Lucks Sent: Tuesday, January 16, 2018 4:15 PM >>   2. You write proper (black-box) tests, with identical inputs, >> except for >>      shifting the bounds. > > This is true of every subprogram. If the functionality of the subprogram is index-agnostic, you should not need to to that. As you don't need to do in other languages (which then suffer from only supporting index-agnostic arrays, of course). Do you think, greatly increasing the number of test cases you need does not matter? declare Test_1 : Integer_Array(1 .. 5) := (3,6,2,3,1); Test_2 : Integer_Array(0 .. 4) := (3,6,2,3,1); Test_3 : Integer_Array(Integer'First .. Integer'First+4) := (3,6,2,3,1); Test_4 : Integer_Array(Integer'Last-4 .. Integer'Last) := (3,6,2,3,1); Expected : constant Integer_Array := (1,2,3,3,6); begin Sort(Test_1); if Test_1 /= Expected then raise Program_Error; end if; Sort(Test_2); if Test_2 /= Expected then raise Program_Error; end if; Sort(Test_3); if Test_3 /= Expected then raise Program_Error; end if; Sort(Test_4); if Test_4 /= Expected then raise Program_Error; end if; end; If Sort where defined according to my proposal, the semantic would make sure that all Sort(Test_?) behave identical (except, perhaps, when the programmer does some really crazy and unportable things with UNCHECKED_* or so). Thus, you could replace the four test cases above by a single one: declare Test: Integer_Array := (3,6,2,3,1); Expected : constant Integer_Array := (1,2,3,3,6); begin Sort(Test); if Test /= Expected then raise Program_Error; end if; end; **************************************************************** From: Stefan Lucks Sent: Tuesday, January 16, 2018 5:06 PM > I don't understand this obsession over a handful of potentially extra > test cases. I can well understand that you have limited time and may consider other issues more important, turning this one down. In any case, I appreciate that you did take your time to discuss this. But if you think, this an "obsession" of mine -- I can assure you, this is not the case. ;-) I'll make two final points, and then I'll stop discussing the proposal: 1. The main issue is not increasing the number of test cases, this is just a symptom of the disease. The disease is the lack of expressiveness in subprogram specifications, namely not being able to specify that "this array parameter is index-agnostic". Note that it is not always so obvious, as in the cases of Sort and Index, whether a the functionality of the subprogram requires/allows a parameter to be index-agnostic or not. Then, it would be good if the spec would provide this information. 2. The entire idea of an implicit shifting of array parameters has always been a part of Ada. You don't believe me? Remember that Ada's predefined "=" for arrays is index-agnostic, since the comparison compares the arrays without the bounds. Which, as you know, leads to some ugly surprises. Even if A=B evaluates to True, P(A) can do something entirely different from P(B). Not good, but forcing the user to always manually shift the arrays for an index-agnostic comparison would be worse! Don't worry! I would not even think of proposing to change the predefined "=". I would not even dare to make any jokes about it. ;-) In any case, being able to put "this behaves similarly to the predefined '='" into a spec would be useful. **************************************************************** From: Randy Brukardt Sent: Tuesday, January 16, 2018 5:25 PM > Do you think, greatly increasing the number of test cases you need > does not matter? (1) If you're writing test cases by hand, it does not matter because your coverage is going to be weak regardless of how many test cases you write (humans are bad at this, and I say that as a human that writes tests specifically for this purpose [ACATS]); (2) If you use a tool to create the test cases, the number doesn't matter (at least until the number reaches hundreds of thousands). QED. **************************************************************** From: Randy Brukardt Sent: Tuesday, January 16, 2018 5:35 PM ... > 1. The main issue is not increasing the number of test cases, this is > just a symptom of the disease. The disease is the lack of > expressiveness in subprogram specifications, namely not being able to > specify that "this array parameter is index-agnostic". Clients don't care about that property. Indeed, the rare routine for which the indexes matter is the outlier (note that I'm not including questions/answers in terms of the bounds here -- in that case, neither the client nor the implementation cares about the bounds per-se, they just have to be careful to make the question/use the answer with the original array and its bounds). Other cases are so rare, and are argubly bad design, that they're hardly worth worrying about at the language level. So my objection is elevating this usual case to something that needs a special declaration. It's just adding noise to a specification, and that makes it harder to read with no significant benefit. **************************************************************** From: Simon Wright Sent: Wednesday, January 17, 2018 3:17 AM > Clients don't care about that property. Indeed, the rare routine for > which the indexes matter is the outlier (note that I'm not including > questions/answers in terms of the bounds here -- in that case, neither > the client nor the implementation cares about the bounds per-se, they > just have to be careful to make the question/use the answer with the > original array and its bounds). I support Randy in this. The common understanding must be that the implementation is index-agnostic; if not, a precondition would make it clear (e.g. Pre => Param'First = 1) **************************************************************** From: Jeffrey R. Carter Sent: Wednesday, January 17, 2018 11:25 AM >>>   2. You write proper (black-box) tests, with identical inputs, >>> except for >>>      shifting the bounds. >> >> This is true of every subprogram. > > If the functionality of the subprogram is index-agnostic, you should > not need to to that. As you don't need to do in other languages (which > then suffer from only supporting index-agnostic arrays, of course). Sorry, I forgot to trim off everything after "tests". The point is, for every subprogram you use, you either trust the author to get it right or you convince yourself that he did. Mostly I do the former; I suspect most people do. Anyway, if I pass a slice of a 1D array that's a sequence, so the indices don't matter, to a subprogram, I don't care whether the subprogram normalizes the indices or not. I only care that it does what it's supposed to and has acceptable performance. ****************************************************************