!standard 3.9.4(32/2) 21-05-07 AI12-0312-1/12 !standard 3.10.1(23) !standard 3.10.2(41) !standard 4.2.1(0) !standard 4.3.3(44) !standard 4.3.5(0) !standard 4.5.7(21/3) !standard 4.5.8(13/3) !standard 4.5.10(0) !standard 5.5(21) !standard 6.1.1(42/3) !standard 6.1.2(0) !standard 7.3.2(24/3) !standard 7.3.3(0) !standard 11.3(7) !standard 13.4(12) !standard 13.4(14) !standard B.3(77) !standard B.3(79/3) !standard B.3(83) !class presentation 19-02-07 !status Amendment 1-2012 19-10-29 !status ARG Approved 10-0-0 19-10-06 !status work item 19-02-07 !status received 19-02-07 !priority Low !difficulty Easy !subject Examples for Ada 202x !summary Additional examples are added, and existing ones improved. !question Some new parts of the Standard have few or no examples, while others have too many examples (see 4.3.5). We need a more consistent use of examples, right? (Yes.) !response (See Summary.) !wording Modify 3.9.4(32/2): ... -- Add [George]{Casey} (see 3.10.1) to the cashier's queue: Append (Cashier, [George]{Casey}); -- After payment, move [George]{Casey} to the sandwich counter queue: Transfer (Cashier, Counter); Modify 3.10.1(23): My_Car, Your_Car, Next_Car : Car_Name := new Car; -- see 4.8 [George]{Casey} : Person_Name := new Person(M); ... [George]{Casey}.Vehicle := Your_Car; [Author's note: George was changed to Casey, because a more unisex name was asked for in the comments against the example for 4.5.7(21/3), and googling famous people named Casey produces about an even mix of the sexes. Chris produced mostly male for example, and Pat was also heavily male oriented. We changed Martha to Becky in order to keep the names the same length.] Modify 3.10.2(41): [Martha]{Becky} : Person_Name := new Person(F); -- see 3.10.1 Cars [ ]: array (1..2) of aliased Car; ... [Martha]{Becky}.Vehicle := Cars(1)'Access; [George]{Casey}.Vehicle := Cars(2)'Access; Append after 4.2.1(9/5): Examples subtype Roman_Character is Character with Static_Predicate => Roman_Character in 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'; Max_Roman_Number : constant := 3_999; -- MMMCMXCIX type Roman_Number is range 1 .. Max_Roman_Number with String_Literal => To_Roman_Number; function To_Roman_Number (S : String) return Roman_Number with Pre => S'Length > 0 and then (for all Char of S => Char in Roman_Character); function To_Roman_Number (S : String) return Roman_Number is (declare R : constant array (Integer range <>) of Integer := (for D in S'Range => Roman_Digit'Enum_Rep (Roman_Digit'Value (''' & S(D) & '''))); -- See 3.5.2 and 13.4 begin [for I in R'Range => (if I < R'Last and then R(I) < R(I + 1) then -1 else 1) * R(I))] 'Reduce("+", 0) ); X : Roman_Number := "III" * "IV" * "XII"; -- 144 (that is, CXLIV) Append after 4.3.3(46/5): Empty_Matrix : constant Matrix := []; -- A matrix without elements Delete paragraph 4.3.5(86/5-87-5). [Already show example of empty container aggregate] Delete paragraph 4.3.5(97/5). [Alternate form example shown above] Delete paragraphs 4.3.5(99/5 - 100/5). [Already show example of empty container aggregate] Delete paragraphs 4.3.5(105/5 - 106/5). [Example is not that different from others] Append after 4.5.7(21/3): Examples Put_Line ("Casey is " & (if Casey.Sex = M then "Male" else "Female")); -- see 3.10.1 function Card_Color (Card : Suit) return Color is -- see 3.5.1 (case Card is when Clubs | Spades => Black, when Hearts | Diamonds => Red); Modify 4.5.8 (13/3): pragma Assert (for some X in 2 .. N {when X * X <= N}[/ 2] => N mod X = 0); {-- see iterator_filter in 5.5} Append after 4.5.10 (53/5): A parallel reduction expression used to calculate the mean of the elements of a two dimensional array of subtype Matrix (see 3.6) that are greater than 100.0: type Accumulator is record Sum : Real; -- See 3.5.7 Count : Integer; end record; function Accumulate (L, R : Accumulator) return Accumulator is (Sum => L.Sum + R.Sum, Count => L.Count + R.Count); function Average_of_Values_Greater_Than_100 (M : Matrix) return Real is (declare Acc : constant Accumulator := [parallel for Val of M when Val > 100.0 => (Val, 1)] 'Reduce(Accumulate, (Sum => 0, Count => 0)); begin Acc.Sum / Real(Acc.Count)); Modify 5.5(22/5): "Example of a {simple} parallel loop{s}: " Add after 5.5(23/5): Example of a parallel loop with a chunk specification: declare subtype Chunk_Number is Natural range 1 .. 8; Partial_Sum, Partial_Max : array (Chunk_Number) of Natural := (others => 0); Partial_Min : array (Chunk_Number) of Natural := (others => Natural'Last); begin parallel (Chunk in Chunk_Number) for I in Grid'Range(1) loop declare True_Count : constant Natural := [for J in Grid'Range(2) => (if Grid (I, J) then 1 else 0)]'Reduce("+",0); begin Partial_Sum (Chunk) := @ + True_Count; Partial_Min (Chunk) := Natural'Min(@, True_Count); Partial_Max (Chunk) := Natural'Max(@, True_Count); end; end loop; Put_Line("Total=" & Partial_Sum'Reduce("+", 0) & ", Min=" & Partial_Min'Reduce(Natural'Min, Natural'Last) & ", Max=" & Partial_Max'Reduce(Natural'Max, 0)); end; For an example of an iterator_filter, see 4.5.8. Append after 6.1.1(42/3): 6 For an example of the use of these aspects and attributes, see the Streams Subsystem definitions in 13.13.1. Append after 6.1.2(50/5): NOTES For an example of the use of these aspects and attributes, see the Vector container definition in A.18.2. Append after 7.3.2(24/3): Examples -- A work scheduler where only urgent work can be scheduled for weekends package Work_Orders is -- See 3.5.1 for type declarations of Level, Day, and Weekday type Work_Order is private with Type_Invariant => Day_Scheduled (Work_Order) in Weekday or else Priority (Work_Order) = Urgent; function Schedule_Work (Urgency : in Level; To_Occur : in Day) return Work_Order with Pre => Urgency = Urgent or else To_Occur in Weekday; function Day_Scheduled (Order : in Work_Order) return Day; function Priority (Order : in Work_Order) return Level; procedure Change_Priority (Order : in out Work_Order; New_Priority : in Level; Changed : out Boolean) with Post => Changed = (Day_Scheduled(Order) in Weekday or else Priority(Order) = Urgent); private type Work_Order is record Scheduled : Day; Urgency : Level; end record; end Work_Orders; package body Work_Orders is function Schedule_Work (Urgency : in Level; To_Occur : in Day) return Work_Order is (Scheduled => To_Occur, Urgency => Urgency); function Day_Scheduled (Order : in Work_Order) return Day is (Order.Scheduled); function Priority (Order : in Work_Order) return Level is (Order.Urgency); procedure Change_Priority (Order : in out Work_Order; New_Priority : in Level; Changed : out Boolean) is begin -- Ensure type invariant is not violated if Order.Urgency = Urgent or else (Order.Scheduled in Weekday) then Changed := True; Order.Urgency := New_Priority; else Changed := False; end if; end Change_Priority; end Work_Orders; Append after 7.3.3(9/5): NOTES For an example of the use of this aspect, see the Vector container definition in A.18.2. Append after 11.3(7): -- For an example of a raise expression, see the Streams Subsystem definitions in 13.13.1. Modify 13.4(12): Example{s} of [an] enumeration representation clause{s}: Append after 13.4(14): -- See 3.5.2 for Roman_Digit use ('I' => 1, 'V' => 5, 'X' => 10, 'L' => 50, 'C' => 100, 'D' => 500, 'M' => 1000); -- For an example of the use of attribute Enum_Rep, see 4.2.1. Modify B.3(77): --Calling the C Library Function{s} strcpy{ and printf} with Interfaces.C; procedure Test is package C renames Interfaces.C; use type C.char_array; -- Call strcpy: -- C definition of strcpy: char *strcpy(char *s1, const char *s2); -- This function copies the string pointed to by s2 (including the terminating null character) -- into the array pointed to by s1. If copying takes place between objects that overlap, -- the behavior is undefined. The strcpy function returns the value of s1. Append after B.3(78/3): [Editor's note: Use the deleted paragraph here.] -- Call printf: -- C definition of printf: int printf ( const char * format, ... ); -- This function writes the C string pointed by format to the standard output (stdout). -- If format includes format specifiers (subsequences beginning with %), the additional -- arguments following format are formatted and inserted in the resulting string -- replacing their respective specifiers. If the number of arguments does not match -- the number of format specifiers, or if the types of the arguments do not match -- the corresponding format specifier, the behaviour is undefined. On success, the -- printf function returns the total number of characters written to the standard output. -- If a writing error occurs, a negative number is returned. -- Note: since the C function's return value is of no interest, the Ada interface is a procedure procedure Printf (Format : in C.char_array; Param1 : in C.char_array; Param2 : in C.int) with Import => True, Convention => C_Variadic_1, External_Name => "printf"; Append after B.3(83): Printf("The String=%s, Length=%d", Chars1, Chars1'Length); !discussion A survey of the RM found that there were no examples in the defining sections for: - Conditional Expressions (4.5.7) - Reduction Expressions using a Combiner (4.5.10) - Pre and Post conditions (6.1.1) - Global Aspect (6.1.2) - Type Invariants (7.3.2) - Default Initial Conditions (7.3.3) - raise expressions and Predicate_Failure (11.3) - Image Attributes (4.10) - Variadic C functions (B.3) - User Defined Literals (4.2.1) - Explicit Chunk definition for parallel loops (5.5) - implicit subtype mark in object renames (8.5.1) - null array aggregates (4.3.3) The 'Image attribute has existed since Ada 83, but improved in Ada 202x, however it doesn't seem that an example is necessary. Similarly, there is an existing example for object renames, with an explicit subtype mark. It seems to not be worthwhile to add another example where the subtype mark is implicit. !corrigendum 3.9.4(32/2) @drepl @xcode<... --@ft<@i< Add George (see 3.10.1) to the cashier's queue:>> Append (Cashier, George); --@ft<@i< After payment, move George to the sandwich counter queue:>> Transfer (Cashier, Counter); ...> @dby @xcode<... --@ft<@i< Add Casey (see 3.10.1) to the cashier's queue:>> Append (Cashier, Casey); --@ft<@i< After payment, move Casey to the sandwich counter queue:>> Transfer (Cashier, Counter); ...> !corrigendum 3.10.1(23) @drepl @xcode Car; --@ft<@i< see 4.8>> George : Person_Name := @b Person(M); ... George.Vehicle := Your_Car;> @dby @xcode Car; --@ft<@i< see 4.8>> Casey : Person_Name := @b Person(M); ... Casey.Vehicle := Your_Car;> !corrigendum 3.10.2(41) @drepl @xcode Person(F); --@ft<@i< see 3.10.1>> Cars : @b (1..2) @b Car; ... Martha.Vehicle := Cars(1)'Access; George.Vehicle := Cars(2)'Access;> @dby @xcode Person(F); --@ft<@i< see 3.10.1>> Cars : @b (1..2) @b Car; ... Becky.Vehicle := Cars(1)'Access; Casey.Vehicle := Cars(2)'Access;> !corrigendum 4.2.1(0) @insc See the conflict file for the changes. !corrigendum 4.3.3(43) @dinsa @xcode True); --@ft<@i< see 3.6>> E : Bit_Vector(M .. N) := (@b =@> True); F : String(1 .. 1) := (1 =@> 'F'); --@ft<@i< a one component aggregate: same as "F">>> @dinst @xcode Matrix := []; --@ft<@i< A matrix without elements>>> !corrigendum 4.3.5(0) @insc See the conflict file for the changes. !corrigendum 4.5.7(21/3) @dinsa For the evaluation of a @fa, the @i@fa is first evaluated. If the value of the @i@fa is covered by the @fa of some @fa, then the @i@fa of the @fa is evaluated, converted to the type of the @fa, and the resulting value is the value of the @fa. Otherwise (the value is not covered by any @fa, perhaps due to being outside the base range), Constraint_Error is raised. @dinss @s8<@i> @xcode Casey.Sex = M @b "Male" @b "Female")); --@ft<@i< see 3.10.1>>> @xcode<@b Card_Color (Card : Suit) @b Color @b --@ft<@i< see 3.5.1>> (@b Card @b @b Clubs | Spades =@> Black, @b Hearts | Diamonds =@> Red);> !corrigendum 4.5.8(13/3) @drepl @xcode<@b Assert (@b X @b 2 .. N / 2 =@> N @b X = 0);> @dby @xcode<@b Assert (@b X @b 2 .. N @b X * X <= N =@> N @b X = 0); --@ft<@i< see >>@fa@ft<@i< in 5.5>>> !corrigendum 4.3.5(0) @dinsc See the conflict file for the changes. !corrigendum 5.5(21) @dinsa @xcode Next /= Head @b -- @ft<@i> Sum := Sum + Next.Value; Next := Next.Succ; @b Summation;> @dinss @i @xcode<-- @ft<@i> @b @b I @b Grid'Range(1) @b Grid(I, 1) := (@b J @b Grid'Range(2) =@> Grid(I,J) = True); @b;> @i @xcode (Chunk_Number) @b Natural := (@b => 0); Partial_Min : @b (Chunk_Number) @b Natural := (@b =@> Natural'Last); @xcode (Chunk @b Chunk_Number) @b I @b Grid'Range(1) @b @b True_Count : @b Natural := [@b J @b Grid'Range(2) =@> (@b Grid (I, J) @b 1 @b 0)]'Reduce("+",0); @b Partial_Sum (Chunk) := @@ + True_Count; Partial_Min (Chunk) := Natural'Min(@@, True_Count); Partial_Max (Chunk) := Natural'Max(@@, True_Count); @b; @b;> @xcode< Put_Line ("Total=" & Partial_Sum'Reduce("+", 0) & ", Min=" & Partial_Min'Reduce(Natural'Min, Natural'Last) & ", Max=" & Partial_Max'Reduce(Natural'Max, 0)); @b;> @i @fa@i<, see 4.5.8.> !corrigendum 6.1.1(42/2) @dinsa @s9 @dinst @s9 !corrigendum 6.1.2(0) @insc See the conflict file for the changes. !corrigendum 7.3.2(24/3) @dinsa @s9 that is inherited from type @i, the specified checks of the specific invariants of both the types @i and @i are performed. For a call of a primitive subprogram of type @i that is overridden for type @i, the specified checks of the specific invariants of only type @i are performed.> @dinss @s8<@i> @i @xcode<@b Work_Orders @b> @xcode< --@ft<@i< See 3.5.1 for type declarations of Level, Day, and Weekday>>> @xcode< @b Work_Order @b Type_Invariant =@> Day_Scheduled (Work_Order) @b Weekday @b Priority (Work_Order) = Urgent;> @xcode< @b Schedule_Work (Urgency : @b Level; To_Occur : @b Day) @b Work_Order @b Pre =@> Urgency = Urgent @b To_Occur @b Weekday;> @xcode< @b Day_Scheduled (Order : @b Work_Order) @b Day;> @xcode< @b Priority (Order : @b Work_Order) @b Level;> @xcode< @b Change_Priority (Order : @b Work_Order; New_Priority : @b Level; Changed : @b Boolean) @b Post =@> Changed = (Day_Scheduled(Order) @b Weekday @b Priority(Order) = Urgent);> @xcode<@b> @xcode< @b Work_Order @b Scheduled : Day; Urgency : Level; @b;> @xcode<@b Work_Orders;> @xcode<@b Work_Orders @b> @xcode< @b Schedule_Work (Urgency : @b Level; To_Occur : @b Day) @b Work_Order @b (Scheduled =@> To_Occur, Urgency =@> Urgency);> @xcode< @b Day_Scheduled (Order : @b Work_Order) @b Day @b (Order.Scheduled);> @xcode< @b Priority (Order : @b Work_Order) @b Level @b (Order.Urgency);> @xcode< @b Change_Priority (Order : @b Work_Order; New_Priority : @b Level; Changed : @b Boolean) @b @b --@ft<@i< Ensure type invariant is not violated>> @b Order.Urgency = Urgent @b (Order.Scheduled @b Weekday) @b Changed := True; Order.Urgency := New_Priority; @b Changed := False; @b; @b Change_Priority;> @xcode<@b Work_Orders;> !corrigendum 7.3.3(0) @insc See the conflict file for the changes. !corrigendum 11.3(7) @dinsa @xcode<@b; --@ft<@i< re-raise the current exception>>> @dinst @xcode<--@ft<@i< For an example of a raise expression, see the Streams Subsystem definitions in 13.13.1.>>> !corrigendum 13.4(12) @drepl @i @dby @i !corrigendum 13.4(14) @dinsa @xcode<@b Mix_Code @b (ADD =@> 1, SUB =@> 2, MUL =@> 3, LDA =@> 8, STA =@> 24, STZ =@>33);> @dinss @xcode<--@ft<@i< See 3.5.2.>> @b Roman_Digit @b ('I' =@> 1, 'V' =@> 5, 'X' =@> 10, 'L' =@> 50, 'C' =@> 100, 'D' =@> 500, 'M' =@> 1000);> @xcode<--@ft<@i< For an example of the use of attribute Enum_Rep, see 4.2.1.>>> !corrigendum B.3(77) @drepl @xcode<@ft<@i<--Calling the C Library Function strcpy>> @b Interfaces.C; @b Test @b @b C @b Interfaces.C; @b C.char_array; @ft<@i<-- Call strcpy:>> @ft<@i<-- C definition of strcpy: char *strcpy(char *s1, const char *s2);>> @ft<@i<-- This function copies the string pointed to by s2 (including the terminating null character)>> @ft<@i<-- into the array pointed to by s1. If copying takes place between objects that overlap,>> @ft<@i<-- the behavior is undefined. The strcpy function returns the value of s1.>>> @dby @xcode<@ft<@i<--Calling the C Library Functions strcpy and printf>> @b Interfaces.C; @b Test @b @b C @b Interfaces.C; @b C.char_array; @ft<@i<-- Call strcpy:>> @ft<@i<-- C definition of strcpy: char *strcpy(char *s1, const char *s2);>> @ft<@i<-- This function copies the string pointed to by s2 (including the terminating null character)>> @ft<@i<-- into the array pointed to by s1. If copying takes place between objects that overlap,>> @ft<@i<-- the behavior is undefined. The strcpy function returns the value of s1.>>> !corrigendum B.3(78/3) @dinsa @xcode< @ft<@i<-- Note: since the C function's return value is of no interest, the Ada interface is a procedure>> @b Strcpy (Target : @b C.char_array; Source : @b C.char_array) @b Import =@> True, Convention =@> C, External_Name =@> "strcpy";> @dinss @xcode< @ft<@i<-- Call printf:>> @ft<@i<-- C definition of printf: int printf ( const char * format, ... );>> @ft<@i<-- This function writes the C string pointed by format to the standard output (stdout).>> @ft<@i<-- If format includes format specifiers (subsequences beginning with %), the additional>> @ft<@i<-- arguments following format are formatted and inserted in the resulting string>> @ft<@i<-- replacing their respective specifiers. If the number of arguments does not match>> @ft<@i<-- the number of format specifiers, or if the types of the arguments do not match>> @ft<@i<-- the corresponding format specifier, the behaviour is undefined. On success, the>> @ft<@i<-- printf function returns the total number of characters written to the standard output.>> @ft<@i<-- If a writing error occurs, a negative number is returned.>>> @xcode< @ft<@i<-- Note: since the C function's return value is of no interest, the Ada interface is a procedure>> @b Printf (Format : @b C.char_array; Param1 : @b C.char_array; Param2 : @b C.int) @b Import =@> True, Convention =@> C_Variadic_1, External_Name =@> "printf";> !corrigendum B.3(83) @drepl @xcode<@ft<@i<-- Now Chars1(1..6) = "qwert" & C.Nul>>> @dby @xcode< @ft<@i<-- Now Chars1(1..6) = "qwert" & C.Nul>>> @xcode< Printf("The String=%s, Length=%d", Chars1, Chars1'Length);> !ASIS None needed. !ACATS test No ACATS tests needed. !appendix From: Brad Moore Sent: Friday, February 22, 2019 5:45 PM I managed to get an initial draft of this AI, possibly just under or over the deadline. So far, Jeff and Ed have not seen this or have had a chance to comment on it, yet they are listed as being involved in the homework assignment. Had I finished this earlier, I would have sent it an initial draft out to them, but since it is this close to the deadline, I figured it would be better to just send this out to the group, in case we want to discuss it at the meeting. In the meantime, it would be great if Jeff and/or Ed could review with an eye to; 1) Did I miss some features that need examples? 2) Are the examples I have satisfactory, or could be improved upon? 3) Did I reduce the container aggregate examples sufficiently, or perhaps cut off too much? 4) Do you have additional examples that you'd like to see added? Some of the features are actually Ada 2005 features, such as type invariants. However, since the goal of this work is to make the RM more consistent in the examples department, this seemed like a good idea to provide a few extra examples. **************************************************************** From: Brad Moore Sent: Friday, February 22, 2019 6:04 PM A minor correction to the User Defined Literal example: function To_Date (Value : String) return Date is (declare Integer_String : constant String := Integer'Image (Integer'Value (Value)); begin (Day => Integer'Value(Integer_String (Integer_String'Last-5 .. Integer_String'Last-4)), Year => Integer'Value (Integer_String (Integer_String'Last - 3 .. Integer_String'Last)), Month => Month_Name'Val (Integer'Value(Integer_String (Integer_String'First .. Integer_String'Last - 6))-1))); In determining the Month component, I needed to subtract 1 before applying 'Val on the Month_Name enumeration. Also, I believe I was missing a semicolon on the declaration of Integer_String in the declare expression. The above has these corrections. **************************************************************** From: Randy Brukardt Sent: Friday, February 22, 2019 9:32 PM > I managed to get an initial draft of this AI, possibly just under or > over the deadline. This message was time-stamped at 5:45 CST, so you were 45 minutes late. As always, I ask that people that might be late send me a heads-up. It didn't matter today, as processing the draft of the hour for AI12-0191-1 took forever. > Some of the features are actually Ada 2005 features, such as type invariants. Type invariants are an Ada 2012 feature. Not that that matters to your point... > A minor correction to the User Defined Literal example: Correction applied. > function To_Date (Value : String) return Date is > (declare > Integer_String : constant String := Integer'Image (Integer'Value (Value)); > begin > (Day => Integer'Value(Integer_String (Integer_String'Last-5 .. Integer_String'Last-4)), > Year => Integer'Value (Integer_String (Integer_String'Last - 3 .. Integer_String'Last)), > Month => Month_Name'Val (Integer'Value(Integer_String > (Integer_String'First .. Integer_String'Last - 6))-1))); Some of these lines are too long to put in the RM as-is. It would help to break them to be shorter, so I don't have to guess where to do it. >!wording > >delete paragraphs 4.3.5 (86/5 - 86/5) [Already show example of empty container aggregate] You have a range with one paragraph in it; I suspect that you meant something else. >Append after 4.5.7 (21/3) ... >Value : constant Natural := (case >type Roman_Digit is ('I', 'V', 'X', 'L', 'C', 'D', 'M'); There is something wrong with this! I haven't previously encountered the "case type expression"! :-) **************************************************************** From: Brad Moore Sent: Friday, February 22, 2019 10:23 PM >> I managed to get an initial draft of this AI, possibly just under or >> over the deadline. > > This message was time-stamped at 5:45 CST, so you were 45 minutes > late. As always, I ask that people that might be late send me a > heads-up. It didn't matter today, as processing the draft of the hour > for AI12-0191-1 took forever. Whew! Glad to hear it made it under the door even if only by luck.... ... >> function To_Date (Value : String) return Date is >> (declare >> Integer_String : constant String := Integer'Image > (Integer'Value (Value)); >> begin >> (Day => Integer'Value(Integer_String (Integer_String'Last-5 .. > Integer_String'Last-4)), >> Year => Integer'Value (Integer_String (Integer_String'Last >> - 3 > .. Integer_String'Last)), >> Month => Month_Name'Val (Integer'Value(Integer_String > (Integer_String'First .. Integer_String'Last - 6))-1))); > > Some of these lines are too long to put in the RM as-is. It would help > to break them to be shorter, so I don't have to guess where to do it. I found it helped to shorten some of the variable names. Here is what I have currently. function To_Date (Value : String) return Date is declare S : constant String := Integer'Image (Integer'Value (Value)); begin (Day => Integer'Value (S (S'Last-5 .. S'Last-4)), Year => Integer'Value (S (S'Last-3 .. S'Last)), Month => Month_Name'Val (Integer'Value (S (S'First .. S'Last-6))-1)); One question. I wasn't sure if I need an extra set of parens around the expression function, or can the parens used for the aggregate object suffice? If not, you'll need to add those. Also not sure if style here warrants putting the declare and begin on the same line, but I quite like the new look of this example. >>!wording >> >>delete paragraphs 4.3.5 (86/5 - 86/5) [Already show example of empty > container aggregate] > > You have a range with one paragraph in it; I suspect that you meant > something else. I meant (86/5 - 87/5) >>Append after 4.5.7 (21/3) > ... >>Value : constant Natural := (case >>type Roman_Digit is ('I', 'V', 'X', 'L', 'C', 'D', 'M'); > > There is something wrong with this! I haven't previously encountered the > "case type expression"! :-) These two lines are garbage and should be deleted.... "Value : constant Natural := (case type Roman_Digit is ('I', 'V', 'X', 'L', 'C', 'D', 'M');" **************************************************************** From: Randy Brukardt Sent: Friday, February 22, 2019 10:59 PM ... > Whew! Glad to hear it made it under the door even if only by luck.... ...but not with this message. :-) ... > > Some of these lines are too long to put in the RM as-is. It would > > help to break them to be shorter, so I don't have to guess where to do it. > > > > I found it helped to shorten some of the variable names. Here is what > I have currently. > > function To_Date (Value : String) return Date is > declare S : constant String := Integer'Image (Integer'Value > (Value)); begin > (Day => Integer'Value (S (S'Last-5 .. S'Last-4)), > Year => Integer'Value (S (S'Last-3 .. S'Last)), > Month => Month_Name'Val (Integer'Value (S (S'First .. S'Last-6))-1)); > > One question. I wasn't sure if I need an extra set of parens around > the expression function, or can the parens used for the aggregate > object suffice? If not, you'll need to add those. This example doesn't have any parens at all (needed around the declare expression), so that's a problem. And putting the declare/declaration/begin on the same line isn't the recommended style, and it's probably too long anyway (only room for roughly 70 characters on a line in an example, including the indenting white space), so let's not do that. > Also not sure if style here warrants putting the declare and begin on > the same line, but I quite like the new look of this example. See above. **************************************************************** From: Jeff Cousins Sent: Monday, February 22, 2019 2:27 PM Presumably "delete paragraphs (86/5 – 86/5)" should be "Delete paragraphs (86/5 – 87/5)". "Add after 5.5 (23/5)" It would help to begin with a comment of explanation, e.g. that it is an example of explicit chunk definition. "Append after 11.3 (7)" I can only see one example in 13.13.1; if so, this should be in singular. "Append after B.3 (78/3)" It would help to begin with a comment of explanation, e.g. that it is an example of a variadic C function. "Append after 4.5.7 (21/3)" Is the last line simply spurious, partly cut and pasted from 3.5.2? Also, could all these be put in RM order? **************************************************************** From: Brad Moore Sent: Monday, March 7, 2019 5:18 PM Here are some relatively minor updates to AI12-0312-1 This addresses editorial comments raised by Randy and Jeff on my previous submission. [This is version /02 of the AI - Editor.] **************************************************************** From: Brad Moore Sent: Friday, March 22, 2019 2:32 PM Here is a minor update to AI12-0312-1 [This is version /03 of the AI - Editor.] Steve Baird had commented that it would be good to have an example of a parallel reduction expression with a combiner. Thanks also to Steve for suggesting an example, which I incorporated. I extended the example to use an iterator filter, which makes the example more practical, as otherwise one could just do an integer reduction / A'Length to get a mean. The example is: Append after 4.5.10 (53/5) A parallel reduction expression that outputs the mean of the elements with even values for an array: type Accumulator is record Sum : Integer; Addends : Natural; end;= function Combine (L, R : Accumulator) return Accumulator is (Sum => L.Sum + R.Sum, Addends => L.Addends + R.Addends); function Reduce (A : Accumulator; Addend : Integer) is (Sum => A.Sum + Addend, Addends => A.Addends + 1); function Mean (A : Accumulator) return Integer is (A.Sum / A.Addends); Put_Line("The mean of the even values of A is" & Mean ([for Val of A when (A mod 2) = 0 => Val] 'Parallel_Reduce(Reduce, (Sum => 0, Addends => 0), Combine))); **************************************************************** From: Jean-Pierre Rosen Sent: Saturday, March 23, 2019 6:49 PM There are several glitches in the example that caused me quite a hard time understanding it... 1) Mean ([for Val of A when (A mod 2) = 0 => Val] "A mod 2" should be "Val mod 2", right? 2) In the above, A is some array whose declaration is not shown, while everywhere else, A is the accumulator. Please use a different name (and possibly show the declaration). 3) It took me quite a time to spot the difference between "addend" and "addends". Using "count" instead of "addends" would help a lot... **************************************************************** From: Brad Moore Sent: Sunday, March 24, 2019 2:20 PM > There are several glitches in the example that caused me quite a hard > time understanding it... > > 1) > Mean ([for Val of A when (A mod 2) = 0 => Val] > "A mod 2" should be "Val mod 2", right? Thanks for catching this, Steve Baird also caught this. He also noted there was an extraneous "=" after the declaration of the Accumulator type. > > 2) > In the above, A is some array whose declaration is not shown, while > everywhere else, A is the accumulator. Please use a different name (and > possibly show the declaration). The example was placed immediately after the following one in the RM, (4.5.10 53/5) Calculate the sum of elements of an array of integers: A'Reduce("+",0) -- See 4.3.3. If you follow that reference you find the declaration of A, which is; type Table is array(1 .. 10) of Integer; A : Table := (7, 9, 5, 1, 3, 2, 4, 8, 6, 0); That being said, this array is too small to likely be worth processing with a parallel reduction expression, so I think the example needs to hint at processing a much larger array. I decided to rework this example, to operate on the Matrix type defined in the RM, which is an unconstrained array type of real. By making the example an expression function, where the matrix is passed as a parameter, that will allow a potentially very large array to be processed, without having to declare the array in the RM. Since the type of the sum is now Real, instead of Integer, the iterator filter should be something like filtering out all values <= 100.0, rather than even numbers, which is probably a more realistic filter anyway. There also were a couple of other glitches in the AI, which I noticed, making a better case for submitting an new update, which I have done below. - The Accumulator type is missing "record" after end, i.e. "end record;" - The function Reduce doesn't specify what it is returning - The expression function, To_Date for an earlier example in the AI Appended after 4.2.1(10/5) has the first opening paren after the declare of the declare expression, instead of before. - In the discussion section there are some comments about missing aspect examples ('Global and 'Stable_Properties) that have since been added to the RM draft, so those comments are no longer needed. It also needs to mention that we were missing an example of a reduction expression using a Combiner. > 3) > It took me quite a time to spot the difference between "addend" and > "addends". Using "count" instead of "addends" would help a lot... I agree that is pretty subtle, but I also find Count is perhaps too vague, and doesn't describe what we are counting. So I would use Addend_Count instead, which seems to address your comment, as well as my own. Here is a new version of the AI after these updates. Note that I got rid of the Mean function, since the new expression function incorporates the mean. I also used a declare expression in the expression function which does the parallel reduction. Here is the updated example for the AI A parallel reduction expression used to calculate the mean of the elements of a two dimensional array of subtype Matrix (see 3.6) that are greater than 100.0: type Accumulator is record Sum : Real; -- See 3.5.7 Addend_Count : Integer; end record; function Combine (L, R : Accumulator) return Accumulator is (Sum => L.Sum + R.Sum, Addend_Count => L.Addend_Count + R.Addend_Count); function Reduce (A : Accumulator; Addend : Real) return Accumulator is (Sum => A.Sum + Addend, Addend_Count => A.Addend_Count + 1); function Average_of_Values_Greater_Than_100 (M : Matrix) return Real is (declare Acc : constant Accumulator := [for Val of M when Val > 100.0 => Val] 'Parallel_Reduce(Reduce, (Sum => 0, Addends => 0), Combine); begin Acc.Sum / Real(Acc.Addend_Count)); And here is the full updated AI, which the changes described above; [This is version /04 of the AI - Editor.] **************************************************************** From: Brad Moore Sent: Thursday, June 20, 2019 12:59 AM In Warsaw, for AI12-0312-1 Examples for Ada 2020, I had an example for user defined literals involving specifying dates as integers using underscores to separate the fields. This generally was not liked, (for example Erhard commented that one could create an aggregate assignment for this, which is safer, which is a good point). So since then I have conjured up another example, that I think is an improvement. This example uses the Roman numeral types in the RM to allow one to express values for an integer type, using roman numeral strings. I think this means, one would be able to write expressions such as; X : Roman_Number := "III" * "IV" * "XII"; -- = 144 or "CXLIV", when 'Image is applied As I am thinking the compiler will deduce from the context that all values are of the same type, and thus should be converted to integer Roman_Number values before applying the operations. Does that sound right? In any case, the example code I have is an interesting example in that it uses a lot of newer features. - Static Subtype Predicates - Dynamic Subtype Predicate - User Defined String Literals - Quantified Expression - Expression Functions - Declare Expressions - Conditional Expressions - Reduction Expressions - Case Expressions - Array Aggregate with an iterator expression - When clause on an iterator expression - User defined 'Image - Preconditions So I am curious to see if others think this is an appropriate example, or if someone might suggest a better idea. The user defined literal going from Roman Numerals to Integer was surprisingly simple with the new features. Going the other way round, with 'Image was surprisingly more difficult than expected, but we could drop off the 'Image easily enough from the example. type Roman_Digit is ('I', 'V', 'X', 'L', 'C', 'D', 'M'); -- From RM 3.5.2 (11) type Roman is array(Positive range <>) of Roman_Digit; -- From RM 3.6 (26) subtype Roman_Character is Character with Static_Predicate => Roman_Character in 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'; subtype Roman_String is String with -- Not really needed for this example Dynamic_Predicate => (for all Char of Roman_String => Char in Roman_Character); subtype Roman_Number is Positive range 1 .. 1_000 -- Subtype with user-defined string literal with String_Literal => To_Roman_Number; for Roman_Number'Put_Image use Image; function To_Roman_Number (S : String) return Roman_Number with Pre => (for all Char of S => Char in Roman_Character); function To_Roman_Number (S : String) return Roman_Number is (declare R : constant array (Integer range <>) of Roman_Number := (for D of S => (case D is when 'I' => 1, when 'V' => 5, when 'X' => 10, when 'L' => 50, when 'C' => 100, when 'D' => 500, when 'M' => 1000)) begin [for I in R'Range => (if I < R'Last and then R(I) < R(I + 1) then -1 else 1) * R(I))] 'Reduce("+", 0) ); procedure Image (Arg : Integer; Stream : not null access Ada.Streams.Root_Stream_Type'Class) with Pre => (Arg <= 1000); procedure Image (Arg : Integer; Stream : not null access Ada.Streams.Root_Stream_Type'Class) is Lookup : constant array (Roman_Digit) of Roman_Number := ('I' => 1, 'V' => 5, 'X' => 10, 'L' => 50, 'C' => 100, 'D' => 500, 'M' => 1000); procedure Convert (Value : Natural) is Above : constant Roman_Digit := [for I in Lookup'Range when Lookup (I) >= Value => I] 'Reduce(Roman_Digit'Min, Roman_Digit'Last); begin if Value = 0 then return; elsif Value = 1 then Roman_Digit'Write (Stream, 'I'); elsif Value = Lookup (Above) then Roman_Digit'Write (Stream, Above); Convert (Value - Lookup (Above)); elsif Roman_Digit'Pos(Above) rem 2 = 0 and then Value >= Lookup (Above) - Lookup (Roman_Digit'Pred(Roman_Digit'Pred (Above))) then Roman_Digit'Write (Stream, Roman_Digit'Pred(Roman_Digit'Pred (Above))); Roman_Digit'Write (Stream, Above); Convert (Value - (Lookup (Above) - Lookup (Roman_Digit'Pred(Roman_Digit'Pred (Above))))); elsif Value /= Lookup (Above) and then Roman_Digit'Pos(Above) rem 2 = 1 and then Value >= Lookup (Above) - Lookup (Roman_Digit'Pred (Above)) then Roman_Digit'Write (Stream, Roman_Digit'Pred (Above)); Roman_Digit'Write (Stream, Above); Convert (Value - (Lookup (Above) - Lookup (Roman_Digit'Pred (Above)))); else Roman_Digit'Write (Stream, Roman_Digit'Pred(Above)); Convert (Value - Lookup (Roman_Digit'Pred (Above))); end if; end Convert; begin Convert (Arg); end Image; X : Roman_Integer := "III" * "IV" * "XII"; -- 144 ( i.e. CXLIV) ... -- Outputs "Answer is 144 ( CXLIV )" Put_Line ("Answer is" & Integer'Image(X) & " ( " & Roman_Number'Image(X) & " )"); I also have a working version of this, using Ada 2012 syntax, and one of my Stream Buffer class in case anyone is interested. **************************************************************** From: Tucker Taft Sent: Thursday, June 20, 2019 7:36 AM Interesting. Implementation is too long for the manual, but we could put just the specs in the manual. Might be clearer if you called the routine "Put_Image" rather than "Image". Might make a good ACATS test. > I think this means, one would be able to write expressions such as; > > X : Roman_Number := "III" * "IV" * "XII"; -- = 144 or "CXLIV", when 'Image is applied > > As I am thinking the compiler will deduce from the context that all > values are of the same type, and thus should be converted to integer > Roman_Number values before applying the operations. > > Does that sound right? > ... I have to think a bit about the overload resolution here. I am unsure whether the multiplication will work as you wrote it. Normally we require exactly one expected type for string literals, unlike for numeric literals. There is no "universal" type for string literals. But since you provided an expected type of Roman_Number it is probably fine. **************************************************************** From: Erhard Ploedereder Sent: Thursday, June 20, 2019 10:58 AM Cute example, much better than the old one, .... **************************************************************** From: Brad Moore Sent: Thursday, June 20, 2019 7:58 AM I've made a few minor fixes to this. In particular, the user-defined Image function needed to write to the stream as characters, not Roman Digits. Since Tullio privately mentioned an interest in seeing the Ada 2012 version, I might as well send out the update with the fix as well as the working Ada 2012 version to the group. Please see the attached tar file which contains both. [Not available here, see source below - Editor.] The Ada 202x version is in main_202x.adb, and the Ada 2012 version is in main.adb. The Ada 2012 version is actually a hybrid between Ada 2012 and Ada 202x, as it incorporates some 202x features that have already been implemented in GNAT, such as @, and array aggregate with iterators. The project file should compile with the latest GNAT community edition, released this month (or in May) For those not wanting to fiddle with untarring files, here are the relevant bits from the updated 202x version.... type Roman_Digit is ('I', 'V', 'X', 'L', 'C', 'D', 'M'); subtype Roman_Character is Character with Static_Predicate => Roman_Character in 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M'; subtype Roman_String is String with Dynamic_Predicate => (for all Char of Roman_String => Char in Roman_Character); subtype Roman_Number is Positive range 1 .. 1_000 with String_Literal => To_Roman_Number; for Roman_Number'Put_Image use Image; function To_Roman_Number (S : String) return Roman_Number with Pre => (for all Char of S => Char in Roman_Character); function To_Roman_Number (S : String) return Roman_Number is (declare R : constant array (Integer range <>) of Integer := (for D of S => (case D is when 'I' => 1, when 'V' => 5, when 'X' => 10, when 'L' => 50, when 'C' => 100, when 'D' => 500, when 'M' => 1000)) begin [for I in R'Range => (if I < R'Last and then R(I) < R(I + 1) then -1 else 1) * R(I))] 'Reduce("+", 0) ); procedure Image (Arg : Integer; Stream : not null access Ada.Streams.Root_Stream_Type'Class) with Pre => (Arg <= 1000); procedure Image (Arg : Integer; Stream : not null access Ada.Streams.Root_Stream_Type'Class) is Lookup : constant array (Roman_Digit) of Roman_Number := ('I' => 1, 'V' => 5, 'X' => 10, 'L' => 50, 'C' => 100, 'D' => 500, 'M' => 1000); procedure Convert (Value : Natural) is function To_Char (D : Roman_Digit) return Roman_Character is (case D is when 'I' => 'I', when 'V' => 'V', when 'X' => 'X', when 'L' => 'L', when 'C' => 'C', when 'D' => 'D', when 'M' => 'M'); Above : constant Roman_Digit := [for I in Lookup'Range when Lookup (I) >= Value => I] 'Reduce(Roman_Digit'Min, Roman_Digit'Last); begin -- Convert if Value = 0 then return; elsif Value = 1 then Character'Write (Stream, 'I'); elsif Value = Lookup (Above) then Character'Write (Stream, To_Char (Above)); Convert (Value - Lookup (Above)); elsif Roman_Digit'Pos (Above) rem 2 = 0 and then Value >= Lookup (Above) - Lookup (Roman_Digit'Pred (Roman_Digit'Pred (Above))) then Character'Write (Stream, To_Char (Roman_Digit'Pred (Roman_Digit'Pred (Above)))); Character'Write (Stream, To_Char (Above)); Convert (Value - (Lookup (Above) - Lookup (Roman_Digit'Pred (Roman_Digit'Pred (Above))))); elsif Value /= Lookup (Above) and then Roman_Digit'Pos (Above) rem 2 = 1 and then Value >= Lookup (Above) - Lookup (Roman_Digit'Pred (Above)) then Character'Write (Stream, To_Char (Roman_Digit'Pred (Above))); Character'Write (Stream, To_Char (Above)); Convert (Value - (Lookup (Above) - Lookup (Roman_Digit'Pred (Above)))); else Character'Write (Stream, To_Char (Roman_Digit'Pred (Above))); Convert (Value - Lookup (Roman_Digit'Pred (Above))); end if; end Convert; begin -- Image Convert (Arg); end Image; X : Roman_Number := "III" * "IV" * "XII"; -- 144 ( i.e. CXLIV) ... -- Outputs "Answer is 144 ( CXLIV ) Put_Line ("Answer is" & Integer'Image(X) & " ( " & Roman_Number'Image(X) & " )"); **************************************************************** From: Randy Brukardt Sent: Thursday, June 20, 2019 11:36 AM > Interesting. Implementation is too long for the manual, but we could > put just the specs in the manual. I think it is OK if all of the stuff unrelated to user-defined literals is left out (that is, all of the Image stuff). Perhaps that fits in the Image section to show a redefinition, but it definitely doesn't belong in a user-defined literal example. I'd also leave out the unused Roman_String, especially as one could also declare a string type thusly: type Other_Roman_String is array (Positive range <>) of Roman_Character; Do you use Roman_Character for anything in this example? > Might be clearer > if you called the routine "Put_Image" rather than "Image". Agreed. Why confuse the usage (usually a routine "Image" returns a String). > Might make a good ACATS test. Well, no, because it uses a kitchen sink of features and would cover objectives from multiple sections. We try to make ACATS tests that mostly use basic stuff outside of the tested feature so that the tests are useable for staged implementations (no one can implement everything at once). I suppose it could make *two* ACATS tests (one for user-defined literals, and one for user-defined image), but I'd probably suggest getting rid of the preconditions and predicates in that case (replacing them with regular code) - those things aren't under test here. **************************************************************** From: Randy Brukardt Sent: Wednesday, July 24, 2019 8:44 PM Jeff had the following comments on your AI that didn't get discussed in Warsaw as Jeff misplaced his notes. These ought to be addressed in the next draft along with the things noted in the minutes: 4.3.5(86/5 - 86/5) should be 4.3.5(86/5 - 87/5) 11.3 (7) There is only one raise expression in 13.13.1 so this should say "for an example of a raise expression..." B.3(78/3) needs a comment saying what it is about: -- Example of a variadic C function **************************************************************** From: Brad Moore Sent: Saturday, September 28, 2019 6:20 PM In the minutes from last meeting, I was asked to see if I could simplify the Reduction Expression example for 4.5.10 (53/5), possibly by using a set container. The example currently in the AI is; type Accumulator is record Sum : Real; -- See 3.5.7 Addend_Count : Integer; end record; function Combine (L, R : Accumulator) return Accumulator is (Sum => L.Sum + R.Sum, Addend_Count => L.Addend_Count + R.Addend_Count); function Reduce (A : Accumulator; Addend : Real) return Accumulator is (Sum => A.Sum + Addend, Addend_Count => A.Addend_Count + 1); function Average_of_Values_Greater_Than_100 (M : Matrix) return Real is (declare Acc : constant Accumulator := [parallel for Val of M when Val > 100.0 => Val] 'Reduce(Reduce, (Sum => 0, Addend_Count => 0), Combine); begin Acc.Sum / Real(Acc.Addend_Count)); I have given thought to using a container such as a set, but so far, have not arrived at anything sensible. However, I do think the example can be simplified, without using containers. We can eliminate the need for a separate combiner function by having the map/reduce part of the value_sequence produce an Accumulator object rather than a real value as an input to the reduction. The count part of the accumulator is 1, which gets tallied up by the reducer. If we do that, then the example becomes; type Accumulator is record Sum : Real; -- See 3.5.7 Count : Integer; end record; function Reduce (L, R : Accumulator) return Accumulator is (Sum => L.Sum + R.Sum, Count => L.Count + R.Count); function Average_of_Values_Greater_Than_100 (M : Matrix) return Real is (declare Acc : constant Accumulator := [parallel for Val of M when Val > 100.0 => (Val, 1)] 'Reduce(Reduce, (Sum => 0, Count => 0)); begin Acc.Sum / Real(Acc.Count)); It this enough of a simplification for this example? I looks pretty simple to me. **************************************************************** From: Jean-Pierre Rosen Sent: Sunday, September 29, 2019 12:15 AM > It this enough of a simplification for this example? I looks pretty simple to me. Much better; I'd rather call the function Reducer (or something else) to avoid the "'Reduce(Reduce" **************************************************************** From: Edmond Schonberg Sent: Sunday, September 29, 2019 8:17 AM Second. For greater clarity I would suggest the name “Accumulate” for that function. **************************************************************** From: Brad Moore Sent: Sunday, September 29, 2019 11:33 AM I agree with both comments, and ultimately went with Eds suggestion. I am now wondering though, whether we need the combiner field for parallel reduction at all? The combiner field (the third parameter to a reduction attribute is only needed for parallel reductions, when the types of the parameters to the reducer function are different types. For non-parallel reduction, there is no need to combine results from different threads of execution. This example shows that it can be possible to simplify the expression to a form that doesn't need the combiner, by using the map/reduce part of the value sequence to make both parameters of the reduction the same type. Another motivating example that was used for justifying the combiner is a concatenation example. Consider: function Accumulate(Result : in String; Letter : in Character) return String is (Result & Letter); Alphabet : constant String := [parallel for Letter in 'A' .. 'Z' => Letter]'Reduce(Accumulate, "", "&"); Admittedly, this is not a practical parallelism example, but it does illustrate my point. Again, the combiner can be eliminated here, and this can all be replaced with; Alphabet : constant String := [parallel for Letter in 'A' .. 'Z' => String'(1 => Letter)]'Reduce("&", ""); It is at least worthwhile to consider if eliminating the combiner from the syntax would "guide" programmers to write simpler, more readable code, which is one of the features I generally like about Ada. **************************************************************** From: Randy Brukardt Sent: Monday, September 30, 2019 8:24 PM ... > I am now wondering though, whether we need the combiner field for > parallel reduction at all? ... > This example shows that it can be possible to simplify the expression > to a form that doesn't need the combiner, by using the map/reduce part > of the value sequence to make both parameters of the reduction the > same type. Didn't we consider that when we designed this feature? Didn't we decide that the performance impact of eliminating the combiner would be unacceptable? After all, the only reason for parallelizing something is to improve the performance, and if doing that requires making a lot of expensive copies then that doesn't make much sense. I'm certain that we discussed this at least once and probably more than once. I'd suggest going back through the mail/minutes on this topic (AI12-0242-1 & AI12-0262-1) to see what we discussed about it in the past (rather than reopening a discussion that we've had in the past). IMHO, this feature is complex enough without forcing people to jump through hoops to figure out how to encode the parameters as both the same type. Just because something is *possible* doesn't mean that we ought to make the *only* way. And I'm pretty sure we had examples that didn't work well without allowing the parameters to be of different types (which then requires a combiner). >It is at least worthwhile to consider if eliminating the combiner from >the syntax would "guide" programmers to write simpler, more readable >code, which is one of the features I generally like about Ada. There's no reduction code that will ever qualify as "simpler" or "readable". The idea itself is a brain-bender for most - I'd guess that most of us will have to write out the expansion of any such expression to figure out what's going on. (There are some of course who find such things "natural", but they're weird. ;-) **************************************************************** From: Brad Moore Sent: Sunday, September 29, 2019 7:58 PM Here is my homework for the upcoming ARG meeting, which addresses all comments captured in the Warsaw meeting. [This is version /05 of the AI - Editor.] Also, I do have recollection of having addressed Jeff's comments a long time ago, so I was curious to find out why his early comments were not incorporated into the version we were looking at in Warsaw. It turns out that on March 7th, 2019, I sent out an update which addressed Jeff's comments. However, that version of the AI did not seem to properly make it into the system. There is a version 1.4 of the AI, which corresponds to Mar 8th, which seems like it should be the version that corresponds to my sent email, but it doesn't have the changes that I sent out. Anyway, I hope I now have addressed Jeff's comments in this new version of the AI. **************************************************************** From: Jean-Pierre Rosen Sent: Monday, September 30, 2019 12:04 AM Suggestion: (case D is when 'I' => 1, when 'V' => 5, when 'X' => 10, when 'L' => 50, when 'C' => 100, when 'D' => 500, when 'M' => 1000, when others => raise Program_Error with "Impossible")) **************************************************************** From: Jeff Cousins Sent: Monday, September 30, 2019 2:41 PM Thanks Brad. Some of the paragraph numbers seem odd places to give the examples: 6.1.1(41/3) (actually now up to 41/5) is in Implementation Permissions, 6.1.2(42/5) is in the middle of Legality Rules. **************************************************************** From: Randy Brukardt Sent: Monday, September 30, 2019 4:27 PM The paragraph numbers in "new" clauses, like 6.1.2, change every time there is a change to those clauses, so they have to be rechecked with every version. Also, we have a meta-rule allowing paragraph numbers of non-normative material to change, so if there are additions near the end of a clause, the paragraph numbers of examples may change. Which is a long-winded way of saying that Brad probably had the right paragraph numbers when he wrote the AI, but they've probably changed since. It would be hard to keep up. **************************************************************** From: Randy Brukardt Sent: Monday, September 30, 2019 9:01 PM >Here is my homework for the upcoming ARG meeting... I fixed up some of the "Modify" and "Append" headers to have the correct form. You have: >Modify A.18.2(8/5): > >type Vector is tagged private > with Constant_Indexing => Constant_Reference, > Variable_Indexing => Reference, > Default_Iterator => Iterate, > Iterator_Element => Element_Type, > Iterator_View => Stable.Vector, > Aggregate => (Empty => Empty, > Add_Unnamed => Append_One, > New_Indexed => New_Vector, > Assign_Indexed => Replace_Element), > Stable_Properties => (Length, Capacity, > Tampering_With_Cursors_Prohibited, > Tampering_With_Elements_Prohibited), > Default_Initial_Condition => > Length (Vector) = 0 and then > (not Tampering_With_Cursors_Prohibited (Vector)) and then > (not Tampering_With_Elements_Prohibited (Vector)){, > Type_Invariant => Length (Vector) <= Capacity (Vector)}; That is, adding a Type_Invariant to the containers. This would be a normative change, so it doesn't belong in an AI about examples. If we're going to do this, it will have to be in some other AI. I presume that you added this change based on Tucker's comment from the meeting: Tucker suggests that we could imagine a type invariant on the containers that Length(V) <= Capacity(V). This followed some discussion about shortcomings in the example you had. I believe he was suggesting a different example that you could use, not a change to the container's contracts. "we could imagine" doesn't sound to me to be a directive to change the actual container's definition. Note that there's nothing really wrong with adding this to the containers other than the work required (both for me and for implementers). But it is already reflected in various postconditions where it could be relevant, so it seems like it would primarily be redundant (you only found a single postcondition that it would make redundant). >Modify A.18.6(16.7/5): > procedure Assign (Target : in out Map; Source : in Map) > with Pre => (if Tampering_With_Cursors_Prohibited (Target) > then raise Program_Error), > Post => Length (Source) = Length (Target) [and then > Capacity (Target) >= Length (Source)]; > >[Author's note: This is a bug in the RM. Ordered maps do not have a Capacity >function!] I've fixed this, an obvious cut-and-paste error. No need to mention it anywhere (few of the container contract things are written out anywhere other than the RM and we can fix mistakes accordingly, at least until we publish a new Standard). I note that I concluded when contracting containers that it would have been a lot easier had all of the containers had a Capacity of some sort. (I also concluded that we needed a different cross-cut than the way the Sets/Maps are organized -- probably, there should have been a set of operations that exist in all six of the "normal" containers defined separately [there are about 30 or so], and then all of the other operations defined individually. That would save a lot of text. Too late to do any of that, of course, it would mainly be churn now and it would change all of the clause numbers.) >Modify A.18.20(5/3): > type List (Capacity : Count_Type) is tagged private{, > with Type_Invariant => Length (List) <= List.Capacity}; This doesn't work as you wrote it, as the lead-in text says that this is only describing the discriminant. Moreover, all of these are wrong anyway as they seem to imply that this is the complete declaration, when this is only the first line. That is, the existing example text (for all of the bounded forms) should end with "..." and not ";", thus: type List (Capacity : Count_Type) is tagged private... as they already have 6 or more aspects specified after them. There's no intent that this declaration change any of those aspects unless there is text elsewhere to do that. (Note that the Bounded_Vectors have text to omit the capacity from the stable properties.) >[Author's question: I presume that we only need to describe type invariants >for bounded containers where the unbounded forms do not already have such a >type invariant? Surely (if we do this at all). >Also, presuming that in unbounded forms it's OK to use >non- object prefix form (for consistency, and not need to redefine to >use prefix form for bounded containers even though that is required >because Capacity is a discriminant, not a primitive operation? ... ??? Bounded_Vectors, Bounded_Hashed_Maps, and Bounded_Hashed_Sets all still have a function Capacity. So it's certainly OK to call it! Map.Capacity is the discriminant, Capacity (Map) is the function. (There's a preference for components, else there would have been a nasty incompatibility and the possibility of "losing" access to components because of an inherited primitive.) **************************************************************** From: Brad Moore Sent: Tuesday, October 1, 2019 8:29 AM > ... >> This example shows that it can be possible to simplify the expression >> to a form that doesn't need the combiner, by using the map/reduce >> part of the value sequence to make both parameters of the reduction >> the same type. > > Didn't we consider that when we designed this feature? Didn't we > decide that the performance impact of eliminating the combiner would be unacceptable? > After all, the only reason for parallelizing something is to improve > the performance, and if doing that requires making a lot of expensive > copies then that doesn't make much sense. I'm certain that we > discussed this at least once and probably more than once. I'd suggest > going back through the mail/minutes on this topic (AI12-0242-1 & > AI12-0262-1) to see what we discussed about it in the past (rather > than reopening a discussion that we've had in the past). The example I was simplifying was originally intended to be an example showing the use of a combiner. Once I found that that example is better written without a combiner, I was struggling to find an example that needed a combiner. Most reducing problems dont. However, I think I have found an example that would likely also be good to include as an example in the RM. That is, Finding the set of prime numbers within some large range of integers. I am thinking of using a Set container for this problem. I will try to write up an example for this, and probably send out another version of the AI, with the additional example, as well as other correction suggested I've received so far. **************************************************************** From: Brad Moore Sent: Tuesday, October 1, 2019 4:34 PM Here is a minor update to my AI. [This is version /06 - Editor.] I applied J.P's suggestion to raise Program_Error with "Impossible", if a Character is found in the String parameter that does not satisfy the precondition. I fixed up the paragraph numbers to match the current draft RM I removed adding type invariants to all the containers, because Randy suggests if we want to do that, that should be a different AI. I added a reduction example that uses a combiner function. (The prime number example) **************************************************************** From: Ed Schonberg Sent: Thursday, October 3, 2019 5:15 PM The new examples are expressive, but the primes constructor using Reduce is incomplete (Is_Prime is incomplete) and both hard to read and much more inefficient than known algorithms. Of course, the good ones are sieves that are intrinsically sequential. I would prefer the following, as an example of iterator filters: primes := (for I in 2 .. N when (for all J in 2 ..I => J * J <= I and then I mod J /= 0) => I)); **************************************************************** From: Ed Schonberg Sent: Friday, October 4, 2019 10:15 AM Steve pointed out that the quantified expression as written is bound to fail, and in fact the upper bound should also be handled by means of a filter, so the proper code is: primes := (for I in 2 .. N when (for all J in 2 ..I when J * J <= I => I mod J /= 0) => I); **************************************************************** From: Brad Moore Sent: Friday, October 4, 2019 10:52 AM I found the same problem with the logic when I tried to implement this in Ada 2012. I agree that is a nice tidy way to express the primes, but I disagree that it is more efficient. In fact, I have implemented (in Ada 2012 this logic), as well as using an Is_Prime function, and the execution times are pretty much equivalent. But when I apply parallelism, as in my example, using the Paraffin libraries, I see that I get about 4 times faster results on my quadcore computer. The source code follows, as well as the output showing the execution times: with Ada.Command_Line; with Ada.Text_IO; use Ada.Text_IO; with Ada.Containers.Ordered_Sets; use Ada; with Ada.Real_Time; with Ada.Float_Text_IO; use Ada.Float_Text_IO; with Paraffin.Loops.Generic_Work_Seeking_Functional_Reduction; procedure Find_All_Primes is Start : constant Integer := (if Command_Line.Argument_Count >= 1 then Integer'Value (Command_Line.Argument (1)) else 2); N : constant Integer := (if Command_Line.Argument_Count >= 2 then Integer'Value (Command_Line.Argument (2)) else 1_000_000); package Integer_Sets is new Ada.Containers.Ordered_Sets (Element_Type => Integer); Start_Time : Real_Time.Time; Elapsed : Duration; use type Real_Time.Time; begin -- Should be Equivalent to Ed's example declare -- Primes : Integer_Sets.Set := -- (for I in Start .. N -- when (for all J in 2 ..I => -- J * J <= I and then I mod J /= 0) => I)); Primes : Integer_Sets.Set; begin Start_Time := Real_Time.Clock; for I in Start .. N loop Inner_Loop : for J in 2 .. I loop if I mod J = 0 then goto Next_I; end if; exit Inner_Loop when J * J > I; end loop Inner_Loop; Primes.Include (I); <> end loop; Elapsed := Real_Time.To_Duration (Real_Time.Clock - Start_Time); Put_Line ("Eds approach: " & Containers.Count_Type'Image (Primes.Length) & " primes found"); Put ("Elapsed = "); Float_Text_IO.Put (Item => Float (Elapsed), Fore => 2, Aft => 2, Exp => 0); New_Line; end; -- Should be equivalent to my example, but using sequential processing declare function Is_Prime (Number : Integer) return Boolean is begin for J in 2 .. Number loop if Number mod J = 0 then return False; end if; exit when J * J > Number; end loop; return True; end Is_Prime; Primes : Integer_Sets.Set; begin Start_Time := Real_Time.Clock; for I in 1 .. N loop if Is_Prime (I) then Primes.Include (I); end if; end loop; Elapsed := Real_Time.To_Duration (Real_Time.Clock - Start_Time); Put_Line ("Brad's approach sequentially: " & Containers.Count_Type'Image (Primes.Length) & " primes found"); Put ("Elapsed = "); Float_Text_IO.Put (Item => Float (Elapsed), Fore => 2, Aft => 2, Exp => 0); New_Line; end; -- Should be equivalent to my example, but using parallel processing declare subtype Iteration is Integer range Start .. N; package Integer_Loops is new Paraffin.Loops.Generic_Work_Seeking_Functional_Reduction (Loop_Index => Iteration, Result_Type => Integer_Sets.Set, Reducer => Integer_Sets.Union, Identity => Integer_Sets.Empty_Set); function Is_Prime (Number : Integer) return Boolean is begin for J in 2 .. Number loop if Number mod J = 0 then return False; end if; exit when J * J > Number; end loop; return True; end Is_Prime; procedure Process (Start, Finish : Iteration; Result : in out Integer_Sets.Set) is begin for I in Start .. Finish loop if Is_Prime (I) then Result.Include (I); end if; end loop; end Process; Start_Time : Real_Time.Time := Real_Time.Clock; Primes : Integer_Sets.Set := Integer_Loops.Parallel_Loop (Process => Process'Access); begin Elapsed := Real_Time.To_Duration (Real_Time.Clock - Start_Time); Put_Line ("Brad's approach in parallel: " & Containers.Count_Type'Image (Primes.Length) & " primes found"); Put ("Elapsed = "); Float_Text_IO.Put (Item => Float (Elapsed), Fore => 2, Aft => 2, Exp => 0); New_Line; end; -- All_Primes := [for I in 1 .. 2_000_000 => I]'Reduce(Integer_Sets.Include, [], Integer_Sets.Union); end Find_All_Primes; Output: Eds approach: 348513 primes found Elapsed = 13.01 Brad's approach sequentially: 348513 primes found Elapsed = 12.88 Brad's approach in parallel: 348513 primes found Elapsed = 4.49 **************************************************************** From: Brad Moore Sent: Friday, October 4, 2019 11:03 AM Also, I should mention, the inputs to the program that provided the results below were: ./find_all_primes 1 5_000_000 And the Is_Prime function if we wanted to include it in full in the RM could be written as; function Is_Prime (Number : Integer) return Boolean is (for all J in 2 ..Number when J * J <= Number => Number mod J /= 0); Which still provides the same filter example, and perhaps is a bit more readable due to being a simpler expression. **************************************************************** From: Tucker Taft Sent: Friday, October 4, 2019 11:16 AM This does seem simpler and easier to understand. **************************************************************** From: Tucker Taft Sent: Friday, October 4, 2019 11:27 AM By the way, whenever I have seen something written like this, the high bound has always been N/2 rather than N since we know Floor(Sqrt(N)) <= Floor(N/2), presuming N > 1. So perhaps: function Is_Prime (Number : Integer) return Boolean is (Number >= 2 and then (for all J in 2 .. Number/2 when J * J <= Number => Number mod J /= 0)); With your original Is_Prime(1) = True ;-) **************************************************************** From: Ed Schonberg Sent: Friday, October 4, 2019 2:56 PM > And the Is_Prime function if we wanted to include it in full in the RM > could be written as; > > function Is_Prime (Number : Integer) return Boolean is > (for all J in 2 ..Number when J * J <= Number => Number mod J > /= 0); > > Which still provides the same filter example, and perhaps is a bit > more readable due to being a simpler expression. the parallel code is a nice example. but in terms of performance this is still quadratic in the number of divisions. The standard sieve is not easily parallelizable but it's linear and only uses additions. by the way, can the quantified expression be marked parallel? **************************************************************** From: Tucker Taft Sent: Friday, October 4, 2019 7:39 PM We have not proposed allowing "parallel" in a quantified expression, though a quantified expression is essentially a special case of a reduction (universal is a reduction over "and" with identity "True" while existential is a reduction over "or" with identity "False"), so it would certainly make sense to allow "parallel." I have argued in the past that these constructs are simple enough and self-contained enough that the compiler could probably decide on its own whether a parallel evaluation would be safe and would produce any savings. But since that argument didn't "fly" for explicit reduction expressions, it seems like it probably wouldn't fly for these "implicit" reductions. **************************************************************** From: Brad Moore Sent: Sunday, October 6, 2019 1:32 PM Here is an update to the RM Examples, which hopefully addresses all comments from yesterday. [This is version /07 of the AI - Editor.] I removed the example with of a reduction expression with a combiner function, as we said we wanted to create a new AI to include such an example, once we come up with one. I changed the Roman numeral conversion example to make use of the existing Roman_Digit type. I also defined an enumeration rep clause for that, and use the 'Enum_Rep attribute to do the conversion. Also the Work_Order example we reworked so that it could actually compile. **************************************************************** From: Randy Brukardt Sent: Monday, November 11, 2019 7:07 PM Brad has in AI12-0312-1: Modify 3.10.1(23): My_Car, Your_Car, Next_Car : Car_Name := new Car; -- see 4.8 [George]{Casey} : Person_Name := new Person(M); ... [George]{Casey}.Vehicle := Your_Car; However, the George object is used in the interface example in 3.9.4 (yes, a forward reference). We need to change the name in that as well: Modify 3.9.4(32/2): ... -- Add [George]{Casey} (see 3.10.1) to the cashier's queue: Append (Cashier, [George]{Casey}); -- After payment, move [George]{Casey} to the sandwich counter queue: Transfer (Cashier, Counter); It's also used in an example in the Heart of Darkness: Modify 3.10.2(41): Martha : Person_Name := new Person(F); -- see 3.10.1 Cars : array (1..2) of aliased Car; ... Martha.Vehicle := Cars(1)'Access; [George]{Casey}.Vehicle := Cars(2)'Access; I found this by looking for George in the Chapter 3 source file for the RM in order to change it, and I landed on some text that didn't match what I was expecting. (I did look through the entire RM source for any other uses of George and did not find any.) Consider this part of my editorial review for this AI. **************************************************************** From: Randy Brukardt Sent: Monday, November 11, 2019 7:27 PM The example for 4.2.1 has: subtype Roman_Number is Positive range 1 .. Max_Roman_Number with String_Literal => To_Roman_Number; But String_Literal is a *type*-related operational attribute. It's not allowed on non-first subtypes by 13.1(8.1/3). Thus, Roman_Number has to be a type declaration: type Roman_Number is range 1 .. Max_Roman_Number with String_Literal => To_Roman_Number; ...which is better anyway as it gets rid of the dependence on Positive. (Note that we certainly don't want type Integer allowing roman numerals as literals. :-) P.S. There is an "i.e." in this example as well, replaced with "that is". **************************************************************** From: Randy Brukardt Sent: Monday, November 11, 2019 9:48 PM A reminder more than a bug: the example in 6.1.1 refers one to the memory buffers declared in 13.13.1. If we decide to remove AI12-0293-1 (as suggested by Bob and others), then we have to change this reference to something else. Either the new string buffers (A.4.12) or any of the containers (A.18.2, etc.) would work. (We're looking for a language-defined package that contains a number of Pre and Post aspects.) **************************************************************** A private comment noted that there was a missing semicolon after the R object declaration in To_Roman_Number. This has been fixed as an editorial review change. **************************************************************** From: Arnaud Charlet Sent: Monday, September 14, 2019 6:01 AM [From his RM review; treated as an editorial review change - Editor.] B.3 (79.1/5) ------------- procedure Printf (Format : in C.char_array) This doesn't match the call in 83.1/5: Printf("The String=%s, Length=%d", Chars1, Chars1'Length); Should be instead: procedure Printf (Format : in C.char_array; Param1 : in C.char_array; Param2 : in C.int) **************************************************************** From the WG 9 informal review #104 4.5.7 (22/5) comment is split over end of line [treated as an editorial review change - Editor.] ****************************************************************