AI22-0153-1

!standard 5.4(4/3)                                  26-07-07  AI22-0153-1/05

!class Amendment 26-02-05

!status work item 26-02-05

!status work item 26-02-05

!status received 26-01-12

!assigned author Tucker Taft

!submitter Tucker Taft

!priority Medium

!difficulty Hard

!subject Generalized Case Statement

!summary

We generalize the case statement to permit selecting among alternatives based on the values of (sub)components of a composite type, or nullness of an access value, or more generally the value of objects of any type, analogous to what is permitted by membership tests.

[This is (loosely) based on a combination of AI12-0214-1 and AI12-0274-1.]

!issue

Ada has case statements and case expressions that allow testing for the value of a selecting_expression. They also enforce full coverage checking. On the other hand, case constructs only work on discrete types, which limits their usefulness. However, it is not uncommon to have a composite type where the values of some or all of the components need to be checked to determine the actions to be taken, and a case statement could provide a nice visible structure to such an algorithm, to replace a series of if/then/else statements. Note that membership tests now permit multiple choices connected with '|' for any type, which also suggests that a more general case statement might be worth considering.

!recommendation

We propose to allow the selecting expression of a case statement (or case expression) to be of any type, as well as be a parenthesized list of expressions (treated effectively as an aggregate[a][b][c][d] of a newly declared type). What is currently the discrete_choice_list for each case_alternative would be replaced by a "case_choice_list", where each case_choice has an extended syntax that includes all of the current options available in a membership_choice (which includes string literals and aggregates), but also allows the introduction of new identifiers for one or more parts of the value that were "matched" by the case_choice, using the notation analogous to that currently allowed in exception handlers, of Identifier : case_choice.

In addition, for any type, a wildcard of others can be used at the top-level (as currently), while <> is usable for nested occurrences, such as in  when (A => 3, B => <>) => ... . As indicated above, an identifier can be introduced for all or part of the value, such as:

   when D : (A => 3, B => <>) => ...

or

   when (Height => 3, Width => Width:<>) => ...

There is a shorthand for Id : <> of simply <Id>, so the above becomes:[e][f][g][h]

   when (Height => 3, Width => <Width>) => ...

For such an example, this is effectively a local rename of the matched component, with the subtype coming from the component's nominal subtype.

For composite types, the choices would generally be subtype_marks or aggregates, and the component values within the aggregate can themselves be given by (nested) case_choices (including <> as above).

For access types, the choices (in addition to <>) are either null, not null, an access subtype_mark (optionally preceded by not null), or a case choice of the designated type, implying the value is first checked for being not null and then dereferenced to compare against the case choice.

For private types, the case_choice can be a subtype_mark, <>, or a pseudo-aggregate consisting of the discriminants, if any, and then some number of values using named notation, given by primitive functions of the type with the given name, that take a single argument and return a non-limited type, such as:

   when (Value => 3, Left => L : not null, Right => null) => …

presuming there is a primitive one-argument function Value returning an integer, and primitive one-argument functions Left and Right returning access values. Note that in the associated case_alternative, the result returned by Left is available via the identifier L.

For variant records, the usual rules for discriminant-dependent components apply, so for a type such as Expr_Node below, we can avoid cases where we refer to the wrong variant by mistake. Let us assume  these definitions of Expr_Kind, Expr_Node, and Expr:

   type Expr_Kind is (Leaf, Unary, Binary);
   type Expr_Node;
   type Expr is not null access Expr_Node;
   type Expr_Node (Kind : Expr_Kind) is record
      case Option is
         when Leaf => Val : Integer;
         when Unary =>
            Unop : Unary_Operator;
            Operand : Expr;
         when Binary =>
            BinOp : Binary_Operator;
            Left, Right : Expr;
      end case;
   end record;

 

