Non-converting type casts

In C,

r = *((float *) &n);

The pointer to int n is casted to a float, and so when it is dereferenced, you get float r. Only works because of common implementation decisions that may not always hold. If float requires more bits than int, the result will usually be undefined.

Java

Has both explicit (cast) and implicit conversion cast.

C

Losing precision by casting from a type with more bits to less bits (e.g., double to float) doesn’t trigger a dynamic semantic error, but does cause undefined behavior. Also, going from a type with less bits to more bits generally involves sign-extending.

Overloading vs Coercion vs Polymorphism

All three can be used to achieve a similar effect. An overloaded name can refer to more than one object. If the addition operation should accept two integers and two floating point numbers, an implementer can define two add operations that expect those argument types. Coercion is when a language allows a value of one type to be used in a context that expecdts another. If an integer and float is pass to the add op, a language can coerce the integer into a float.

Polymorphism likewise applies to code that is designed to work with multiple types. However, the types must generally have certain characteristics in common, and the code captures this commonality. For example, explicit parametric polymorphism (generics in C++).

Universal reference types

Most OO languages provide a tag to objects to identify their types at runtime. In Java, C#, this is a regular type cast, and in C++ there is dynamic_cast for this.

Example:

Object x = new Integer(3);
Integer y = (Integer)x;

is legal, but x requires a tag to ensure it is castable to Integer.

Type inference

Types of expressions must be inferred, e.g., if adding to subrange types, is the output a subrange? Just an integer? May require dynamic semantic check.

Declarations

Goal is to reduce burden on programmer.

In C++, auto and decltype. decltype matches type of a given expression, e.g.,

template <typename A, typename B>
A a; B b;
decltype(a+b) sum;

Hindley-Milner type inference

H-M inference determines types of names without explicitly giving the types. The translator looks at uses of name, and infers the type (or indicate error if uses are incompatible).

Seen in ML and OCaml. ML and OCaml are popular strongly typed functional programming languages. OCaml is ML with objects.

Allows types to be as general as possible. It captures what is needed for type consistency, but the concrete type could be anything.

Unification

H-M type inference implemented with an algorithm that performs unification.

Rules:

  • Any type variable unifies with any type expression
  • Any two type constants (int, double, etc) unify only if they are the same
  • Any two type constructors (array, record, etc) unify only if they are of the same type constructor and all of their component types also unify

Composite types - Records

Scott Ch. 8

Introduced in Cobol. Usually consists of simpler types.

Records usually laid out contiguously. “Alignment” is important in systems programming; programmers may want to arrange fields to keep certain values in the same cache line. The alignment can cause “holes”, but re-arranging to get rid of holes can mess up the cache lines.

Some languages (Pascal) allows records to be packed, i.e., optimized for space instead of speed. Access requires multiple instructions, but no holes in memory.

When declaring the components of a record, declare items big to small or vice versa to pack things “nicely” without having to think too much about it.