You are on page 1of 27

2

Values and Types


Part 4
▪ Types of values.
▪ Primitive, composite, recursive types.
▪ Type systems: static vs dynamic typing, type completeness.
▪ Expressions.
▪ Implementation notes.
2-1
Expressions

▪ An expression is a PL construct that may be evaluated to


yield a value.
▪ Forms of expressions:
• literals (trivial)
• constant/variable accesses (trivial)
• constructions
• function calls
• conditional expressions
• iterative expressions.

2-2
Constructions

▪ A construction is an expression that constructs a


composite value from its component values.
▪ In C, the component values are restricted to be literals. In
Ada, Java, and Haskell, the component values are
computed by evaluating subexpressions.

2-3
Example: Java object constructions

▪ Assume:
class Date {
public int m, d;
public Date (int m, int d) {
this.m = m; this.d = d;
}

}

▪ Object constructions:
Date today = new Date(12, 25);
Date tomorrow =
new Date(today.m, today.d+1);

2-4
Example: Ada record and array
constructions
▪ Record constructions:
type Date is record
m: Month;
d: Day_Number;
end record;
today: Date := (Dec, 25);
tomorrow: Date := (today.m, today.d+1);

▪ Array construction:
leap: Integer range 0 .. 1;

month_length: array (Month) of Integer :=
(31, 28+leap, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31);
2-5
Example: Haskell tuple and list constructions

▪ Tuple constructions:
today = (Dec, 25)
m, d = today
tomorrow = (m, d+1)

