How to define a variable restricted to take values within a set?

Assume this code:

 package possible_celltypes1_p is new indefinite_ordered_sets (element_type => String);
   possible_celltypes1 : constant possible_celltypes1_p.Set :=
                 ["C4-2B",
                  "HCC1806",
                  "HeLa",
                  "LNCaP",
                  "MDA-MB-468",
                  "PC12 Adh",
                  "PC3-GFP",
                  "RSC95",
                  "RSC96"];

   type celltype1_t is new Unbounded_String;

I want here that variables of type celltype1_t can only take values among the members of possible_celltypes1. How can I do this in the most simple way?

Note that :

  type celltype1_t is new Unbounded_String with
      Dynamic_Predicate => possible_celltypes1.Contains(To_String(celltype1_t));

does not serve since it gives infinite recursion.

Obs: possible_celltypes1 may not be defined as constant.

reinert

A reason not to use an enumeration type?

PC12 Adh is not a valid Ada identifer.

I don’t understand why you would do that. Identifiers and what will be displayed to users are two different things.

Yes, but I just try to avoid double bookkeeping and keep things simple. Assume double bookkeeping of 1000 entities often to be changed.

You can cast the value back down to Unbounded_String to avoid recursion:

type celltype1_t is new Unbounded_String with
        Dynamic_Predicate => possible_celltypes1.Contains(To_String(Unbounded_String(celltype1_t)));

Magick! :clap:

I may need teaspoon explanation.

Several issues here, one is style; I recognize the _t and _p suffix-style from Pascal, but these aren’t nearly as useful as Capitalization_and_Underscores combined with meaningful names. While possible_celltypes is not terrible, celltype1_t is: precisely because it’s not really telling you anything, the 1 makes you ask the question “what’s the difference between 1 and 2?

Nitpicking aside, you can use a subtype easily, or a discriminated type — discriminated type:

With Ada.Strings.Equal_Case_Insensitive;
Package Example is
   Function Validate_Name(Text : String) return Boolean;
   Procedure Add_Name( Value : String )
      with Pre => Validate_Name( Value )
                  or else raise Constraint_Error with "Invalid Name: " & Value;
   -- A value must appear in the set of Names.
   Type Values is limited private;
Private
  Function "="(Left, Right : String) return Boolean
    renames Ada.Strings.Equal_Case_Insensitive;
  Package Name_Set is new indefinite_vectors (element_type => String);
  Internal_Data :  Aliased Name_Set.Vector:= Name_Set.Empty_Vector;

  Type Values( Value_Set : not null access Name_Set.Vector :=
                  Internal_Data
             ) is record
    Internal_Value : Name_Set.Extended_Index;
  end record;
--and so on.
End Example;

For the subtype, yo are doing essentially the same:

  Possible_Types : Constant String_Set.Set; --...
  Subtype String_Value is String
    with Dynamic_Predicate => Possible_Types.Contains( String(String_Value) );

When you declare a derived type, it inherits all of the primitive operations of the base type. So when you made reference to To_String it was to the overriding version for your new type. But since your new type has a dynamic predicate, that version of To_String has to check the predicate, which calls To_String, which has to check the predicate, which calls To_String, which has to check the predicate, and so on.

By casting the input of To_String back to the base type of Unbounded_String, it calls the base type’s version of To_String which doesn’t need to check a predicate as Unbounded_String doesn’t have to check the dynamic predicate, breaking the recursion.

1 Like

Just curious, is that the case because Unbounded_String isn’t tagged? I.e. if it were tagged and To_String hadn’t been overridden then wouldn’t the compiler effectively apply this parent conversion behind the scenes? :thinking:

It probably would have the same effect whether tagged or not (in this specific case). Both tagged an untagged types create type appropriate overrides for their parent type’s operations.

1 Like

Yeah, that seems to be the case, e.g. given this:

type Twins is tagged
      record
         I1, I2 : Natural;
      end record;  
   function Sum (T : Twins) return Natural is (T.I1 + T.I2);

type Odd_Twins is new Twins
     with null record
     with Dynamic_Predicate => (Sum (Odd_Twins) mod 2) = 1;

then infinite recursion is also reported by the compiler. I was thinking that since Sum isn’t overriden then the compiler would set-up a call similar to:

with Dynamic_Predicate => (Sum (Twins (Odd_Twins)) mod 2) = 1;

which removes the recursion warning but that doesn’t seem to be the case (or maybe the predicate evaluation takes place before the conversion?)