Here is an example where we use a regular case statement testing the discriminant, and end up referencing a component from the wrong variant:

   procedure Print_Expr (E : Expr) is
   begin
      case E.Kind is
         when Unary =>
            Put (E.UnOp'Image & ' ');
            Print_Expr (E.Right);
               --  Error here, not statically checked
               --  (should have been E.Operand).
         when ...
         ...
      end if;
   end Print_Expr;

 

The incorrect reference above to E.Right (instead of the correct E.Operand) is not guaranteed to be caught at compile time. However, if we use the proposed generalized case statement, we can do the following:

   procedure Print_Expr (E : Expr) is
   begin
      case E.all is
         when (Unary, Unop => <Op>, Operand => <Opnd>) =>
             --  Component names statically checked as in an aggregate
            Put (Op'Image & ' ');
            Print_Expr (Opnd);
         when ...
         ...
      end if;
   end Print_Expr;

 

A nice benefit of the above is that with this construct, it is impossible to incorrectly access E.Right when the Kind is not Binary[i][j], because of the existing language checks for aggregates with discriminant-dependent components.

Here are more examples of using a composite type in a case statement:

   type R is record
      A, B : Boolean;
   end record;

   R_Inst : R;

   case R_Inst is
      when (True, True) => ..
      when (True, False) => ..
      when (False, False) => ..
      when (False, True) => .. -- Every possibility has been covered.
   end case;

 

This example also suggests how an aggregate of an anonymous type might be used:

   case (X >= 0, Y >= 0) is
      when (True, True) => ..
      when (True, False) => ..
      when (False, False) => ..
      when (False, True) => .. -- Every possibility has been covered.
   end case;

 

Here we are using aggregate notation in a way that would normally be illegal, since there is no expected type.  As a case expression in a generalized case construct, such an aggregate is interpreted as being a positional record aggregate of a type whose components are presumed to be of the type determined by each expression, each of which is treated as a complete context (similar to the operand of a type conversion – expected to be of any type).

As mentioned above, we allow the use of <> to represent all remaining values of a component (in this way, it acts like others for component values), and others

can also be used in the usual way as the component name in an aggregate to indicate all remaining components, as in regular aggregates, and with the same limitations. These two distinct kinds of "wildcards" become particularly useful when the selecting expression is of an array type:

    type Arr is array (Natural range <>) of Integer;

   A :
Arr := ...;

   
case A is[k][l][m][n][o][p]
       
-- Match when first element is one and others are not
       
when [1, others => Integer'First .. 0 | 2..Integer'Last] => ...

       
-- Match when every element after the first is one
       
when [<>, others => 1][q][r][s][t][u][v] => ...
       -- Match all other possibilities

        when others => ...

    end case;

 

We don't restrict the use of <> wildcards in the same way they are restricted in normal aggregates, where they generally are only allowed in conjunction with named notation.  We treat them as equivalent to, for example, a full range of the possible values for a scalar type, and we wouldn't want to make any restrictions on where such ranges could appear.  Furthermore, when there are a series of case choices, it is helpful to have the <> choices line up with the non-wildcard choices, as in some of the examples below.

Note that we allow matching using null/not null (or null/<>) for access types. This allows

a case branch where it is known that an access value can be safely dereferenced:

    type Some_Access is access ...;
    type A_Record is record
        Cnt : Natural;
        Data : Some_Access;
    end record;
    Obj : A_Record;

    case Obj is
       when (1, not null) => Obj.Data.all ... -- OK.
       when (<>, not null) => Obj.Data.all ... -- OK.
       when (<>, <>) => -- not safe to dereference here
    end case;

 

We can use string literals in place of an aggregate:

      case S is
          when "begin" => ...;
          when "end" => ...;
          when others => ...;
      end case;

Full coverage, non-ambiguity, and non-redundancy requirements:

There are three distinct goals for case statements in general, and these are generalized a bit for our proposed generalized case statements:

  1. All possible values are covered
  1. The construct should have an explicit "when others" if the other alternatives are not known to cover all possible values of the selecting expression
  1. No ambiguity between alternatives
  1. We allow overlap between alternatives, but only if the latter one is a proper superset of any earlier alternatives.
  2. This is a generalization of the rule allowing a final "when others" alternative, which allows a bit more flexibility, so that something like a "<>" can be used as a wildcard for a given component that necessarily might overlap with an earlier alternative which used a more specific value for that same component.
  3. We do not allow partial overlaps, where the order of the alternatives would make a difference in which one was chosen; for full overlaps we require the superset to follow the more restrictive alternative(s).
  1. No redundancy between alternatives
  1. We do not allow an alternative which only covers values that are already covered by earlier alternatives, unless it is explicitly a "when others", and even then, a warning about dead code might be expected from certain static analysis tools.

Example of the above three rules in action for a case over a two-element record (or array) of integers:

  case Obj is
     when (1, <>) => -- OK.
     when (2,  2) => -- OK.
     when (<>, 1) => -- not OK due to partial overlap with 1st
     when (1,  2) => -- not OK due to being subset of 1st (must precede it)
     when (2, <>) => -- OK since this is a superset of 2nd alternative
     when (1..2, <>) => -- not OK, since redundant with 1st+5th
  end case; -- not OK, since only (1..2, <>) has been covered

!wording

Replace 3.8.1(5/3) with:

discrete_choice ::= discrete_simple_choice | others

simple_choice ::=

   choice_expression | subtype_indication | range

Replace 4.5.7(6/3) with:

case_expression_alternative ::=

    when case_choice_list =>

        dependent_expression

Replace 5.4(3) with:

case_statement_alternative ::=

   when case_choice_list =>

      sequence_of_statements

Add after 5.4(3):

case_choice_list ::=

  [choice_parameter_specification:] case_choice {'|' case_choice}

case_choice ::=

  choice_aggregate | simple_choice | not null | others

choice_aggregate ::=

    property_choice_aggregate | container_choice_aggregate

property_choice_aggregate ::=

  '(' property_choice {, property_choice} ')'

property_choice ::= [property_list =>] choice_element_list

property_list ::= property_selector{, property_selector}

property_selector ::= identifier

container_choice_aggregate ::= '[' container_choice{, container_choice} ']'

container_choice ::= [key_choice_list =>] choice_element_list

choice_element_list ::=

    [choice_parameter_specification:] choice_element {'|' choice_element}

  | < choice_parameter_specification >

choice_element ::= choice_aggregate | membership_choice | not null | <>

Replace 5.4(4/3 and 5/3):

The selecting_expression is expected to be of any discrete type. The expected type for each discrete_choice is the type of the selecting_expression.

Legality Rules

The choice_expressions, subtype_indications, and ranges given as discrete_choices of a case_statement shall be static. [Redundant: A discrete_choice others, if present, shall appear alone and in the last discrete_choice_list.]

with:

The selecting_expression is expected to be of any type. If the selecting_expression is an aggregate, it shall be in the form of a record_aggregate with only positional component associations, where each association has an expression. The expected type for each expression is any type.

If the selecting_expression is not in the form of an aggregate, then the associated subtype for each case_choice is the subtype of the selecting_expression. If the selecting_expression is in the form of an aggregate, then each non-others case_choice shall be in the form of a property_choice_aggregate, where each property_choice is a positional element.  The associated subtype for each positional element is the subtype of the corresponding element of the selecting_expression.

The associated subtype determines what form a case_choice or choice_element may take.  A case_choice of others is allowed for any associated subtype[; Redundant: if present, it shall appear alone and in the last case_choice_list]. Similarly, a choice_element of <> (or a choice_element_list of <choice_parameter_specification>) is allowed for any associated subtype; such an element is called a wildcard element, as is an others choice.  A non-wildcard case_choice or choice_element (called simply a specific choice below) is more restricted, as follows:

In a property_choice_aggregate, the associated subtype of a choice_element is determined by the subtype of the component or the result subtype of the property function, identified by the property_selector.

Legality Rules

A wildcard choice_element of <> shall appear alone in its choice_element_list.

If the expected type of a specific choice is a scalar or string type, then any simple_choice or membership_choice shall be static, or in the case of a string type, be a subtype that is statically constrained. If the expected type is a composite type other than a string type, any simple_choice or membership_choice shall be a subtype that is statically constrained.

Replace 5.4(6/3-10):

The possible values of the selecting_expression shall be covered (see 3.8.1) as follows:

Two distinct discrete_choices of a case_statement shall not cover the same value.

with:

A specific choice is defined to cover a value in the following cases:

A wildcard choice covers all values of the expected type not otherwise covered. A case_choice_list or a choice_element_list covers a value if one of its choices covers the value.

The possible values of the selecting_expression shall be covered as follows:

Two distinct case_choices of a case_statement shall not cover the same value, unless the second case_choice appears in a separate, later case_choice_list and covers a superset of the values covered by the earlier case_choice, and includes at least one wildcard choice or one not null choice not present in the earlier case_choice.

** Remainder is TBD.

!discussion

We adopt the aggregate-like notation for matching against composite values. This seems the most natural for Ada, and permits both positional and named notation for individual components.  We allow <> to be used in positional notation, to allow lining them up with non-wildcard values when there are multiple similar choice aggregates.  We also permit subtypes and <> wildcards for matching a composite value.  Note that unlike in a regular aggregate, there is no danger of <> unintentionally introducing an uninitialized component, since these are matching constructs, not initialization constructions.

For access values, we allow null and not null in addition to <> and subtypes. We also allow an implicit dereference, which implies not null, by specifying a value of the designated type.

For private types we propose a kind of pseudo-aggregate, with the discriminants, if any, followed by some number of tests on "properties" of the object of the private type, where a "property" is defined to be something with a single-parameter primitive function returning a value of a non-limited type. Clearly this feature is optional, and could be omitted from the initial proposal for simplicity, allowing matching only via discriminants, subtypes, or wildcards.

There is actually no particular need for the type of the selecting expression (or its subcomponents) to have a primitive equality operator, presuming the matching would be happening at the discriminant/component level for limited types, or they would be matched against <>.

Coverage, ambiguity, and redundancy checks

We require full coverage, which of course is easy for the programmer to accomplish by adding a "when others" alternative. However, one of the major advantages of the current "case statement" is that you can omit the "when others" and the compiler will inform you if you missed something. We definitely want this advantage to carry over to the generalized case statement. It turns out there is a straightforward algorithm for enforcing this, even for arbitrarily complex choices.

The algorithm can be derived as follows. Generally the compiler will need to construct a kind of "automaton" to decide which alternative to choose, where the automaton is simply a tree (or directed graph) of individual tests for each (sub)component having any sort of restriction (i.e. not simply <>), with the graph of tests leading to one of the various case statement sequences. The most straightforward way to do this is to construct a nondeterministic finite automaton (NFA) based on the tests to be performed, and then merge the various tests to produce a deterministic finite automaton (DFA). The resulting DFA will indicate in a straightforward way whether there is ambiguity or redundancy, because it will lead to a node with multiple associated sequences of statements (ambiguity), or have no path to one or more of the sequences of statements (redundancy). Lack of coverage is detected by adding a "when others" case if one doesn't exist, and then verifying that after constructing the DFA, there is no path that reaches the synthetic "when others" alternative. This algorithm is laid out and illustrated in the paper on "Rigorous Pattern Matching as a Language Feature[w][x][y]" (https://link.springer.com/article/10.1007/s10009-025-00788-z).

It turns out this algorithm can handle a wide range of matching requirements[z][aa][ab][ac], but it also means that we can choose what features we want to support based on usefulness for the user, and avoid or include individual capabilities as we decide what makes the most sense for the language, while still having confidence we can enforce full coverage, non-ambiguity, and non-redundancy.

!examples

Here is an example of using property choice aggregates for the private type Ada.Calendar.Time, which has several single-parameter "property" functions, such as Year, Month, Day, etc.  The set of choices is exhaustive, so there is no need for an "others" choice.

use Ada.Calendar;
function Generation_Name (Birth_Date : Time) return String is
begin
  case Birth_Date is
    when (Year => Year_Number'First .. 1945) => return "Pre_Boomer";
    when (Year => 1946 .. 1964) => return "Boomer";
    when (Year => 1965 .. 1980) => return "Gen_X";
    when (Year => 1981 .. 1996) => return "Millenials";
    when (Year => 1997 .. 2012) => return "Gen_Z";
    when (Year => 2013 .. 2019) | (Year => 2020, Month => 1 .. 2) =>
     return "Gen_Alpha_Pre_Covid";
    when (Year => 2020, Month => 3 .. 12) | (Year => 2021, Month => 1 .. 4) =>
     return "Covid_Baby";
    when (Year => 2021, Month => 5 .. 12) | (Year => 2022 .. 2025) =>
     return "Gen_Alpha_Post_Covid";
    when (Year => 2026 .. Year_Number'Last) => return "Gen_Beta_And_Beyond";
  end case;
end Generation_Name;

!ACATS test

New ACATS tests would be needed to check that the various new capabilities are supported.

!appendix

[NOTE: This is the !appendix from AI12-0214-1, not all of which pertains to the current proposal.]

From: Raphael Amiard

Sent: Sunday, October 9, 2016  7:41 AM

Here is an AI for a feature proposal I've been drafting with some help. Of

course too late to discuss at this meeting, but it'll let a lot of time for

people to look at it until the next one though !

[This is version /01 of the AI - Editor.]

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

From: Tucker Taft

Sent: Thursday, October 13, 2016  10:14 AM

Did you consider the syntax:

    when (True, <A>) =>

where <id> is declaring id to represent what "<>" would have represented on

its own?

I think we also talked about:

   when R : (True, <>) =>

where you now use R.blah to refer to parts matched by <>

I find the "declare ... when ..." syntax too verbose, and think the

"when R : ( ... ) =>"  syntax the most consistent with how exception

occurrences are declared now.

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

From: Raphael Amiard

Sent: Thursday, October 13, 2016  10:29 AM

> Did you consider the syntax:

>

>    when (True, <A>) =>

No, we didn't think about that. On the one hand, I like it because it's very

concise, coherent with the unnamed case, and quite clear about what this does !

On the other hand I'm worried that it will make the lexer's work a bit harder,

since here "<A>" can be parsed either as "Op(LT), Id(A), Op(GT)" or as

"Pattern_Match_Id(A)".

I'll try and implement this in libadalang's parser, to see what the

repercussions are.

>

> where <id> is declaring id to represent what "<>" would have

> represented on its own?

>

> I think we also talked about:

>

>   when R : (True, <>) =>

>

> where you now use R.blah to refer to parts matched by <>

>

> I find the "declare ... when ..." syntax too verbose, and think the "when R :

> ( ... ) =>" syntax the most consistent with how exception occurrences

> are declared now.

Yes we did. I think that being able to name the top level object is a great

capability, so I'll add it to the AI. I don't however, as explained in the

previous exchanges, think that it is a substitute for sub-component matching.

If you want I can submit my rationale on the ARG thread.

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

From: Tucker Taft

Sent: Thursday, October 13, 2016  10:40 AM

...

> On the other hand I'm worried that it will make the lexer's work a bit

> harder, since here "<A>" can be parsed either as "Op(LT), Id(A), Op(GT)"

> or as "Pattern_Match_Id(A)".

This one is pretty easy, because you can actually lex it as LT, Id, GT.  But

you just have to distinguish in the parser between unary "<" and binary ">"

which is pretty easy.  We make that sort of distinction all the time.

> I'll try and implement this in libadalang's parser, to see what the repercussions are.

I would be surprised if it is difficult to handle in the parser.  I don't see

any need to alter the lexer for this.

...

>> I find the "declare ... when ..." syntax too verbose, and think the

>> "when R : ( ... ) =>" syntax the most consistent with how exception

>> occurrences are declared now.

>

> Yes we did. I think that being able to name the top level object is a

> great capability, so I'll add it to the AI. I don't however, as

> explained in the previous exchanges, think that it is a substitute for

> sub-component matching. If you want I can submit my rationale on the ARG thread.

Yes, please do, as I don't remember why you think it is not an adequate substitute.

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

From: Raphael Amiard

Sent: Thursday, October 13, 2016  10:41 AM

...

>This one is pretty easy, because you can actually lex it as LT, Id, GT.  But

>you just have to distinguish in the parser between unary "<" and binary ">"

>which is pretty easy.  We make that sort of distinction all the time.

Yes, you're probably right !

>Yes, please do, as I don't remember why you think it is not an adequate

>substitute.

Here it is, slightly edited to use the new syntax you proposed - I already

love it :)

Naming the object that is being matched upon, while it can be useful, is not

sufficient. First it's not as expressive. You'll have to repeat the path to the

sub (sub-sub) component you wanted to match, which is verbose and possibly

error prone. And then, if you want to make a rule out of the fact that you can

statically check that the path is correct, the implementation will be more

complex, because you'll have to remember the paths and check that what the user

is doing is going along those paths. Taking the realistically complex example

of the connection I showed earlier:

type Connection_State is (Init, Connecting, Connected, Disconnected);

type Ping (Has_Ping_Info : Boolean := False) is record

   case Has_Ping_Info is

   when True =>

      Last_Ping_Time    : Time_T;

      Last_Ping_Id      : Ping_Id;

   end case;

end record;  

type Connection_Info (State : Connection_State) is record

   Server : Internet_Address;

   case State is

   when Connected =>

      Session_Id        : Unbounded_String;

      Ping_Info         : Ping;

   when Connecting =>

      When_Initiated    : Time_T;

   when Disconnected =>

      When_Disconnected : Time_T;

   when Init =>

      null;

   end case;

end record;

C : Connection_Info;

case C is

   when (Connected, <S_Id>, (True, <>, <Ping_Time>)) =>

      Put_Line ("Connected ! Session Id is " & S_Id & " Ping time is " & Ping_Time'Image);

   when others => null;

end case;

Constrast with only top-level object naming:

case C is

   when CC : (Connected, <>, (True, <>, <>)) =>

      Put_Line ("Connected ! Session Id is "

                & CC.Session_Id & " Ping time is "

                & C.Ping_Info.Last_Ping_Time'Image);

                --  Woops, I used the original name rather than the matched

                --  name ! The compiler will silently ignore my error.

   when others => null;

end case;

Having to repeat the path is less readable and more error prone. You go through

the trouble of expressing the pattern, just to have to repeat the logic

underneath, effectively writing the paths twice, once in aggregate syntax, the

other in prefix syntax.

Statically ensuring that the accessed information is correct will be much more

work for the compiler.

Not to mention, that would be (yet another) feature that we don't implement

like other languages.

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

From: Randy Brukardt

Sent: Monday, January 9, 2017  6:45 PM

(Replying to an old thread that I must have missed back in October:)

...        

>Constrast with only top-level object naming:

>                

>case C is

>   when CC : (Connected, <>, (True, <>, <>)) =>

>      Put_Line ("Connected ! Session Id is "

>                & CC.Session_Id & " Ping time is "

>                & C.Ping_Info.Last_Ping_Time'Image);

>                --  Woops, I used the original name rather than the matched

>                --  name ! The compiler will silently ignore my error.

What error? C and CC are views of the same object, and clearly have the same

value. If there is an error here, it is declaring CC in the first place (see

below).

>                   when others => null;

>                end case;

One would want these shorthands in cases where the name of the original object

is too complex. If, for instance, the original object was a function call with

parameters, then the shorthand makes sense:

case Get_Connection (From => Server) is

 ... -- Rest as above.

But in this case, if you mistyped the identifier, the compiler will give you

an error. So I don't see any real problem with mistakes here.

Keep in mind that every identifier (and every entity for that matter) that one

declares adds to the cognitive load of the reader. You really shouldn't do it

unless it actually helps reading the code. (Exactly where that line is

obviously is a personal choice, but it is far away from renaming a single

character identifier.)

>Having to repeat the path is less readable and more error prone. You go

>through the trouble of expressing the pattern, just to have to repeat

>the logic underneath, effectively writing the paths twice, once in

>aggregate syntax, the other in prefix syntax.

Arguably, that's a good thing.

BTW, Ada doesn't currently allow positional <> aggregate components, and I'd

suggest that we retain that rule here. (Assuming you really want to model these

patterns as aggregates.) [Especially as many style guides ban positional record

aggregates altogether.] Therefore, your example would have to be written

something like:

case Get_Connection (From => Server) is

   when CC : (State => Connected, Server => <>,

              Session_Id => <>,

              Ping_Info => (Has_Ping_Info => True, Last_Ping_Time => <>, Last_Ping_Id => <>)) =>

       Put_Line ("Connected ! Session Id is "

                 & CC.Session_Id & " Ping time is "

                 & CC.Ping_Info.Last_Ping_Time'Image);

So the names you need are already in the source. Declaring more names would

just be more noise.

[Aside: in writing the above, I see that your original example doesn't have

enough components (the Server component seems to have been left out). Which is

why many style guides require component names ... ;-)]

>Statically ensuring that the accessed information is correct will be

>much more work for the compiler.

There seems to be no need to do that. Again, this is just a different view of

an existing object, we really should not care which of those views is accessed.

                

>Not to mention, that would be (yet another) feature that we don't

>implement like other languages.

You already know what I think about that: if you want to use some other

language, do that. Don't try to mess up Ada with the exact features of other

languages; whatever we do should fit into the Ada model and not look like it

was stolen from someone else. (That's why some form of case coverage is

mandatory for this feature.)

Tucker's idea seems to fit with the existing syntax of the language, and seems

to be sufficient for the job.

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

From: Raphael Amiard

Sent: Sunday, February 12, 2017  6:45 AM

Thank you for your answer Randy, and sorry I took so long to answer ! I have

been swamped with other work at AdaCore, currently depiling my ARG work pile :)

>> case C is

>>    when CC : (Connected, <>, (True, <>, <>)) =>

>>       Put_Line ("Connected ! Session Id is "

>>                 & CC.Session_Id & " Ping time is "

>>                 & C.Ping_Info.Last_Ping_Time'Image);

>>                 --  Woops, I used the original name rather than the matched

>>                 --  name ! The compiler will silently ignore my error.

> What error? C and CC are views of the same object, and clearly have

> the same value. If there is an error here, it is declaring CC in the

> first place (see below).

We at least agree on that, in that case :) CC is useless. My example was

probably not such a good one. See below.

> One would want these shorthands in cases where the name of the

> original object is too complex. If, for instance, the original object

> was a function call with parameters, then the shorthand makes sense:

>

> case Get_Connection (From => Server) is

>   ... -- Rest as above.

>

> But in this case, if you mistyped the identifier, the compiler will

> give you an error. So I don't see any real problem with mistakes here.

This is not about mistyping, it is about accessing a component that is

statically valid, but dynamically invalid due to discriminants. Let me

amend a previous example:

C, C2 : Connection_Info

case C is

   when (Connected, <>, <>, (True, <>, <>)) =>

      Print_Ping_Time (C2.Ping_Info.Ping_Time)

Here you're accessing the wrong object altogether (C2). This is valid Ada, so

it is pretty impossible to emit a warning, even though the code is clearly

wrong. Arguably the programmer should have used more descriptive names. He

should also have not made errors. The job of the compiler is to help him, and

that's a great opportunity to do so.

With introducing a binding, both writing the code and checking it is easier:

C, C2 : Connection_Info

case C is

   when (Connected, <>, <>, (True, <>, <Ping_Time>)) =>

      Print_Ping_Time (Ping_Time)

> Keep in mind that every identifier (and every entity for that matter)

> that one declares adds to the cognitive load of the reader. You really

> shouldn't do it unless it actually helps reading the code. (Exactly

> where that line is obviously is a personal choice, but it is far away

> from renaming a single character identifier.)

Yes, I agree with that line of reasoning. The C/CC example was a straw-man, of

the alternative proposal I don't like. In the example above, I feel like the

"Ping_Time" binding that is introduced, both by it's strong locality and

because of the static guarantees associated to it, helps the user understand

the code and make sure it's correct.

>> Having to repeat the path is less readable and more error prone. You go

>> through the trouble of expressing the pattern, just to have to repeat the

>> logic underneath, effectively writing the paths twice, once in

>> aggregate syntax, the other in prefix syntax.

> Arguably, that's a good thing.

Let's argue then :) I see no benefit in repeating the path, only potential for

errors, both for the writer and for the reader of the code.

> BTW, Ada doesn't currently allow positional <> aggregate components,

> and I'd suggest that we retain that rule here. (Assuming you really

> want to model these patterns as aggregates.) [Especially as many style

> guides ban positional record aggregates altogether.] Therefore, your

> example would have to be written something like:

>

> case Get_Connection (From => Server) is

>     when CC : (State => Connected, Server => <>,

>                Session_Id => <>,

>                Ping_Info => (Has_Ping_Info => True, Last_Ping_Time => <>,

>                              Last_Ping_Id => <>)) =>

>         Put_Line ("Connected ! Session Id is "

>                   & CC.Session_Id & " Ping time is "

>                   & CC.Ping_Info.Last_Ping_Time'Image);

>

> So the names you need are already in the source. Declaring more names

> would just be more noise.

This does not make sense. The introduction of new names is used to introduce

new bindings. If you have a record "Line" with two "Points" components, who

themselves have X and Y components, if you want to match on the 4 subvalues

the components names are not going to be enough.

> [Aside: in writing the above, I see that your original example doesn't have

> enough components (the Server component seems to have been left out). Which

> is why many style guides require component names ... ;-)]

>> Statically ensuring that the accessed information is correct will be much

>> more work for the compiler.

> There seems to be no need to do that. Again, this is just a different view

> of an existing object, we really should not care which of those views is

> accessed.

The point of this feature, in my mind, is that you match the

discriminants and the components at the same time. So every new binding

you introduce is statically guaranteed to correspond to something in the

matched value.

If you combine that with:

1. A style rule (that can easily be statically checked) that it is

forbidden to access variable components of a record via the regular dot

notation, eg. you have to use matching.

2. A legality rule that it is forbidden to mutate the object that you're

matching upon (similar to the rule about renamings of discriminated

records component if I remember correctly)

Then you get a style of programming where it is possible to statically

guarantee that the user cannot illegally access a component of a

discriminated record. This is the situation in languages such as OCaml,

and it is a highly desirable one IMHO.

I think it is possible to reach this goal without introducing new

bindings, eg. you need to check the components paths used  inside case

branches, but the specification and implementation of such a feature

will be harder as far as I can tell.

>> Not to mention, that would be (yet another) feature that we don't implement

>> like other languages.

> You already know what I think about that: if you want to use some other

> language, do that. Don't try to mess up Ada with the exact features of other

> languages; whatever we do should fit into the Ada model and not look like it

> was stolen from someone else. (That's why some form of case coverage is

> mandatory for this feature.)

Yes, I agree that similarity to other languages is not a strong

argument. However, there often was a good reason why a feature was

expressed in a certain way in another language, especially when this

language is a language where the type safety was given a lot of thought,

as ML and Haskell are. We should take some time to consider it. Here the

reason of introducing new bindings is not (solely) expressivity, it's

safety, a characteristic we care deeply about.

> Tucker's idea seems to fit with the existing syntax of the language, and

> seems to be sufficient for the job.

I strongly disagree with that. Tucker's idea is insufficient to

guarantee safety, which is one of the key points of this  feature, not

expressivity. I'm waiting for a counter argument :)

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

From: Randy Brukardt

Sent: Monday, February 13, 2017  5:10 PM

> Thank you for your answer Randy, and sorry I took so long to answer !

> I have been swamped with other work at AdaCore, currently depiling my

> ARG work pile :)

Real work being more important than ARG fun -- what a concept! :-)

 

...

> This is not about mistyping, it is about accessing a component that is

> statically valid, but dynamically invalid due to discriminants. Let me

> amend a previous example:

>

> C, C2 : Connection_Info

>

> case C is

>    when (Connected, <>, <>, (True, <>, <>)) =>

>       Print_Ping_Time (C2.Ping_Info.Ping_Time)

>

> Here you're accessing the wrong object altogether (C2). This is valid

> Ada, so it is pretty impossible to emit a warning, even though the

> code is clearly wrong.

Well, it's only clearly wrong if you know the intent; I have parallel objects

like this all the time (often in writing list process). Which I suppose is

your point.

BTW, you've again ignored the fact that <> can only appear in named notation,

and I think that really does make a difference in these examples. (Not to

mention that your example has seven components when written this way, not

six. :-)

> Arguably the

> programmer should have used more descriptive names. He should also

> have not made errors. The job of the compiler is to help him, and

> that's a great opportunity to do so.

>

> With introducing a binding, both writing the code and checking it is

> easier:

>

> C, C2 : Connection_Info

>

> case C is

>    when (Connected, <>, <>, (True, <>, <Ping_Time>)) =>

>       Print_Ping_Time (Ping_Time)

>

>

> > Keep in mind that every identifier (and every entity for that

> > matter) that one declares adds to the cognitive load of the reader.

> > You really shouldn't do it unless it actually helps reading the

> > code. (Exactly where that line is obviously is a personal choice,

> > but it is far away from renaming a single character identifier.)

>

> Yes, I agree with that line of reasoning. The C/CC example was a

> straw-man, of the alternative proposal I don't like. In the example

> above, I feel like the  "Ping_Time" binding that is introduced, both

> by it's strong  locality and because of the static guarantees

> associated to it, helps the user understand the code and make sure

> it's correct.

What guarantees? It seems to me that you need those guarantees anytime you

have any sort of binding (the form doesn't matter). That is, the Tucker-style

binding needs the same guarantees.

> >> Having to repeat the path is less readable and more error prone.

> >> You go through the trouble of expressing the pattern, just to have

> >> to repeat the logic underneath, effectively writing the paths

> >> twice, once in aggregate syntax, the other in prefix syntax.

> > Arguably, that's a good thing.

>

> Let's argue then :) I see no benefit in repeating the path, only

> potential for errors, both for the writer and for the reader of the

> code.

>

> > BTW, Ada doesn't currently allow positional <> aggregate components,

> > and I'd suggest that we retain that rule here. (Assuming you really

> > want to model these patterns as aggregates.) [Especially as many

> > style guides ban positional record aggregates altogether.]

> > Therefore, your example would have to be written something like:

> >

> > case Get_Connection (From => Server) is

> >     when CC : (State => Connected, Server => <>,

> >                Session_Id => <>,

> >                Ping_Info => (Has_Ping_Info => True, Last_Ping_Time =>

> >                              <>, Last_Ping_Id => <>)) =>

> >         Put_Line ("Connected ! Session Id is "

> >                   & CC.Session_Id & " Ping time is "

> >                   & CC.Ping_Info.Last_Ping_Time'Image);

> >

> > So the names you need are already in the source. Declaring more

> > names would just be more noise.

>

> This does not make sense. The introduction of new names is used to

> introduce new bindings. If you have a record "Line"

> with two "Points"

> components, who themselves have X and Y components, if you want to

> match on the 4 subvalues the components names are not going to be

> enough.

What doesn't make sense? You already have to have the component names in the

pattern, adding binding names as well is likely be confusing rather than

helpful.

Side-comment here: The way you have the binding defined, it doesn't seem

possible to pass a larger part of the matched record to a subprogram.

Consider a modification of the above:

 case Get_Connection (From => Server) is

     when CC : (State => Connected, Server => <>,

                Session_Id => <>,

                Ping_Info => (Has_Ping_Info => True, Last_Ping_Time =>

                              <>, Last_Ping_Id => <>)) =>

         Put_Line ("Connected ! Session Id is "

                   & CC.Session_Id & Display_Ping_Info (CC.Pinf_Info));

In this case, we're using an existing routine to generate the details about

the Ping_Information. That's pretty common (after all, one of the likely

reasons for having a subrecord is that it gets independently processed). I

don't see any way of doing this with your binding short of going back and

copying the original selecting information.

...

> The point of this feature, in my mind, is that you match the

> discriminants and the components at the same time. So every new

> binding you introduce is statically guaranteed to correspond to

> something in the matched value.

>

> If you combine that with:

>

> 1. A style rule (that can easily be statically checked) that it is

> forbidden to access variable components of a record via the regular

> dot notation, eg. you have to use matching.

> 2. A legality rule that it is forbidden to mutate the object that

> you're matching upon (similar to the rule about renamings of

> discriminated records component if I remember correctly)

