Type Systems
 

This section deals with more theoretical aspects of types.

A type system is a set of rules used by a language to structure and organize its collection of types.

We use the term object (or data object) to denote both the storage and the stored value.

The operations defined for a type are the only way to  manipulate its instantiated objects.

A type error is any attempt to manipulate objects with illegal operations.

A program is type safe (or type secure) if it is guaranteed to have no type errors.

 

Static Versus Dynamic Program Checking ( for type errors )

Let's consider errors in general before looking at type errors specifically.

There are two categories of errors

There are two broad categories of error checking based on when the errors are checked for

Strong Typing and Type Checking

The goal of a type system is to prevent the writing of type-unsafe programs as much as possible.

In general, there different ways for a PL to achieve a strong type system.

How should a designer choose a type system when designing a new PL?

Type Compatibility

Consider a PL with an operation, OP, which expects an operand of type T

A strict type system might require that OP may only be legally invoked with a parameter of type T

On the other hand, the PL might define conditions under which an operand of type Q is also acceptable
without violating type safety.

In this case we say that "in the context of operation OP, type Q is compatible with type T".

Type compatibility is sometimes called type conformance or type equivalence.

When this compatibility is precisely defined, the PL can still have a strong type system.

Example: The program fragment in figure 3.8 is written in a hypothetical PL.
What are the effects of different sorts of type compatibility rules?

 

 

Name Compatibility - A strict conformance rule in which a type name is compatible only with itself

Under this rule:

Structural Compatibility - Two types are compatible if they have the same structure

Under this rule:

A few issues with structural compatibility as defined here.

Name Compatibility is:

Name compatibility is often preferable because it prevents two types from being considered
compatible just because their representations happen to be identical.

Practical Issue:

Some PLs adopt the idea of type compatibility, but either poorly define the rules
or leave it entirely up to the implementer.

This results in programs accepted by one compiler and rejected by another compiler

Type Conversions

 

 

Example of coercion

x = x + z;  (in C )

Any coercions which may occur depend on context.

If z is float and x is int:

Explicit Type Conversion

An explicit conversion can be used in some PLs to avoid an undesirable coercion.

For example, C has a cast construct which can force a type conversion that otherwise
might not occcur.

Assuming the same variable types as above, a programmer could write:

x = x + (int) z;

Ada provides only explicit conversions, subject to rules defining allowed conversions.

Advantages of allowing coercions

Disadvantages of allowing coercions

Types and Subtypes

Assume a type T is defined as a set of values with an associated set of operations.

A subtype STof T can be defined to be a subset of those values ( and, for simplicitiy, the same operations )

*note - the discussion here is in the context of conventional PLs. We ignore the ability to
specify user-defined operations for subtypes

If ST is a subtype of T, T is also called ST's supertype (or parent type)


If a PL supports subtypes, it must define:

Example - Pascal

 

 

Generic Types

Consider a generic abstract data type for a stack of elements of parameter type T,
with operations having the following signatures:

push: stack(T) ´ T ® stack(T)

pop: stack(T) ® stack(T) ´ T

length: stack(T)
®int

The operations defined for type stack(T) should work uniformly for any possible type T.

 Since the type is not known, how can the routines be type-checked?

PLs like Ada, C++ and Eiffel support this by instantiating generic types and/or routines at compile-time.

The generic type parameters are bound to concrete types, enabling type-checking.
 - C++ only requires explicit instantiation of generic classes, not routines

Monomorphic versus Polymorphic Type Systems

A statically typed language can provide a strong, simple type system in which every program entity
has a specific type (defined by a declaration), and every operation requires operands of exactly
the sort appearing in the operation definition.

A monomorphic type system is a type system in which every object belongs to one and only one type,
as described above.

A polymorphic type system is a type system in which objects can belong to more than one type.

 

C, Pascal and Ada all deviate from strict monomorphism to some degree.

All practical PLs have some degree of polymorphism, so to differentiate between them
we need to differentiate among the various levels and kinds of polymorphism.

The different facets of  polymorphism can be classifies as shown in figure 3.10.

Let's show how the classification scheme applies in the case of polymorphic functions.

Polymorphic functions are those whose arguments and return values (domain and range)
can belong to more than one type.

 

 

Level 1 - universal vs, ad hoc polymorphism

Level 2 - universal :: parametric vs inclusion

Level 2 - ad hoc :: overloading vs. coercion


 

The Type Structure of Representative Languages

 

The type structure of a PL  is an overall hierarchical classification of the features provided for structuring data.

In order to completely understand the semantics of a PL, this description must be complemented by a precise
understanding of the rules of the type system