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.
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.
Just curious, is that the case because Unbounded_String isn’t tagged? I.e. if it were tagged and To_Stringhadn’t been overridden then wouldn’t the compiler effectively apply this parent conversion behind the scenes?
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.
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?)