Version 1.8 of ai12s/ai12-0312-1.txt

Unformatted version of ai12s/ai12-0312-1.txt version 1.8
Other versions for file ai12s/ai12-0312-1.txt

!standard 5.5.2(2/3)          19-03-24 AI12-0312-1/04
!class presentation 19-02-07
!status work item 19-02-07
!status received 19-02-07
!priority Low
!difficulty Easy
!subject Examples for Ada 2020
!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
Append after 4.2.1(10/5):
Examples
-- The Date Example in 3.8 could be augmented to support integer literal representation
type Month_Name is (Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
type Date is record Day : Integer range 1 .. 31; Month : Month_Name; Year : Integer range 0 .. 4000; end record with Integer_Literal => To_Date;
function To_Date (Value : String) return Date;
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)));
...
Today : Date := 2_22_2019; -- Integer representation of date (MMDDYYYY format)
Append after 4.3.3(47/5):
Empty_Matrix : constant Matrix := []; -- A matrix without elements
Delete paragraphs 4.3.5(86/5 - 86/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 ("George is " & (if George.Sex = M then "Male" else "Female")); -- See 3.10.1
function Value (Digit : Roman_Digit) return Natural is -- See 3.5.2
(case Digit is when 'I' => 1,
when 'V' => 5, when 'X' => 10, when 'L' => 50, when 'C' => 100, when 'D' => 500, when 'M' => 1000);
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 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, Addend_Count => 0), Combine); begin Acc.Sum / Real(Acc.Addend_Count));
Modify 5.5(22/5): "Example{s} of [a] 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;
Append after 6.1.1(41/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(42/5):
NOTES
For an example of the use of these aspects, see the Vector container definition in A.18.2.
Append after 7.3.2(24/3):
Examples
package Work_Orders is
type Work_Order is private with Type_Invariant => Day_Scheduled (Work_Order) in Weekday or else Priority (Work_Order) = Urgent; -- See 3.5.1
function Day_Scheduled (Order : in Work_Order) return Day; -- See 3.5.1 function Priority (Order : in Work_Order) return Level; -- See 3.5.1
...
private
type Work_Order is record Scheduled : Day; Urgency : Level; ... end record;
function Day_Scheduled (Order : in Work_Order) return Day is (Order.Scheduled); function Priority (Order : in Work_Order) return Level is (Order.Urgency);
...
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 examples of raise expressions, see the Streams Subsytem definitions in 13.13.1.
Append after B.3(78/3):
procedure Printf (Format : in C.char_array) 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.
!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.

****************************************************************

Questions? Ask the ACAA Technical Agent