======= 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).