Skip to content

Union types with implicit discriminants #169

@nholsti

Description

@nholsti

The following suggests an alternative solution to the "optional object" extension that is part of the proposal in AI22-0152-1/03, currently being discussed in the ARG. The solution suggested here does not include the automatic memory management (object ownership) part of the proposal in AI22-0152 (but is compatible with it) and is mostly an easier way to create and use the kind of discriminated records with variants that are the current idiomatic approach to optional values in Ada. This solution is not limited to the two cases (object exists, or does not exist) but can provide any number of cases, just as discriminated records can provide any number of variants.

The proposed new kind of type declaration has the form

type T is T1 or T2 or ... or Tn;

where T1 ... Tn are existing subtype names or null, or perhaps preferably, null record. For example:

type Value is Character or Integer or Boolean or null record;

The declaration of type T, above, would be equivalent to the following declaration of a discriminated record type, except that none of the literals and identifiers below that do not appear in the declaration of type T, for example Kind, would visibly exist:

type Kind is (T1_Kind, T2_Kind, ..., Tn_Kind);
type T (K : Kind) is record
   case K is
      when T1_Kind => V1 : T1;
      ...
      when Tn_Kind => Vn : Tn;
   end case;
end record;

If one of the Ti is null record then the corresponding variant is when Ti_Kind => null and the discriminant K would have the default value Ti_Kind (as in AI22-0152). In any case, a default value for objects of type T could be specified with a Default_Value aspect for T whether or not T has a null-record variant.

For brevity types of this new kind are called union types below and the types T1 ... Tn are called the variants of the union type.

Note that the equivalence between a union type and a discriminated record is meant to be only a semantic equivalence. The representation of a union type may not be the same as for the "equivalent" record type, and they would be different types. This allows various optimizations for simple union types, for example using a negative value for the null record case in Positive or null record.

The special case of an "optional object" then corresponds to a type declaration of the form:

type Optional_T is T or null record;

When an object of union type is assigned a value, the expected type of the expression is the union type itself, or any variant of the type. Type qualification can be used to be explicit or to solve ambiguities.

When the value of an object of union type is needed (as an expression operand or actual parameter) there could be two ways to read it, one requiring run-time checks and the other needing only compile-time checks:

  • When an object of union type is used as an expression operand or actual parameter, the expected type of the operand or parameter, if not the union type itself, must be one of the variants of the union type, and a run-time check is applied to verify that this is the current variant of the object. This is the same as a normal discriminant check, but the expected value of the discriminant is deduced from the expected type of the value and not from which discriminant-dependent component is read.
  • Alternatively, a new form of case statement and caseexpression can be used to make the check explicit, as described below.

Since the discriminant (K : Kind) and its literals are not visible, we need a new form of case statement and case expression to "unwrap" union-type values. (This has similarities to the generalized case statement proposed in AI22-0153-1/02.) If V is an object of the union type T, with variants T1 .. Tn, the new case has the form:

case V is
   when V1 : T1 => ...
   ...
   when Vn : Tn => ...
end case;

In each when the declaration of a new name Vi for the value, of this variant type, is optional. The form of the corresponding new case expression is analogous. Since the Vi are to be seen as renamings, there must be restrictions on changes to V within such a casestatement, or restrictions on accessing a Vi after any change to V.

An object of a union type could also be tested for membership in some variant(s), for example

if V in T1 | T4 | null record then ...

If the membership test allows only one variant, it could include a new name, as in the case form:

if V in V1 : T1 then ... (here V1 is of type T1)

This sketch of course omits many things, for example how to deal with visible discriminants on a union type, in addition to the invisible variant discriminant. However, since union types are defined as equivalent to standard discriminated records, those details should not be very difficult to add.

This alternative proposal for optional objects does not include the object-based ownership and automatic memory management that is the actual core of AI22-0152, but I think this alternative proposal is compatible with that larger extension to Ada. The object-based ownership idea does not care how optional objects are introduced into the language, it only says how to understand and implement recursive types where the recursion is through optional objects instead of through access types.

However, for constructing recursive types via optional-type components, without allowing "or null" to be stated ad-hoc on such components (as in AI22-0152), it seems necessary to allow union types with variants of incomplete type, which somewhat breaks the discriminated-record equivalence. For example:

type Tree;
type Optional_Tree is Tree or null record;
type Tree is record
   Data : Element;
   Left, Right : Optional_Tree;
end record;

Probably that Optional_Tree type should also be considered an incomplete type, until it is completed by the completion of the Tree type.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Feature RequestA proposal for a new language feature or capability

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions