======= LSN009.TagElem ======= !topic LSN on Enumeration Extension and Tagged Elementary Types !reference MD-3.4*;2.0 !from Tucker Taft 91-06-05 !discussion For the record, here is an LSN discussing Enumeration Extension and tagged elementary types. Our current thinking about simplifying OOP has us dropping these features, but here are some of the pros and cons. ENUMERATION EXTENSION Enumeration extension was one of the first OOP proposals we made. It is very straightforward to describe and implement (ignoring tagging for now). The question is it worth the cost, and how does it fit with the rest of OOP. Here are some examples of use: -- This example is drawn from the SQL/Ada work type Boolean_With_Unknown is new Boolean with (Unknown); -- A three-valued logic Complement : constant array(Boolean_With_Unknown) of Boolean_With_Unknown := (False => True, True => False, Unknown => Unknown); function "not"(Right : Boolean_With_Unknown) return Boolean_With_Unknown renames (Complement(Right)); And_Table : constant array(Boolean_With_Unknown, Boolean_With_Unknown) of Boolean_With_Unknown := (False => (False, False, False), True => (False, True, Unknown), Unknown => (False, Unknown, Unknown) ); function "and"(Left, Right : Boolean_With_Unknown) return Boolean_With_Unknown renames (And_Table(Left, Right)); etc... Null_Value_Error : exception; type Integer_With_Null is private; Null_Value : constant Integer_With_Null; type Value(Int : Integer_With_Null) return Integer; -- raises Null_Value_Error if Int is Null function "="(Left, Right : Integer_With_Null) return Boolean_With_Unknown; function "<"(Left, Right : Integer_With_Null) return Boolean_With_Unknown; function ">="(Left, Right : Integer_With_Null) return Boolean_With_Unknown; -- These all return Unknown if either Left or Right is Null. etc... X, Y : Integer_With_Null; if X < Y then -- X is definitely less than Y elsif X >= Y then -- X is definitely greater than Y else -- X or Y is a Null (i.e. unspecified) value end if; This seems like a pretty natural extension of boolean, and the fact that the extended boolean is usable in "if" statements makes it all the more elegant. This is true because the "if" construct is already a boolean class-wide operation. =============== Here is an example of tagged enumeration extension, to implement error codes: type Error_Enum is tagged (No_Error); subtype Error_Code is Error_Enum'CLASS; -- A global error code is defined in a distributed fashion Last_Error : Error_Code := No_Error; -- This keeps track of the last error code. . . . type Semantic_Error_Enum is new Error_Enum with (Undefined_ID, Unresolvable_Aggregate, Ambiguous_ID, . . .); -- Define the errors unique to the Semantic Phase. . . . -- somewhere in the semantic phase if ident-is-undefined then Last_Error := Undefined_ID; return; end if; . . . ============== As these examples indicate, enumeration extension is useful in at least two contexts: 1) if there are existing class-wide operations on the enumeration class (e.g. "if" statements use boolean) which can be generalized to be useful on the extended type. This does not require tagging. 2) the type is tagged, allowing instances of the universal type to represent uniquely any enumeration literal declared in any extension. This provides a clean and natural way to define unique error codes, etc., in a distributed fashion Here are some of the disadvantages/complexities associated with enumeration extension: 3) The universal type if untagged cannot represent all extensions, and instead Constraint_Error may be raised on conversion to the universal untagged type. 4) The universal type if tagged cannot be used in contexts where a discrete type is required (e.g. as an array index, or a for loop). 5) The ordering relations are not generally what the extender may want. That is, the new enumeration literals might more naturally appear in the middle of the ordered sequence. This can be worked around by defining an explicit "Pos" function rather than relying on 'POS, as well as overriding the comparison operators. TAGGED ELEMENTARY TYPES MD 2.0 proposes that tagging be a general capability, usable for both elementary types and composite types. The example above indicates the use for tagged enumeration types. Here is an example of use of tagged numeric types, to associate units with floating point types. type Units_Enum is (Meters, Kilograms, Seconds); type Unit_Power is range -3..3; -- A reasonable range of units type Units_Vector is array(Units_Enum) of Units_Power; -- A units vector gives the power for each unit type Float_With_Units is tagged new Float; No_Mapping : constant Float_With_Units := 0.0; Units_Map : array(Unit_Power, Unit_Power, Unit_Power) of Float_With_Units'CLASS := (others => (others => (others => No_Mapping); -- This map records an instance of the particular type -- which has this combination of units function Float_With_Units'Units return Units_Vector is <>; -- Each derivative of Float_With_Units must define this function -- This function is a dispatching function on the universal type function "*"(Left, Right : Float_With_Units'CLASS) return Float_With_Units'CLASS; -- This is the general purpose multiply which does the right -- thing with units -- . . . Similar things possible for "/" and "**" ... Duplicate_Units : exception; -- Raised if two types with the same units are registered in the map Undefined_Units : exception; -- Raised if a result returns a units combination that is not in the map generic type New_Float_Type is new Float_With_Units; package Register_Float_Type is end; -- This generic registers each float type with units for -- implementing multiply. . . . package body Register_Float_Type is Unit_Values : constant Units_Vector := New_Float_Type'Units; begin -- Check if this combination of units is already in the -- units map if Units_Map(Unit_Values(Meters), Unit_Values(Kilograms), Unit_Values(Seconds)) /= No_Mapping then -- Oops, duplicate raise Duplicate_Units; end if; -- Record this type in the units map Units_Map(Unit_Values(Meters), Unit_Values(Kilograms), Unit_Values(Seconds)) := New_Float_Type'(0.0); end Register_Float_Type; function "*"(Left, Right : Float_With_Units'CLASS) return Float_With_Units'CLASS is -- This is the general purpose multiply which does the right -- thing with units Result_Units : Units_Vector; Result : Float_With_Units'CLASS; begin -- Calculate result units for U in Units_Enum loop Result_Units(U) := Left'Units(E) + Right'Units(E); end loop; -- Pull out a value of the appropriate type Result := Units_Map(Result_Units(Meters), Result_Units(Kilograms), Result_Units(Seconds)); if Result = No_Mapping then -- Oops, no mapping for this units combination raise Undefined_Units; end if; -- Add a zero of the "appropriate" tag to the result of -- multiplying left and right, to get the tag inserted -- correctly on conversion to the universal type. return Result + Float_With_Units'CLASS(Float(Left) * Float(Right)); end "*"; Given this example, one can predict places where tagged elementary types could be useful: 1) Where the elementary type has some additional attributes (like Units) which affect the result/meaningfulness of certain class-wide operations. However, as this example illustrates, it is difficult to construct a value of the appropriate tag based on computed attributes (like the sum of the units of the operands to multiply). There are a number of complexities associated with tagged elementary types: 2) Elementary types are generally special-cased in representation, parameter passing, arithmetic manipulation, etc. to make best use of the hardware instructions, registers, stack operations, etc. By adding the possibility of tagging to all elementary types we could effectively double the number of special cases needed. 3) The tag of a tagged universal elementary type is not constant. This distinguishes it from tagged universal composite types, and adds to the complexity of describing the rules for dispatching operations (especially with OUT parameters). Of course, the fact that the tag is variable is what makes tagged enumeration 4) Elementary types have literals. This makes the somewhat tricky rules for assigning tags to parameterless operations (like literals) more of a common concern, rather than a minor issue (see MD 3.4.4).