2
Values and Types
Part 3
▪ Types of values.
▪ Primitive, composite, recursive types.
▪ Type systems: static vs dynamic typing, type completeness.
▪ Expressions.
▪ Implementation notes.
2-1
Recursive types
▪ A recursive type is one defined in terms of itself.
▪ Examples of recursive types:
• lists
• trees
2-2
Lists (1)
▪ A list is a sequence of 0 or more component values.
▪ The length of a list is its number of components. The
empty list has no components.
▪ A non-empty list consists of a head (its first component)
and a tail (all but its first component).
▪ A list is homogeneous if all its components are of the
same type. Otherwise it is heterogeneous.
2-3
Lists (2)
▪ Typical list operations:
• length
• emptiness test
• head selection
• tail selection
• concatenation.
2-4
Lists (3)
▪ For example, an integer-list may be defined recursively to
be either empty or a pair consisting of an integer (its head)
and a further integer-list (its tail):
Integer-List = nil Unit + cons(Integer Integer-List)
or Integer-List = { nil } { cons(i, l) | i Integer; l Integer-List }
where Unit is a type with only one (empty) value.
▪ Solution:
Integer-List = { nil }
{ cons(i, nil) | i Integer }
{ cons(i, cons(j, nil)) | i, j Integer }
{ cons(i, cons(j, cons(k, nil))) | i, j, k Integer }
…
2-5
Example: Java lists (1)
▪ Class declarations for integer-lists:
class IntList {
public int data;
public IntList tail; recursive
public IntList (int d, IntList t) {
data = d; tail = t;
}
}
▪ An integer-list construction:
new IntList(2,
new IntList(3,
new IntList(5,
new IntList(7, null)))));
2-6
Example: Java lists (2)
▪ Class declarations for object-lists:
class List {
public Object data;
public List tail;
public List (Object d, IntList t) {
data = d; tail = t;
}
}
▪ Note that List objects are heterogeneous lists (since
data can refer to an object of any class).
▪ By contrast, IntList objects are homogeneous lists.
2-7
Example: Haskell lists
▪ Type declaration for integer-lists:
data IntList = Nil | Cons Int IntList
recursive
▪ Some IntList constructions:
Nil
Cons 2 (Cons 3 (Cons 5 (Cons 7 Nil)))
▪ Actually, Haskell has built-in list types:
[Int] [String] [[Int]]
▪ Some list constructions:
[] [2,3,5,7] ["cat","dog"] [[1],[2,3]]
2-8
Example: Ada lists
▪ Type declarations for integer-lists:
type IntNode;
type IntList is access IntNode;
type IntNode is record
mutually
data: Integer;
recursive
tail: IntList;
end record;
▪ An IntList construction:
new IntNode'(2,
new IntNode'(3,
new IntNode'(5,
new IntNode'(7, null)))
2-9
Strings
▪ A string is a sequence of 0 or more characters.
▪ Some PLs (ML, Python) treat strings as primitive.
▪ Haskell treats strings as lists of characters. Strings are thus
equipped with general list operations (length, head
selection, tail selection, concatenation, …).
▪ Ada treats strings as arrays of characters. Strings are thus
equipped with general array operations (length, indexing,
slicing, concatenation, …).
▪ Java treats strings as objects, of class String.
2-10
Static vs dynamic typing (1)
▪ Before any operation is performed, its operands must be
type-checked to prevent a type error. E.g.:
• mod operation: check that both operands are integers
• and operation: check that both operands are booleans
• indexing operation: check that the left operand is an array, and that
the right operand is a value of the array’s index type.
2-11
Static vs dynamic typing (2)
▪ In a statically typed PL:
• all variables and expressions have fixed types
(either stated by the programmer or inferred by the compiler)
• all operands are type-checked at compile-time.
▪ Most PLs are statically typed, including Ada, C, C++, Java,
Haskell.
2-12
Static vs dynamic typing (3)
▪ In a dynamically typed PL:
• values have fixed types, but variables and expressions do not
• operands must be type-checked when they are computed at run-
time.
▪ Some PLs and many scripting languages are dynamically
typed, including Smalltalk, Lisp, Prolog, Perl, Python.
2-13
Example: Python dynamic typing (1)
▪ Python function definition: The type of n is unknown.
def even (n): So the “%” (mod) operation
return (n % 2 == 0) must be protected by a run-
time type check.
▪ The types of variables and parameters are not declared, and
cannot be inferred by the Python compiler. So run-time
type checks are needed to detect type errors.
2-14
Example: Python dynamic typing (2)
▪ Python function definition:
def respond (prompt):
# Print prompt and return the user’s response,
# as an integer if possible, otherwise as a string.
try:
response = raw_input(prompt) yields a string
return int(response)
except ValueError: converts the string to an
return response integer, or throws
ValueError if impossible
▪ Application code:
m = respond("Month? ")
if m == "Jan": m = 1
elif m == "Feb": m = 2
2-15
Example: Ada static typing
▪ Ada function definition:
The compiler doesn’t
function is_even (n: Integer) know the value of n.
return Boolean is But, knowing that n’s
begin type is Integer, it infers
return (n mod 2 = 0); that the type of “n mod
end; 2 = 0” will be Boolean.
▪ Call: The compiler doesn’t know the
p: Integer; value of p. But, knowing that p’s
… type is Integer, it infers that the
if is_even(p+1) … type of “p+1” will be Integer.
▪ Even without knowing the values of variables and
parameters, the Ada compiler can guarantee that no type
errors will happen at run-time.
2-16
Static vs dynamic typing (4)
▪ Advantages and disadvantages of static and dynamic typing:
• Static typing is more efficient.
– Dynamic typing requires run-time type checks (which make
the program run slower), and forces all values to be tagged
(to make the type checks possible).
– Static typing requires only compile-time type checks, and
does not force values to be tagged.
• Static typing is more secure: the compiler can guarantee
that the object program contains no type errors. Dynamic
typing provides no such security.
• Dynamic typing is more flexible. This is needed by some
applications where the types of the data are not known in
2-17
advance.
Type completeness (1)
▪ In principle, a value of any type can be:
• assigned
• composed with other values (as components of composite values)
• passed as an argument (to a procedure or function)
• returned as a function result.
▪ But some (mainly older) PLs restrict which of these
operations are applicable to certain types of values.
2-18
Example: type completeness (1)
▪ Ada function and application code:
type Complex is
record x, y: Float; end record;
function sum (c1, c2: Complex)
return Complex is
begin
return (c1.x+c2.x, c1.y+c2.y);
end;
-- Print the complex sum of p, q, and r:
put(sum(sum(p, q), r));
2-19
Example: type completeness (2)
▪ What if Ada function results were restricted to primitive
values?
procedure add (c1, c2: in Complex;
c3: out Complex) is
begin
c3 := (c1.x+c2.x, c1.y+c2.y);
end;
-- Print the complex sum of p, q, and r:
declare
t1, t2: Complex;
begin
add(p, q, t1);
add(t1, r, t2);
put(t2);
end;
2-20