0% found this document useful (0 votes)
12 views

Lecture 5

This document discusses types of values and expressions in programming languages. It covers primitive types, composite types, and recursive types. It also discusses static vs dynamic typing and type completeness. Expressions include literals, variables, constructions, function calls, and conditional expressions. Constructions build composite values from components. Function calls apply functions to arguments. Conditional expressions choose a subexpression based on a condition. Implementation involves representing values with bit sequences based on their types.

Uploaded by

Doaa Elsayed
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views

Lecture 5

This document discusses types of values and expressions in programming languages. It covers primitive types, composite types, and recursive types. It also discusses static vs dynamic typing and type completeness. Expressions include literals, variables, constructions, function calls, and conditional expressions. Constructions build composite values from components. Function calls apply functions to arguments. Conditional expressions choose a subexpression based on a condition. Implementation involves representing values with bit sequences based on their types.

Uploaded by

Doaa Elsayed
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 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