It has to be the latter, especially in your scheme -- it is essentially a

renaming of a discriminant-dependent component, so the same rules have to

apply. (We adopted that rule for iterators, for instance, for similar

reasons.) That means that the selecting expression would have to be "known

to be constrained".

And this is what I was talking about above: this is a property of *any*

binding in such a matching (so long as some discriminant-dependent components

are involved); it doesn't really matter about the syntax involved. If you have

any non-box matching on a discriminant or discriminant-dependent component,

you can't allow the item to be mutable lest the promise implicit in the

declaration be violated.

So the safety issue is the same either way; it doesn't depend on how the

binding(s) are defined.

...

> I think it is possible to reach this goal without introducing new

> bindings, eg. you need to check the components paths used inside case

> branches, but the specification and implementation of such a feature

> will be harder as far as I can tell.

It's easy, I described it above. It's all in terms of existing Ada terminology

("known to be constrained", "discriminant-dependent component", etc.). It

might have to apply to multiple records (which I believe is already the case

for renames), but nothing hard or weird about that.

...

> > Tucker's idea seems to fit with the existing syntax of the language,

> > and seems to be sufficient for the job.

>

> I strongly disagree with that. Tucker's idea is insufficient to

> guarantee safety, which is one of the key points of this feature, not

> expressivity. I'm waiting for a counter argument :)

