Type Expressions and Type Constructors

Recap: Type Expressions

In Session 16, we learned that a data type can be basic or constructed. A basic type is one provided as a primitive in the language, such as int, char, or boolean. A constructed type is one created, implicitly or explicitly, out of other types.

In Klein, the basic types are integer and boolean. Klein doesn't have arrays, lists, records, or pointers, so we might think that it has no constructed types. But it is helpful for us to think of the signature of a user-defined function as specifying a type.

For example, the Klein length function has this signature:

function length(n : integer): integer

This signature requires the caller to pass one argument, an integer. It promises that the function will produce an integer as its value, for use in the calling expression. We can say that the length function has this type:

(integer) → integer

In Session 15, we studied is-special.kln, which has several functions with different types. For example, divides has this signature:

function divides(x: integer, n: integer): boolean

Its type is:

(integer, integer) → boolean

count has this signature:

function count(bit: integer, n: integer): integer

and this type:

(integer, integer) → integer

Compilers for functional programming languages such as Haskell and Scala can do amazing things with function types, but even a compiler for Klein can use function types effectively when type checking function bodies and function calls.

Because a type can now be more than just a name, we need to think more generally about type expressions. We will associate a type expression with each language construct that can have a type: identifiers, expressions, and functions.

We saw in Session 16 that we can define the idea of a type expressions more explicitly using an inductive definition:

Klein doesn't have type names, but it does have a constructed type. So let's make sure we understand the idea at a deeper level before moving on to build Klein's type checker.

Type Constructors

Type constructors may sound exotic, but the idea is really pretty simple. Like constructors in OO programming, they simply give us a way to create instances. Type constructors let us create new types out of other types.

Most languages provide a set of basic types, such as integer, float, char, and boolean. They also allow the programmer to construct new types that are aggregates of, or referents to, other types. For example, a Java class is a record type that groups one or more values of other types into a single object. Declaring a class creates a new type of thing in the language.

Different kinds of languages offer different kinds of type constructors. Among the more common of these are:

Array

Given a type expression T and an index set I, array(T, I) is a type expression. In some languages, the index set is limited to the integers, often bounded from below at 1 or 0. For example:

int[] foo = new int[10];

has the type: array([0..9], int).

In other languages, the user can specify any enumeration or ordered collection. For example:

type DAY is (MON, TUE, WED, THU, FRI, SAT, SUN);
Hours : array(DAY) of FLOAT;

has the type: array({MON, TUE, WED, THU, FRI, SAT, SUN}, int)

List

Given a type expression T, list(T) is a type expression. For example, in Racket:

'(1 3 5 7 9)

has the type: list(int)

In many functional languages, this is the fundamental type constructor. Many of my Racket programs include a list-of?(type-predicate) function to use when checking the type of a constructed list.

Tuple

Given type expressions T1 and T2, then (T1, T2) is a type expression. This corresponds to the Cartesian product of the value sets of each type.

For example, in Python:

("name", salary)

is a tuple with type (String, int).

Even in languages that do not have tuples as a data structure, tuple type expressions can be useful in a compiler, as a way of describing other types. An array type is a tuple whose first element specifies an index set and whose second is an arbitrary type. The argument signature of a function include a formal parameter list, which can be described as a tuple of zero or more types.

Pointer

Given a type expression T, then pointer(T) is a type expression. For example:

int *foo;

has the type pointer(int).

Record

A record is similar to a tuple, in that it is the product of other types. However, in a record, each component, called a field, has a name that is also part of the type. For example:

struct account {
  int account_number;
  char *first_name;
  char *last_name;
  float balance;
};

is a record with the type (int=account_number, pointer(char)=first_name, pointer(char)=last_name, float=balance).

Notice that one constructed type can be built using another constructed type.

Function

Given type expressions T1 and T2, then T1→T2 is a type expression. These correspond to the domain and range of a function, respectively.

Consider the Java string concatenation operator, +, which allows expressions such as "World " + "Series". The type of + is (String, String) → String.

The same rule applies to user-defined functions. For example:

int frobnicate(String name, int start ) { ... }

has the type (String, int) → int. This is another of one constructed type being built using another constructed type.

Toward a Klein Type Checker

We need type constructors in our Klein compiler in order to construct the types we use to describe values, variables, and more complex expressions.