Dispatching case statement extension

I have been writing a lot of very tedious and unsafe code recently. The code is related to serialization and input-output. The problem is inability to dispatch to lately, when the type is frozen. I cannot go back and redesign the whole types hierarchy or I intentionally do not want to in order to keep the new code separate. Consider an embedded target that has a carefully designed set of types. On the host side you might wish to add pretty printing, graphic rendering and thousands other stuff not needed on the target.

I came up to a relatively simple language extension of the case statement that would select alternatives according to the object’s tag. Let we have a hierarchy of types:

  type I is interface;
  type T is tagged null record;
  type T1 is new T with null record;
  type T2 is new T with null record;
  type T3 is new T1 and I with null record;
  type T4 is new T1 and I with null record;
  ...
  procedure Print (File : File_Type; Object : T'Class);

How to implement Print? Presently one have to write a long if-statement with renaming declarations:

   if Object in T1 then
      declare
         X : T1 renames T1 (Object);
      begin
         ...
      end;
   elsif Object in I'Class then
      ...

It is not only excessively long, but also exposed to errors. E.g. checking for T3 must precede checking for I’Class.

The dispatching case statement would be:

  case Object is
     when X : T1 => -- X is T1 renames T1 (Object)
        ... -- E.g. Print (X);
     when X : I'Class =>
        ...
     when X : T3 =>
        ...
     when T2 | T4 => -- No object declared
        ...
     when T'Class => -- Covers all Object'Tag, no others needed
        ...
   end case;

The expression in the case statement can be either a class-wide object or of the Tag type. In the latter case no object can be specified in the alternatives and others is required.

The rules about the alternatives’ choices:

  • No object declaration is allowed in alternatives having a list of choices (so long we have no anonymous tagged types)
  • If alternative’s choices lists intersect then they must be strict sub/supersets (nested). In the example above I’Class contains T3 and is inequal to T3.
  • Among matching alternatives the alternative corresponding to the most narrow choice is selected (the rule above guarantees its existence)
  • When the union of all choices lists does not cover all argument, others is required.

I think you present a very interesting problem and I would also like to know a solution for it! I am not as knowledgeable in Ada as you are, but… Doesn’t this AdaCore RFC (already implemented in GNAT for a while) help with it? ada-spark-rfcs/features/rfc-pattern-matching.rst at e80f31bf2b4495acbc934e33be4cf48dee4c4873 · AdaCore/ada-spark-rfcs · GitHub As far as I can see, it allows matching objects…

Sorry if this is not helpful or relevant, I am just guessing. Best regards,
Fer

No, it was quite interesting reading. I find it different. First of all I don’t understand how alternatives matching several patterns get selected. Patterns lack ordering and languages generated by patterns are undecidable most of the time. In short forget static sanity checks.

Then as someone who worked with patterns a lot, I formed that opinion of patterns being a bad idea. Look at the examples provided in the AI. Can you tell which cases will match what? Difficult, right? And this is just the entry level. Patterns do not scale.

Even more important to me is that there is no renaming. That is one of two main issues I want to be addressed: getting a narrower type view on the object = dispatch.

Another is safety and maintainability. when new types are added to the hierarchy. You could add new alternatives without much care about the order of checks and let the compiler to figure.

P.S. There is a close case:

if P /= null then
   declare
      X : T renames P.all;
   begin
      ...
   end;
end if;

I do not have a proposal for that.

P.P.S. Concerting patterns, the right solution (I proposed that many years ago) is to make sets a first-class entity. That means:

  • in and not in must become operators. Definable, overridable, primitive just like other operations;
  • Same for x’Range, x..y, x|y. They would return an anonymous set type.
  • Each discrete type T must have T’Set, a corresponding built-in set type.
  • For other types a set type can be introduced by the programmer per aspect or pragma
  • x’Succ, x’Pred, (all first-class valued attributes) must be symbols and operations.

That would simplify loops eliminating most of the horrors of iteration schemas introduced recently. That could also work as “patterns.” And it would simplify the syntax, all that acrobatics defining “choices”, “ranges”, “aspects” etc instead of just having a mere expression.