Tucker's idea combined with a "known-to-be-constrained" rule works fine to

guarantee safety (as it is the same as an object rename), and indeed that

seems necessary for any sort of binding. So that ends up identical either way.

OTOH, your proposal doesn't seem to allow both partial matching AND direct

access to the enclosing (sub)record that contains that matching. That seems

to be *less* functionality and *more* complexity. Which makes it a no-brainer

to me, YMMV. ;-)

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

From: Raphael Amiard

Sent: Tuesday, February 14, 2017  5:10 PM

>> Thank you for your answer Randy, and sorry I took so long to answer !

>> I have been swamped with other work at AdaCore, currently depiling my

>> ARG work pile :)

> Real work being more important than ARG fun -- what a concept! :-)

It's all fun, with varying degrees of "urgent" attached :)

>> case C is

>>     when (Connected, <>, <>, (True, <>, <>)) =>

>>        Print_Ping_Time (C2.Ping_Info.Ping_Time)

>>

>> Here you're accessing the wrong object altogether (C2). This is valid

>> Ada, so it is pretty impossible to emit a warning, even though the

>> code is clearly wrong.

> Well, it's only clearly wrong if you know the intent; I have parallel

> objects like this all the time (often in writing list process). Which

> I suppose is your point.

It's not only clearly wrong if you know the intent: It's clearly wrong if