▪ List construction:
monthLengths =
[31, if isLeap y then 29 else 28,
31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

2-6
Function calls (1)

▪ A function call computes a result by applying a function to


some arguments.
▪ If the function has a single argument, a function call
typically has the form “F(E)”, or just “F E”, where F
determines the function to be applied, and the expression E
is evaluated to determine the argument.
▪ In most PLs, F is just the identifier of a specific function.
However, in PLs where functions as first-class values, F
may be any expression yielding a function. E.g., this
Haskell function call:
(if … then sin else cos)(x)
2-7
Function calls (2)

▪ If a function has n parameters, the function call typically


has the form “F(E1, …, En )”. We can view this function
call as passing a single argument that is an n-tuple.

2-8
Function calls (3)

▪ An operator may be thought of as denoting a function.


▪ Applying a unary operator to its operand is essentially a
function call with one argument:
 E is essentially equivalent to (E)

▪ Applying a binary operator to its operands is essentially


a function call with two arguments:
E1  E2 is essentially equivalent to (E1, E2)

▪ Thus a conventional arithmetic expression is essentially


equivalent to a composition of function calls:
a * b + c / d is essentially equivalent to
+(*(a, b), /(c, d))

2-9
Conditional expressions

▪ A conditional expression chooses one of its


subexpressions to evaluate, depending on a condition.
▪ An if-expression chooses from two subexpressions, using
a boolean condition.
▪ A case-expression chooses from several subexpressions.
▪ Conditional expressions are commonplace in functional
PLs, but less common in imperative/OO PLs.

2-10
Example: Java if-expressions

▪ Java if-expression:
x>y ? x : y

▪ Conditional expressions tend to be more elegant than


conditional commands. Compare:
int max1 (int x, int y) {
return (x>y ? x : y);
}
int max2 (int x, int y) {
if (x>y)
return x;
else
return y;
}

2-11
Example: Haskell if- and case-expressions

▪ Haskell if-expression:
if x>y then x else y

▪ Haskell case-expression:
case m of
feb -> if isLeap y then 29 else 28
apr -> 30
jun -> 30
sep -> 30
nov -> 30
_ -> 31

2-12
Implementation notes

▪ Values and types are mathematical abstractions.


▪ In a computer, each value is represented by a bit sequence
stored in one or more bytes or words.
▪ Important principle: all values of the same type must be
represented in a uniform way. (But values of different
types can be represented in different ways.)
▪ Sometimes the representation of a type is PL-defined (e.g.,
Java primitive types).
▪ More commonly, the representation is implementation-
defined, i.e., chosen by the compiler.

2-13
Representation of primitive types (1)

▪ Each primitive type T is typically represented by single or


multiple bytes: usually 8 bits, 16 bits, 32 bits, or 64 bits.
▪ The choice of representation is constrained by the type’s
cardinality, #T:
• With n bits we can represent at most 2n different values.
• So the smallest possible representation is log2(#T) bits.

2-14
Representation of primitive types (2)

▪ Booleans can in principle be represented by a single bit (0


for false and 1 for true). In practice, the compiler is likely
to choose a whole byte.
▪ Characters have a representation determined by the
character set:
• ASCII or ISO-Latin characters have an 8-bit representation
• Unicode characters have a 16-bit representation.

▪ Enumerands are typically represented by unsigned


integers starting from 0.
• E.g., the enumerands of type Month above would be represented
by the integers {0, …, 11}. The representation must have at least 4
bits. In practice the compiler is likely to choose a whole byte.
2-15
Representation of primitive types (3)

▪ Integers have a representation influenced by the desired


range. Assuming two’s complement representation, in n
bits we can represent the integers {–2n–1, …, 2n–1–1}:
• In a PL where the compiler gets to choose the number of bits n,
from that we can deduce the range of integers.
• In a PL where the programmer defines the range of integers, the
compiler must use that range to determine the minimum n. E.g., if
the range is {0, …, 1010}, the representation must have at least 35
bits. In practice the compiler is likely to choose 64 bits.

▪ Real numbers have a representation influenced by the


desired range and precision. Nowadays most compilers
adopt the IEEE floating-point standard (either 32 or 64
bits).
2-16
Representation of Cartesian products

▪ Tuples, records, and structures are represented by


juxtaposing the components in a fixed order.
▪ Example (Ada):
type Date is record
y 2000 2004
y: Year_Number;
m: Month; m jan dec
d: Day_Number; d 1 25
end record;

▪ Implementation of component selection:


• Let r be a record or structure.
• Each component r.f has a fixed offset (determined by the
compiler) relative to the base address of r.
2-17
Representation of arrays (1)

▪ The values of an array type are represented by juxtaposing


the components in ascending order of indices.
▪ Example (Ada):
type Vector is array (1 .. 3) of Float;
1 3.0 1.0
2 4.0 1.0
3 0.0 0.5

2-18
Representation of arrays (2)

▪ Implementation of array indexing:


• Let a be an array with index range {l, …, u}.
• Assume that each component occupies s bytes (determined by the
compiler).
• Then a(i) has offset s(i–l) bytes relative to the base address of a.
(In C and Java l = 0, so this simplifies to si bytes.)
• The offset computation must be done at run-time (since the value
of i is not known until run-time).
• A range check must also be done at run-time, to ensure that
l  i  u.

2-19
Representation of objects (simplified)

▪ Example (Java):
class Point {
Point tag
private float x, y;
… // methods 1.0 x
}
Circle tag 2.0 y
class Circle
extends Point { 0.0 x
private float r; Rect. tag y
… // methods 0.0
} 1.5 x
5.0 r
class Rectangle 2.0 y
extends Point {
private float w, h; 3.0 w
… // methods
} 4.0 h

2-20
Representation of disjoint unions (1)

▪ Each value of a disjoint-union type is represented by


juxtaposing a tag with one of the possible variants. The
type (and therefore representation) of the variant depends
on the current value of the tag.
▪ Example (Haskell):
data Number = Exact Int | Inexact Float

tag Exact tag Inexact


variant 2 variant 3.1416

2-21
Representation of disjoint unions (2)

▪ Example (Ada):
type Accuracy is (exact, inexact);
type Number (acc: Accuracy := exact) is
record
case acc of
when exact => ival: Integer;
when inexact => rval: Float;
end case;
end record;
acc exact acc inexact
ival 2 rval 3.1416

2-22
Representation of disjoint unions (3)

▪ Example (Ada):
type Form is (pointy,circular,rectangular);
type Figure (f: Form := pointy) is record
x, y: Float;
case f is
when pointy => null;
when circular => r: Float;
when rectangular => w, h: Float;
end case; f pointy f circ. f rect.
end record;
x 1.0 x 0.0 x 1.5

y 2.0 y 0.0 y 2.0


r 5.0 w 3.0
h 4.0 2-23
Representation of disjoint unions (4)

▪ Implementation of tag test and projection:


• Let u be a disjoint-union value/object.
• The tag of u has an offset of 0 relative to the base of u.
• Each variant of u has a fixed offset (determined by the compiler)
relative to the base of u.

2-24
Representation of recursive types (1)

▪ Each value of a recursive type is represented by a pointer


(whether the PL has explicit pointers or not).
▪ Example (Ada):
type IntList;
type IntNode is record
data: Integer;
tail: IntList;
end record;
type IntList is access IntNode;

2 3 5 7 data
tail

2-25
Representation of recursive types (2)

▪ Example (Haskell):
data IntList = Nil | Cons Int IntList

Cons Cons Cons Cons Nil


2 3 5 7

2-26
Representation of recursive types (3)

▪ Example (Java):
class IntList {
public int data;
public IntList tail;

}

IntList IntList IntList IntList tag


2 3 5 7 data
tail

2-27

You might also like