Subtypes
Introduction to Subtypes
Subtypes
- Type Natural is known as a subtype
- Subtypes are an important part of modern languages
- We will look at subtypes
- of Integers and Classes
- in Ada and Java
Types and Subtypes
- We look at subtypes in the context of a type
- A type consists of two sets:
- Set of values
- Set of operations (that operate on those values)
- Examples:
- Type Integer:
- Values: All 32 bit integers
- Operations: Integer operations such as +, -, *, /, mod, rem, 'first, 'last, IO, :=
- Type Character: values and operations
- Values: All 8 bit characters
- Operations: &, 'pos, 'val, IO, :=
What is a Subtype
- Defintion: A subtype of a type is a subset of the values of a type, with same operations (normally)
- Example: Natural is a subtype of type Integer
- Values: 0 .. largest 32-bit Integer
- Operations: All integer operations (eg :=, +, -, )
Declaring Subtypes in Ada
- Ada Declaration:
-
subtype Natural is Integer range 1 .. Integer'last
- Remember,
Integer'Last
is the largest value in type Integer
- Other examples:
-
subtype Positive is ...
-
subtype Test_Scores is ...
- Subtypes Natural and Positive are defined in package Standard
- Package Standard is automatically available
- Types Character and Boolean are also defined in package Standard
Why Do We Use Subtype Natural?
- In general, use the type that best reflects the values to be stored
- Person reading code will have better understanding of purpose of variable
- Better error checking possible
- Easier to prove properties of programs with more restricted types
- Easier to improve performance if you can prove that a value will only
have certain values
- Example: Counting something
- Use type Natural: value will be 0 or greater
- Attempt to use negative count will result in a Constraint Error
- Assuming that negative counts are not allowed
Type Checking and Subtypes
Subtypes where Parent Types are Expected
- In general, subtype values can be used any place the parent type is expected
- Example:
function sum_abs(x, y: Integer) return Natural is
begin
return abs x + abs y;
end sum_abs;
n: Natural;
i, j: Integer;
...
j := sum_abs(i, n)
Some run time type checking occurs
Parent Types where Subtypes are Expected
- A value of parent type P can can be used where a child type C is expected
- But a runtime error might occur
- Example:
function sum_1_to_n(n: Natural) is
sum: Natural := 0;
begin
for i in 1 .. n loop
sum := sum + i;
end loop;
end sum_1_to_n;
i, s: Integer;
begin
get(n)
s := sum_1_to_n(i); -- Can generate a runtime error
Compiler must generate runtime check for negative value passed to sum_1_to_n
Subtypes Example: Type Checking, Constraint Error
- Let's think about what kind of error checking is required when using
subtypes
- Some errors can't be caught at compile time - they must be caught at
runtime
- But, the compiler must generate the code to catch them at runtime
- Example:
with ada.integer_text_io; use ada.integer_text_io;
procedure foo is
i: Integer;
n1, n2: Natural
begin
get(i);
n1 := i; -- fails if ...
get(n2); -- fails if ...
n1 := n1 - n2; -- fails if ...
i := n2; -- fails if ...
end foo;
Compiler generates code for runtime checks of subtype values
Constraint error raised at runtime if invalid value assigned
Subtypes: Implicit Conversion
- Notice that the compiler does implicit conversion between subtypes
- Examples: What are the types on each side of the assignment
with ada.integer_text_io; use ada.integer_text_io;
procedure foo is
i: Integer;
n1: Natural;
p1: Positive;
begin
get(p1); -- fails if ...
n1 := p1; -- fails if ...
get(n1); -- fails if ...
p1 := n1; -- fails if ...
i := p1;
n1 := i;
end foo;
Ada does implicit type conversion in these cases:
- from parent type to child type
- from child type to parent type
- from child type to child type
Which require runtime checks for valid values?
- What code is generated by the implicit conversion
Notice: Integer get works for subtypes
Subtypes and Strong Typing
- Bottom Line: Catching all possible type errors requires the compiler to
- Do strong type checking at compile time
- Generate code to checks types at runtime
- Strong type checking at compile time: Compiler verifies that operations operate on valid variables
myFloat: float := 3; -- invalid
myVar := 2 + 3.0; -- invalid
- Compiler generates runtime checks to verify variables are assigned only valid values
get(myNat); -- needs runtime check
myPos := myNat; -- needs runtime check
Subtypes and Java
Numeric Subtypes and Java
- Consider byte, short, int, long
- These define a subtype hierarchy
- Java allows a widening conversion without a cast (ie implicit
conversion)
- Java requires a cast for a narrowing conversion (ie explicit
conversion needed)
- Example
i = b; -- Cast not required (but is allowed)
b = (byte) i; -- Cast required. What happens?
What do the widening and narrowing conversions do?
- Widening conversion will not lose information.
- Narrowing conversion can lose information. No exception is thrown
if info is lost.
Class Subtypes and Java
- Where else do we see subtypes in Java?
- Assume we have classes: P, C1 extends P, C2 extends P
P myP, C1 myC1, C2 myC2;
myC1 = new C1();
myC2 = new C2();
// Do the assignments below compile?
// If not, can they be cast to compile correctly?
// Can any of these cause runtime errors?
myP = myC1;
myC1 = myP;
myC2 = myP;
myC1 = myC2;
Which assignment is a widening conversion? Which is a narrowing?
What are Java's rules for compiling subtypes?
- parent = child?
- child = parent?
- child1 = child2?
What does the typecast specify?
Which require runtime checks for valid values?