your goal is to disallow access to a discriminant dependent component, when

you don't statically know that this access is correct (which is the case

above), then you should not access it, regardless of the intent.

> BTW, you've again ignored the fact that <> can only appear in named

> notation, and I think that really does make a difference in these examples.

> (Not to mention that your example has seven components when written

> this way, not six. :-)

Yes, sorry about that, I did not completely ignore it, I think I altered some

of them, and not all, very sloppy of me...

>> Yes, I agree with that line of reasoning. The C/CC example

>> was a straw-man, of the alternative proposal I don't like. In

>> the example above, I feel like the  "Ping_Time" binding that

>> is introduced, both by it's strong  locality and because of

>> the static guarantees associated to it, helps the user

>> understand the code and make sure it's correct.

> What guarantees?

The ones outlined above: You can statically check that a components exists

before accessing it.

> What doesn't make sense? You already have to have the component names in the

> pattern, adding binding names as well is likely be confusing rather than

> helpful.

In that case, we're talking about a point record with no discriminant, so we

don't care about safety, so it's completely a style issue, which is by essence

subjective. I can't find a concrete example to discuss so let's agree this is

not a case that is interesting for this discussion.

> Side-comment here: The way you have the binding defined, it doesn't seem

> possible to pass a larger part of the matched record to a subprogram.

> Consider a modification of the above:

>

>   case Get_Connection (From => Server) is

>       when CC : (State => Connected, Server => <>,

>                  Session_Id => <>,

>                  Ping_Info => (Has_Ping_Info => True, Last_Ping_Time =>

>                                <>, Last_Ping_Id => <>)) =>

>           Put_Line ("Connected ! Session Id is "

>                     & CC.Session_Id & Display_Ping_Info (CC.Pinf_Info));

>

> In this case, we're using an existing routine to generate the details about

> the Ping_Information. That's pretty common (after all, one of the likely

> reasons for having a subrecord is that it gets independently processed). I

> don't see any way of doing this with your binding short of going back and

> copying the original selecting information.

To be clear, I'm not arguing that top level binding is useless, in fact many

languages with pattern matching do propose it. I'm arguing that it is not a

substitute for sub components bindings, for the reasons outlined before.

This example of yours, while arguably expressive, also shows why it would be

hard to guarantee the property I have outlined above - no access to fields if

you can't guarantee their legality statically. You would have to keep a shape of

the whole data structure, with known and unknown discriminants, possibly across

indirectly nested case statements. This is flow analysis at this stage, and

probably something you don't want to make mandatory at the language level.

>> If you combine that with:

>>

>> 1. A style rule (that can easily be statically checked) that it is

>> forbidden to access variable components of a record via the

>> regular dot

>> notation, eg. you have to use matching.

>> 2. A legality rule that it is forbidden to mutate the object

>> that you're

>> matching upon (similar to the rule about renamings of discriminated

>> records component if I remember correctly)

> It has to be the latter, especially in your scheme -- it is essentially a

> renaming of a discriminant-dependent component, so the same rules have to

> apply. (We adopted that rule for iterators, for instance, for similar

> reasons.) That means that the selecting expression would have to be "known

> to be constrained".

My list is inclusive, not exclusive. Of course 2. has to be guaranteed with

Tuck's proposal and with mine. However it is not the main point. The main point

is 1., because this is what will allow to enforce the invariant that no

component of a discriminated record is accessed if we don't know statically that

it is correct.

With the rule enforced, the code at the beginning:

C, C2 : Connection_Info

case C is

    when (Connected, <>, <>, (True, <>, <>)) =>

       Print_Ping_Time (C2.Ping_Info.Ping_Time)

Would be illegal because C2.Ping_Info.Ping_Time would fall under this rule.

If this was really the intent of your code, you'd have to write:

C, C2 : Connection_Info

case C is

    when (Connected, <>, <>, (True, <>, <>)) =>

       Print_Ping_Time

         (case C2 is

            when (Connected, <>, <>, <>, (True, <>, <PT>)) => PT)

            when others => No_Ping_Time)

It is more verbose, which is in this case a good thing ! You're ensuring that

the programmer handles the error case explicitly.

> So the safety issue is the same either way; it doesn't depend on how the

> binding(s) are defined.

Only because we're not talking about the same safety issue.

>> I strongly disagree with that. Tucker's idea is insufficient to

>> guarantee safety, which is one of the key points of this feature, not

>> expressivity. I'm waiting for a counter argument :)

> Tucker's idea combined with a "known-to-be-constrained" rule works fine to

> guarantee safety (as it is the same as an object rename), and indeed that

> seems necessary for any sort of binding. So that ends up identical either

> way.

It does not, as explained above, guarantee safety of an accessed component of a

discriminated record if that component depends on the discriminant.

> OTOH, your proposal doesn't seem to allow both partial matching AND direct

> access to the enclosing (sub)record that contains that matching.

