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

Types and Subtypes

• We look at subtypes in the context of a type

• A type consists of two sets:
1. Set of values
2. 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 :=, +, -, )

• ` 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
1. Do strong type checking at compile time
2. Generate code to checks types at runtime

1. Strong type checking at compile time: Compiler verifies that operations operate on valid variables
2. ```        myFloat: float := 3;   -- invalid
myVar := 2 + 3.0;      -- invalid
```
3. Compiler generates runtime checks to verify variables are assigned only valid values
4. ```            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?