# Notion of Subtyping

## Subtypes as Subsets

In systems we have formally studied so far, each expression had exactly one type, such as 'int' or 'int → bool'

- this is not case any more in systems with subtyping

We can often think of a type as approximating the set of values to which an expression can evaluate

- statement corresponds to

What are the sets like?

- in previous systems, distinct types denoted disjoint sets: if then
- in more complex systems, these sets overlap

We write , saying *s is a subtype of t* to correspond to

For function types, we assume that functions are defined on all arguments and that means

There are type systems that have intersections and unions, roughly corresponding to and

## Subtyping as a Syntactic Notion

We treat as a (potentially arbitrary) relation on the set of types, but we aim to define type system such that the following 'weakening' rule holds:

- and

then also

Two ways to ensure the weakening rule:

**First way:** introduce explicitly a general subtyping rule

**Second way:** construct the remaning rules so that the subtyping rule follows as a consequence

In any case, we can use the same general methodology for Proving Safety Properties using Types

- the meaning of type is 'something that makes program type check'
- by soundness, if program type checks, then program has some good properties

## Illustrative Example: Positive and Negative Integer Types

Suppose that we have arbitrary-precision integers denoted by 'int'. Then

Introduce 'pos' with

and 'neg' with

Suppose int,pos,neg are the only types

We have

and

Assuming that are special constructs (and not just variables as in lambda calculus), we have the following rules (fixing some environment ):

for every

for every

for every

Let denote arithmetic expressions.

Some of the rules:

For every type ,

Note that these rules will prevent division by zero

What if we wish to check whether an integer is positive or negative?

- introduce ifpos and ifneg operations:
- (ifpos x y z) evaluates to y if x is positive and to z otherwise
- inside y, we know that x is positive
- here x must be a variable (not a complex expression)

**Example:** Expression

(ifpos x (y / x) (x + 1))

will never cause division by zero or any other arithmetic error.

Moreover, we can type check e.g. operations on fractions represented as a pair (p,q) of integer p and a positive integer q, with the meaning p/q :

def multiplyFractions(p1 : int, q1 : pos, p2 : int, q2 : pos) : (int,pos) { (p1*q1, q1*q2) } def addFractions(p1 : int, q1 : pos, p2 : int, q2 : pos) : (int,pos) { (p1*q2 + p2*q1, q1*q2) } def printApproxValue(p : int, q : pos) = { print(p/q) // no division by zero }

More sophisticated types can track intervals of numbers and ensure that a program does not crash with an *array out of bounds* error.