It does. You just have to put your result in a declare block. You're not usually

one to argue that this added verbosity is actually a big deal I think !

declare

   CC : Connection_Info := Get_Connection (From => Server)

begin

  case CC is

      when (State => Connected, Server => <>,

            Session_Id => <>,

            Ping_Info => (Has_Ping_Info => True, Last_Ping_Time =>

                          <>, Last_Ping_Id => <>)) =>

          Put_Line ("Connected ! Session Id is "

                    & CC.Session_Id & Display_Ping_Info (CC.Pinf_Info));

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

From: Randy Brukardt

Sent: Tuesday, February 14, 2016  4:16 PM

> To be clear, I'm not arguing that top level binding is useless, in fact many

> languages with pattern matching do propose it. I'm arguing that it is not a

> substitute for sub components bindings, for the reasons outlined before.

Well, you have to be careful about making a proposal too complex. I've learned

through much bitter experience that if you come up with a fully worked out

proposal with all of the bells and whistles, you're most likely to end up with

nothing. It probably would have been better to spring this component matching

proposal when the rest of this idea is nearly finished... :-)

> This example of yours, while arguably expressive, also shows why it would be

> hard to guarantee the property I have outlined above - no access to fields if

> you can't guarantee their legality statically. You would have to keep a shape of

> the whole data structure, with known and unknown discriminants, possibly across

> indirectly nested case statements. This is flow analysis at this stage, and

> probably something you don't want to make mandatory at the language level.

Within in one of your case statements (or, for that matter, in the scope of a

renames of the component), the Legality Rule already guarantees the property

you want. Indeed, because of the renames solution, there is a way to already

guarantee the property in Ada today (with an appropriate checking tool, of

course; sounds like something AdaControl could do).

That is, one could insist that all discriminant dependent components are bound

with renames before use:

     Ping_Time : ... renames Get_Connection (From => Server).Ping_Info.Last_Ping_Id;

Combined with appropriate "if"s/assertions, you can be guaranteed that the

component exists and is safe. (Indeed, with a proper tool, you really shouldn't

need to do anything, as a tool can relatively easily prove this property if it is

provable at all.)

...

> >> 1. A style rule (that can easily be statically checked) that it is

> >> forbidden to access variable components of a record via the regular

> >> dot notation, eg. you have to use matching.

...

> The main point is 1., because this is what will allow to enforce the

> invariant that no component of a discriminated record is accessed if

> we don't know statically that it is correct.

At least in my code, it is common to have a subprogram that works on a single

variant. For instance, the routine I was working on yesterday (slightly

modernized):

    procedure Lookup_Allocator (Expr : in Node_Ptr)

       with Pre => Expr.Kind = Allocator;

    procedure Lookup_Allocator (Expr : in Node_Ptr) is

    begin

        Lookup_Expr (Expr.Allocator_Type); -- The component is discriminant-dependent.

        ...

    end Lookup_Allocator;

With your rule, you'd have to wrap this entire body in one of your case

statements, and presumably have an "others" clause with an Internal_Error

call. But that completely defeats the purpose of the precondition (violating

our style rule: "never repeat the precondition in the body"), and would add a

lot of extra verbiage to the code.

So that seems like a very silly rule to have in general. I could see having it

in code that is inside of a case statement, but that seems too limited to be

of much use. And clearly, any barely competent tool could prove that the

component use is safe (at least in the absence of some other task causing

mischief).

> With the rule enforced, the code at the beginning:

>

> C, C2 : Connection_Info

>

> case C is

>     when (Connected, <>, <>, (True, <>, <>)) =>

>        Print_Ping_Time (C2.Ping_Info.Ping_Time)

>

>

> Would be illegal because C2.Ping_Info.Ping_Time would fall under this

> rule.

>

> If this was really the intent of your code, you'd have to write:

>

> C, C2 : Connection_Info

>

> case C is

>     when (Connected, <>, <>, (True, <>, <>)) =>

>        Print_Ping_Time

>          (case C2 is

>             when (Connected, <>, <>, <>, (True, <>, <PT>)) => PT)

>             when others => No_Ping_Time)

>

>

> It is more verbose, which is in this case a good thing !

> You're ensuring that the programmer handles the error case explicitly.

Seriously, this looks like madness to me. No sane programmer is ever going

to write the second just to meet some style rule. (Especially if they have to

put all of the component names into the patterns!)

...

> It does not, as explained above, guarantee safety of an accessed

> component of a discriminated record if that component depends on the

> discriminant.

That's not a worthwhile goal if it requires writing gallons of unnecessary

code, especially in the precondition/predicate/assertion cases.

> > OTOH, your proposal doesn't seem to allow both partial matching AND direct

> > access to the enclosing (sub)record that contains that matching.

>

> It does. You just have to put your result in a declare block. You're not

> usually one to argue that this added verbosity is actually a big deal I

> think !

>

> declare

>    CC : Connection_Info := Get_Connection (From => Server) begin

>   case CC is

>       when (State => Connected, Server => <>,

>             Session_Id => <>,

>             Ping_Info => (Has_Ping_Info => True, Last_Ping_Time =>

>                           <>, Last_Ping_Id => <>)) =>

>           Put_Line ("Connected ! Session Id is "

>                     & CC.Session_Id & Display_Ping_Info (CC.Pinf_Info));

If that's acceptable, then you don't need any binding mechanism and the

complications that it brings.

Besides, if you're really willing to write a lot of code, you don't need this

feature at all, so that simplifies it down to nothing -- the ultimate simple

solution. ;-)

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

From: Raphael Amiard

Sent: Wednesday, June 14, 2016  8:38 AM

> Well, you have to be careful about making a proposal too complex. I've

> learned through much bitter experience that if you come up with a

> fully worked out proposal with all of the bells and whistles, you're

> most likely to end up with nothing. It probably would have been better

> to spring this component matching proposal when the rest of this idea

> is nearly finished... :-)

I think the component matching is integral to the feature actually. Something

that only allows you to match literals would be crippled, both in terms of

expressivity and in terms of potential safety. It would still be a big

improvement on the status quo though, so I guess we can discuss this live in

Vienna !

> > This example of yours, while arguably expressive, also shows why it

> > would be hard to guarantee the property I have outlined above - no

> > access to fields if you can't guarantee their legality statically.

> > You would have to keep a shape of

> > the whole data structure, with known and unknown discriminants,

> > possibly across indirectly nested case statements. This is flow

> > analysis at this stage, and probably something you don't want to make

> > mandatory at the language level.

>

> Within in one of your case statements (or, for that matter, in the

> scope of a renames of the component), the Legality Rule already

> guarantees the property you want. Indeed, because of the renames

> solution, there is a way to already guarantee the property in Ada

> today (with an appropriate checking tool, of course; sounds like something

> AdaControl could do).

>

> That is, one could insist that all discriminant dependent components

> are bound with renames before use:

>      Ping_Time : ... renames Get_Connection (From =>

> Server).Ping_Info.Last_Ping_Id; Combined with appropriate

> "if"s/assertions, you can be guaranteed that the component exists and

> is safe. (Indeed, with a proper tool, you really shouldn't need to do

> anything, as a tool can relatively easily prove this property if it is

> provable at all.)

I'm not sure I understand this point ! We'll have to discuss this live.

> At least in my code, it is common to have a subprogram that works on a

> single variant. For instance, the routine I was working on yesterday

> (slightly modernized):

>

>     procedure Lookup_Allocator (Expr : in Node_Ptr)

>        with Pre => Expr.Kind = Allocator;

>

>     procedure Lookup_Allocator (Expr : in Node_Ptr) is

>     begin

>         Lookup_Expr (Expr.Allocator_Type); -- The component is discriminant-dependent.

>         ...

>     end Lookup_Allocator;

>

> With your rule, you'd have to wrap this entire body in one of your

> case statements, and presumably have an "others" clause with an

> Internal_Error call. But that completely defeats the purpose of the

> precondition (violating our style rule: "never repeat the precondition

> in the body"), and would add a lot of extra verbiage to the code.

>

> So that seems like a very silly rule to have in general. I could see

> having it in code that is inside of a case statement, but that seems

> too limited to be of much use. And clearly, any barely competent tool

> could prove that the component use is safe (at least in the absence of

> some other task causing mischief).

I understand what you mean. I think it is also an issue of code style, and as

with error handling in general, there is no unique good solution. However:

1. I do not think everybody would need to use that rule, or for that matter,

   such a simple rule. Its use is a trade-off between simplicity and safety,

   one that I would personally choose.

2. If you have a tool that does basic intra-procedural analysis, such as what

   you seem to be advocating, you could make the rule more powerful, by saying

   it only forces you to check the discriminant if it is not known at this

   point in the control flow, making the code above OK !.

In that case, the fact of being able to bind sub-components in the matchers is

just a very DRY convenient way to get at sub-components.

> Seriously, this looks like madness to me. No sane programmer is ever

> going to write the second just to meet some style rule. (Especially if

> they have to put all of the component names into the patterns!)

First, I don't agree that this second part should be enforced, second, your

perspective on verbosity seems double-standard'ish to me:

- You're fine with repeating sub-components name completely, even though it

  brings no benefits to the user and makes checking safety harder.

- You think this is unacceptable verbosity even though it brings substantial

  benefits. This isn't just "some style rule", it is a (certainly restrictive)

  rule that allows the programmer to be sure that it will eliminate a certain

  class of errors completely.

> ...

> > It does not, as explained above, guarantee safety of an accessed

> > component of a discriminated record if that component depends on the

> > discriminant.

>

> That's not a worthwhile goal

I strongly disagree with this unsubstantiated claim. I think it is a very

worthwhile goal.

> if it requires writing gallons of unnecessary

> code, especially in the precondition/predicate/assertion cases.

As explained above, we can imagine smarter rules/tools if you use a style of

code where you know you pass around objects with an already known discriminant.

Or you can still, not use the rule altogether.

> Besides, if you're really willing to write a lot of code, you don't need this

> feature at all, so that simplifies it down to nothing -- the ultimate simple

> solution. ;-)

I don't see how that is True, a-minima you still need some flow sensitive

checking tool to guarantee access to fields that depend on a discriminant.

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

From: Randy Brukardt

Sent: Wednesday, June 14, 2016  2:42 PM

>> Besides, if you're really willing to write a lot of code, you don't need this

>> feature at all, so that simplifies it down to nothing -- the ultimate simple

>> solution. ;-)

>

>I don't see how that is True, a-minima you still need some flow sensitive

>checking tool to guarantee access to fields that depend on a discriminant.

You need that in any case, and any ASIS-based tool has enough information to

make the check. So, for that matter, does any compentent Ada optimizer. (This

would make a possible Code Quality Warning in Janus/Ada, see the most recent

blog entry on RRSoftware.Com - http://www.rrsoftware.com/html/blog/quality.html

- for the basic idea.) No extra syntax needed.

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

!topic Renaming in class membership test

!reference Ada 2012 RM4.4(3/4)

!from Niklas Holsti 18-01-15

!keywords membership renaming

!discussion

(This suggestion for some Ada extensions is taken from a discussion on

comp.lang.ada, started on 2018-01-04 by Dmitry A. Kazakov within a

thread with the irrelevant Subject "Re: stopping a loop iteration

without exiting it".)

It is sometimes necessary to supplement dynamic dispatching by manually

coded case analysis, using membership tests in which the

tested_simple_expression has a class-wide type and the membership_choice

is a descendant class-wide subtype, for example as follows, where X is a

class-wide expression:

   if X in T'Class then ...

If the test returns True, the following actions usually need to access

the tested_simple_expression (X) as an object of the membership_choice

descendant type (T'Class). This leads to the following clumsy

construction, which requires writing the descendant type identifier

three times, and introducing a new indentation level:

   if X in T'Class then

      declare

         Same_X : T'Class renames T'Class (X);

      begin

         ... use Same_X as an object of T'Class

      end;

   end if;

Both Dmitry and I have been bothered by this feature of class-based case

analysis.

It is suggested to allow a combination of the membership test and the

declaration of the renaming (Same_X), as in:

    if X is Same_X : T'Class then

       ... Here Same_X is a renaming of T'Class (X).

    end if;

This form uses a different keyword ("is", not "in") to separate it from

the normal membership test.

Clearly, this form of membership test cannot have more than one

membership_choice (that is, it cannot be "if X is T'Class | S'Class then

...") and it cannot be a negative test (it cannot be "is not").

Earlier in the same discussion thread, a similarly extended "case"

statement for a class-wide selecting_expression was suggested, as in:

    case X is  -- or "case X'Tag"

       when Some_T : T'Class =>

          ... Here Some_T is a renaming of T'Class(X).

       when Some_S : S =>

          ... Here Some_S is a renaming of S(X).

       when others => ...

    end case;

where the legality conditions would require that no two "when" clauses

have overlapping classes (that is, both "whens" cannot be True for the

same X) and that an "others" clause always be present. However, this

form could be problematic in a generic context, where the

non-overlapping requirement of formal generic types (T, S) might not be

easy to check at compile-time.

A further extension to the above would let the selecting_expression (X)

be an access-to-class-wide, instead of a class-wide, with implicit

dereferencing for the renamings (Some_T would be a renaming of

T'Class(X.all)), and would then permit a "when null => ..." to handle

the case X = null.

Continuing with further variations, access types in general can

sometimes lead to similar clumsy renamings, as in this example from

Dmitry, where P is access X_Type:

    if P /= null then

       declare

          X : X_Type renames P.all;

       begin

          ...

       end;

Here, again, an extension might allow a "case" statement with the access

value P as the selecting_expression, although it can access only a

single type, and a renaming combined with the "when":

    case P is

       when X : X_Type =>

          ... Here X is a renaming of P.all.

       when null =>

          ...

    end case;

Finally, a similar extension was suggested to the normal "case"

statement, with a discrete selecting_expression. Here the extension is

not needed to avoid a renaming declaration, but could help readability.

For an example, from Dmitry, the following code:

    declare

       Symbol : constant Character := Get_Character;

    begin

       case Symbol is

          when '0'..'9' =>

             -- Process digit

          when 'A'..'Z' | 'a'..'z' =>

             -- Process letter

could be replaced by this, somewhat simpler code:

    case Get_Character is

       when Digit : '0'..'9' =>

          -- Process the digit Digit.

       when Letter : 'A'..'Z' | 'a'..'z' =>

          -- Process the letter Letter.

As observed in the comp.lang.ada thread, these suggestions have the

common flavour of introducing a kind of "pattern matching" syntax into

Ada control flow, but a very simple one (the pattern defines a single

new name).

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

From: Randy Brukardt

Sent: Saturday, January 20, 2018  8:15 PM

> As observed in the comp.lang.ada thread, these suggestions have the

> common flavour of introducing a kind of "pattern matching" syntax into

> Ada control flow, but a very simple one (the pattern defines a single

> new name).

There already is such a "pattern matching" proposal in the hopper; the current

plan is to split it from AI12-0214-1 where it currently lives.

I first became concerned about the number of gee-gaws proposed for Ada 2020

because of this pattern matching proposal, so that should make it fairly

obvious where I stand on this one. ;-)

...

> If the test returns True, the following actions usually need to access

> the tested_simple_expression (X) as an object of the membership_choice

> descendant type (T'Class). This leads to the following clumsy

> construction, which requires writing the descendant type identifier

> three times, and introducing a new indentation level:

>

>    if X in T'Class then

>       declare

>          Same_X : T'Class renames T'Class (X);

>       begin

>          ... use Same_X as an object of T'Class

>       end;

>    end if;

I've written a lot of such code (especially in the Claw Builder), and I never

once used this construction. In the Claw Builder, X typically is a dereference

of an access to a Root_Window, and the test is needed to call some operation

on some operation only defined for a child hierarchy (for instance, for

controls).

In such cases, the dependent code is almost always a single (dispatching?)

call, and since there is only one use of the name, it is better to just use

the type conversion directly on the call parameter, rather than to introduce

an extra name. Even if there are several uses, it is often the case that the

conversions are hardly any longer than the renaming, so the simpler code is

preferred.

In general, I think it is a bad idea to rename objects, as that requires

introducing an additional identifier to the program, increasing the number

that a reader must understand. Renaming is mainly a construction that helps

the writer, not the reader. I think it is best reserved for the rare cases

where the entity has to be evaluated once rather than multiple times. (It also

can make the code slower, by creating an intermediate storage location with

associated memory write(s), rather than just evaluating into registers).

Aliasing (having multiple names for the same thing) makes code harder to

understand both for the compiler and for human readers. There's a reason that

it is better to pass in all of the objects needed for a given subprogram even

if they are visible elsewhere (so the reader can consider the subprogram as a

single unit without considering any possible aliasing).

...

> Continuing with further variations, access types in general can

> sometimes lead to similar clumsy renamings, as in this example from

> Dmitry, where P is access X_Type:

>

>     if P /= null then

>        declare

>           X : X_Type renames P.all;

>        begin

>           ...

>        end;

This is even worse. You've introduced an entire block and an extra name to

save 4 characters on each use! Any reasonable compiler will eliminate any

redundant checks, so this construction buys nothing except a bunch of extra

lines in the code (and an obvious reduction in readability).

Now, I realize I am a charter member in the "write it all out explicitly"

club (I doubt that few other Ada programmers will write out

"Ada.Strings.Unbounded.To_Unbounded_String" as often as I do), but Ada code

is (or should be) primarily about making the result easy to read and

understand. Reasonable people can disagree about about how long is too long,

but the only time constructions like the above make sense is when the new name

is substantially shorter (remaining understandable) than the original name.

And in such cases, the length of the construction isn't particularly relevant

(since it is a lot less than the names in question). Shorthands here just make

it easier for the writer rather than helping the reader.

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

 

[a]For array values, I doubt that modelling the selecting expression on array aggregates is expressive enough. In the paper to which you refer, you show an example of an array aggregate that matches a string that contains a C-style hexadecimal literal, that is, a "0x" prefix followed by hex digits. But this example could not, I believe, be changed to match Ada-style hexadecimal literals, because of the need to match the final '#' that would follow the "others" that matches the hex digits. A regular expression could work, but I think that deciding "containment" between regular languages is quite hard, so it would be hard to verify the required completeness etc. properties.

Have you considered instead making each selecting expression be a Boolean expression, but restricted to forms that your algorithm can handle?

[b]In the past we have talked about having a case-like construct where each alternative is a boolean expression, but it is clearly a different construct.  I believe we discussed something like:

*case is*

*when* A > B =>

*when* A = B =>

*when* A < B =>

*end case*

This is basically just a slight restructuring of an if/elsif chain, so doesn't really add much value.

Randy suggested allowing if/elsif chains to be checked for the three "desirable" properties mentioned in the AI, but that would clearly require some way of signaling the check is desired.  So I suppose "case is" could be a way to do that.

[c]As far as the power of array aggregates, they are useful for simple situations, which is all we are aiming for in this AI.  A future AI could expand toward a more general pattern matching language, but that is not the goal of this one.

[d]OK, thanks.

[e]I really hate declarations that do not optionally include their subtype and especially those that do not look like [Ada] declarations, so the boxy form is a non-starter for me. I note that it isn't used in any examples, so it doesn't seem very important, either. Please drop it.

[f]There is no reason we couldn't include the subtype within the <...>, e.g. <W : subtype>.

[g]The readability of that would be bad. These things are not going to be using single character names and 7 character subtype names -- the average object declaration in my programs is somewhere around 40 characters (in part because we use fully qualified names for types and subtypes - use clauses are mainly used for operators and enumeration literals). With a typical sized declaration the opening and closing angle brackets would be widely separated.

[h]For the <...> construct, the subtype will come from the matched component, so it is more like a local rename of the component, and for renames, we know generally the specified subtype is largely ignored.

We could certainly say that the short-hand cannot be used if you want to be explicit about the subtype.  Note that the short-hand is replacing "L : <>" with "<L>".

In fact, inserting an explicit subtype has somewhat different semantics, since it restricts itself to components whose value satisfies the subtype constraints and static predicates.  So that is not the same meaning as "<>", which is indicating no constraints or predicates on the value.  If you want to limit it to a particular subtype, that would require "L : subtype" and no shorthand would be available anyway.

[i]Do you mean that there would be a compile-time check that E.Right is not accessed in the "when (Unary, ...)" code? Would it also be forbidden to assign a new value to E in that code (if E were a variable)?

[j]This is related to the issue of renaming of discriminant-dependent components, which is discussed in AI22-0131-2 on the proposed "Part_Of" aspect, and probably needs attention to enable such local renames more broadly.

[k]Did you mean this "case" to completely cover the possible cases? It seems not to handle the case where some, but not all, of the "others" elements are 1, and possibly also the case where A'Length < 2, depending on the meaning of "others" here.

[l]Good point.  Simplest is to add an "others" case.

[m]OK. Further question, about the cases with array aggregates: evidently they do not cover the case A'Length = 0, but do they cover the case A'Length = 1? That is, do the "others" parts in the aggregates match a value of A where there are no "others" elements after the first element? If they do, then if there are two "when" cases that have the same condition for the first element and only differ for the "others" elements, those would be ambiguous when A'Length = 1. Which case would be chosen?

[n]If two choices could both match the same value, and neither is a proper superset of the other, then it would be illegal.

See "No ambiguity between alternatives" below.

[o]You did not answer my question for A'Length = 1. In the paper to which you refer, you say that an "others" part "indicates that there MIGHT be further components" (my capitalization), so the answer is that the patterns do apply when A'Length = 1 and there are no further components.

This means that the example pattern in the article that should match C hexadecimal literals also matches the string "0x", which I do not think is an acceptable C hex literal. So that pattern aggregate should be ['0', 'x' | 'X', Hex_Char, others => Hex_Char], right?

[p]Sorry, I didn't quite understand your  question.  Yes, it sounds like the example in the article needs to be amended.

[q]<> is only allowed in positional array aggregates after "others". Please correct.

[r]There are a number of ways these choice aggregates differ from standard aggregates, in that they allow ranges rather than single values, etc.  "<>" in this context simply means "any value".

[s]I understand that, but <> here is used in the same way that it is used in an aggregate: as an entity that matches a value of any type. The reasons that we don't allow it in positional aggregates (too much chance for error or confusion given the lack of type information) certainly apply here as well. I see no reason for these to be different than aggregates here (and indeed, if this is to be allowed here, then it should be allowed for regular aggregates as well).

[t]I have added to the discussion the point that when there a series of choices, it is helpful to have the wildcards line up with the non-wildcard choices, which wouldn't work if we allow positional notation for non-wildcard choices but require named notation for wildcard choices.

I also believe these are different in that they are not defining the value, simply matching against an existing value, so the possibility of unintentionally introducing an uninitialized component is not there.

[u]I don't see the problem, in that if you want them to line up, give all of them named notation. You can still give positional notation for any other components. Indeed, I  believe that almost all record aggregates should use named notation anyway,  and that is especially true when the component expression does not carry a type.

[v]This looks like something where a straw vote might help clarify what is the consensus of the ARG.  This seems particularly onerous when matching against an array, since the normal array aggregate syntax would make it very difficult to have any correspondence between different choices when using <> for some of the components.

[w]In the paper you say that the sequence_pattern provides "rudimentary support for regular expressions", including the "+" and "*" suffixes to indicate repetition. Do you include this in your suggestion for Ada (this AI)?

If so, then the following pattern would match an Ada hex literal:

['1', '6', '#', Hex_Char+, '#']

except that it does not allow underlines. Underlines could be included, but the pattern would be more complex, to exclude leading or trailing underlines and repeated underlines.

[x]I was not proposing a full pattern-matching construct in this AI.  I was focusing on just a few features in the generalized case statement.  I would wait for the ARG to see how far we want to go beyond this point.  We could add more features over time if we see the generalized case statement is embraced by the Ada community.

[y]OK, understood.

I am happy to make a laudatory comment, for a change: in reading the paper, I was impressed with the red-black tree rotation example. The pattern approach very nicely bypasses all the checks for null pointers that would be necessary in conventional code using Boolean expressions. I tried to come close to similar brevity with a number of helper subprograms, and defining the children as an array indexed by the enumeration (Left, Right) to handle the symmetrical cases,  but it was considerable work.

[z]Perhaps the algorithm can, but my brain can't. :-) I don't understand how to characterize the inputs to the NFA/DFA in cases where the choices cannot be easily enumerated. (Such as float values, private types, etc.) Are we trying somehow to exclude such cases? (The AI starts with "extending case statements to the value of objects of any type". That doesn't seem possible to me.)

[aa]Presuming we have optional objects, then choices such as "null" or "not null" make sense for all such types.  For private types, we propose, at a minimum, allowing matching against discriminant values/ranges.  For floating point types, ranges could be used.  There is no current way to specify open ranges, only closed ranges, so some sort of <> or "others" wildcard would be needed to ensure full coverage.

We could define a syntax for open ranges (e.g. 1.0 ..< 2.0) but that would be in a separate AI, if we think it is important.  This would allow full coverage of a floating point range without ambiguity.  If you stick to only supporting closed ranges, covering a (nearly) continuous range like that for floating-point values with two or more case choices would be awkward.

[ab]Perhaps answering your question more directly, a case statement can generally be converted into a series of if/then/elsif statement.  Presuming you can do that, then the NFA => DFA algorithm is simply merging and splitting the tests performed so that it is deterministic, without backtracking.

[ac]A few points here: (1) Float ranges aren't very useful; you touched on some reasons why, additionally they don't take into account denormal values and NaNs (which need to be allowed in some circumstances. Don't see how one could usefully make a completeness check with them.

(2) Yes, one can always make a NFA. But my question was about how that could be used to make completeness and overlap checks. It sounds to me like it cannot much outside of discrete entities (discrete values, null/not null, etc.). So the claim that it can handle "arbitrary matching requirements" is a wild stretch. (If the choices are arbitrary boolean functions, there is no possibility of telling anything about overlaps.) Perhaps tone this down some to make the limits clear.