Principles of Programming Language - Chapter 6
Principles of Programming Language - Chapter 6
6.1 Introduction
A data type bundles together:
A set of values
Programs work by manipulating data, so it’s crucial that your language’s built-in
types match the real-world concepts in your problem. When they do, your code
is easier to write, read, and update.
Early Fortrans (pre-1990): Only had arrays, so programmers built lists and
trees inside those arrays.
6th chapter 1
3. Documentation (a declaration like CustomerRecord immediately tells you what
data you’re handling)
6.2.1.1 Integer
Stores whole numbers (… –2, –1, 0, 1, 2, …).
Add 1
6.2.1.2 Floating-Point
Models real numbers by storing:
1 sign bit
Exponent
Fraction (mantissa)
6th chapter 2
Because many decimals (e.g., 0.1, √2, e) have no exact binary form, these are
approximate.
6.2.1.3 Complex
Supported by languages like Fortran and Python.
z = 7 + 3j
6.2.1.4 Decimal
Used in business apps (COBOL, C#, F#) where exact decimal arithmetic is
required (e.g., money).
Trade-offs:
A little more storage—6 decimal digits require 24 bits in BCD vs. 20 bits
in pure binary
6th chapter 3
6.2.3 Character Types
Characters are stored as numeric codes
Encoding options:
char in C/Java/C#
Used for: text output/input, labels, file names, user messages, etc.
6th chapter 4
Substring Reference (Slice) – Extract part of a string, e.g., characters 3–5
C / C++ Style
Strings = char arrays, null-terminated ( '\0' )
Java / C# Style
Java: String (immutable), StringBuilder (mutable)
Common methods:
Python Style
Strings = primitive, immutable type
s[2:5] → slice
s + "!" → concatenation
6th chapter 5
Methods: s.find() , s.replace() , s.upper() , etc.
Other Languages
F# – string class, + for concat
Dynamic Length
Grows/shrinks at run-time
6.3.4 Evaluation
Built-in strings → better readability and writability
Mutable string buffers (e.g., StringBuilder ) → more efficient for building large
text
Type name
6th chapter 6
Fixed length
Limited dynamic:
Fully dynamic:
On growth: allocate larger block, copy old text, free old block
Example in C#:
6th chapter 7
If so, how does the compiler distinguish?
Coercion to Integer
C / C++
Methods:
ordinal() : position
toString() : name
C#
6th chapter 8
enum Color { Red, Blue, Green, Yellow };
ML / F#
Swift
enum Day { case Mon, Tue, Wed, Thu, Fri, Sat, Sun }
Scripting Languages
(Perl, Python, Ruby, JS, PHP)
No native enums
Self-documenting code
Cons:
6th chapter 9
Not available in many scripting languages
Subscripted access:
arrayName[index] (C/Java)
Address formula:
Where:
lowerBound : usually 0 or 1
Usually integer
6th chapter 10
When Are Bounds Fixed?
Dimensions
Single: a[0..99]
Multi: m[0..9][0..9]
int a[] = { 4, 5, 7, 83 };
Java / C#:
6th chapter 11
int[] a = { 4, 5, 7, 83 };
Python:
a = [4, 5, 7, 83]
Perl:
@a = (4, 5, 7, 83);
Jagged:
Examples:
6th chapter 12
jagged[0] = new int[5];
6.5.7 Slices
Python
v = [2,4,6,8,10,12]
sub = v[1:4] # [4,6,8]
step = v[0:6:2] # [2,6,10]
Perl
@v = (2,4,6,8,10,12);
@sub = @v[1..4]; # [4,6,8,10]
Ruby
v = [2,4,6,8,10,12]
sub = v.slice(1,3) # [4,6,8]
6.5.8 Evaluation
Arrays are fundamental to programming
6th chapter 13
Access Function (2D Row-Major):
These keys work like unique identifiers that help you look up values.
Languages like Java, C++, C#, and F# support them using built-in
classes.
A hash function converts a key (like a name) into a number to find the value
quickly.
6th chapter 14
A value (can be a string, number, or reference)
❌ Delete an Entry
delete $salaries{"Gary"};
6th chapter 15
📋 Get Keys, Values, and Loop Through Hash
keys %salaries → Returns all keys
🌐 In Other Languages
Python: Associative arrays are called dictionaries
Keys can be any object, like strings, numbers, or even other objects
🧪 PHP Arrays
PHP arrays are flexible:
🧾 Swift Dictionaries
Called dictionaries in Swift
6th chapter 16
✅ When to Use Associative Arrays
Use them when:
- You want to
search values quickly
- You need to
store related data (e.g., name & salary)
Avoid them when:
- You need to
process every item one by one (regular arrays are faster)
As the table grows, more bits are used, which helps it scale smoothly
When expanding, only half the elements need to be moved, saving time
Useful for functions like current() and next() to move through elements in
order
Each piece of data inside a record is called a field, and instead of using
index numbers (like in arrays), we use field names to access them.
For example, to store a student's details like name, student number, and
grade point average:
6th chapter 17
Student number can be an integer,
In records, the fields are stored next to each other (adjacent memory),
even if the sizes are different.
Records have been part of many programming languages since the 1960s,
starting with COBOL.
In C, C++, C#, and Swift, the struct keyword is used to define records.
In C#, structs are special types that are stored on the stack, not the
heap like classes.
In Python and Ruby, you can use hashes (dictionaries) to act like records.
Records use names or identifiers for each field like .Name , .Age .
Also, records can include other structures, like unions (a topic discussed
in Section 6.10).
6th chapter 18
01 EMPLOYEE-RECORD.
02 EMPLOYEE-NAME.
05 FIRST PICTURE IS X(20).
05 Middle PICTURE IS X(10).
05 LAST PICTURE IS X(20).
02 HOURLY-RATE PICTURE IS 99V99.
EMPLOYEE-RECORD contains:
and HOURLY-RATE .
In Java, records are declared using data classes, and nested records are
created using nested classes.
Example:
6th chapter 19
Example:
Employee_Record.Employee_Name.Middle
In dot notation, you start from the outermost record and go to the inner
field.
A fully qualified reference means writing all the enclosing record names
to refer to a specific field.
FIRST
FIRST OF EMPLOYEE-NAME
FIRST OF EMPLOYEE-RECORD
Elliptical references help the programmer write less, but they make the
compiler's job harder and can reduce readability.
6.7.3 Evaluation
Records are widely used and are very useful in programming languages.
Records and arrays are similar, but they are used in different situations:
Arrays are good when all elements are of the same type and are
processed the same way.
And when fields are not processed in the same way or order.
6th chapter 20
In records:
Field names work like fixed index names (called static subscripts).
Be slower,
Conclusion:
Both are efficient and useful depending on what kind of data you're
working with.
But because each field can be a different size, we can’t use array-like
addressing (which expects same-size elements).
Instead, each field is given an offset address, which is the position of the
field relative to the start of the record.
The compiler uses this offset to find the location of each field.
A descriptor is a structure that stores all details about the record, like
field names, types, and offsets.
6th chapter 21
A tuple is a type of data structure that is similar to a record, but with one
big difference: the elements inside a tuple are not given names.
Instead of using field names like Name or GPA , you access tuple elements
by position (like first , second , etc.).
In Python, tuples are immutable, which means you cannot change their
values after you create them.
2. Make changes.
Tuples are useful when you want to protect data from being changed, like
when passing it to a function and making sure the function doesn't modify
it.
Even though tuples are similar to lists in Python, the key difference is:
an integer 3 ,
a float 5.8 ,
You can access a tuple element using brackets and index number:
myTuple[1]
6th chapter 22
This gives the second element of the tuple (in this case 5.8 ), because
Python indexing starts at 0.
In ML, tuples can also mix different data types, just like Python.
an integer,
and a string.
#1(myTuple);
You can also define a new tuple type in ML using a type declaration.
Example:
6th chapter 23
an integer,
The here does not mean multiplication. It just separates the components
and means "a combination of these types."
Tuples in F#
F# also has support for tuples.
Example:
let a, b, c = tup;;
This assigns:
3 to a ,
5 to b ,
7 to c .
When you assign each element of the tuple to a variable like this, it’s called
a tuple pattern.
A tuple pattern is just a way to use multiple variables to hold the values
from a tuple at once.
For tuples with two elements in F#, you can use special functions:
6th chapter 24
Use of Tuples in Programming
In Python, ML, and F#, tuples are often used when a function needs to
return more than one value.
So any changes inside the function don’t affect the original tuple.
This makes tuples a safe way to send grouped data into a function without
risking changes to the original values.
Instead of using field names like Name or GPA , you access tuple elements
by position (like first , second , etc.).
In Python, tuples are immutable, which means you cannot change their
values after you create them.
2. Make changes.
Tuples are useful when you want to protect data from being changed, like
when passing it to a function and making sure the function doesn't modify
6th chapter 25
it.
Even though tuples are similar to lists in Python, the key difference is:
an integer 3 ,
a float 5.8 ,
You can access a tuple element using brackets and index number:
myTuple[1]
This gives the second element of the tuple (in this case 5.8 ), because
Python indexing starts at 0.
In ML, tuples can also mix different data types, just like Python.
6th chapter 26
val myTuple = (3, 5.8, 'apple');
an integer,
and a string.
#1(myTuple);
You can also define a new tuple type in ML using a type declaration.
Example:
an integer,
The here does not mean multiplication. It just separates the components
and means "a combination of these types."
Tuples in F#
F# also has support for tuples.
Example:
6th chapter 27
This creates a tuple with 3 numbers.
let a, b, c = tup;;
This assigns:
3 to a ,
5 to b ,
7 to c .
When you assign each element of the tuple to a variable like this, it’s called
a tuple pattern.
A tuple pattern is just a way to use multiple variables to hold the values
from a tuple at once.
For tuples with two elements in F#, you can use special functions:
So any changes inside the function don’t affect the original tuple.
This makes tuples a safe way to send grouped data into a function without
risking changes to the original values.
6th chapter 28
What Are Lists?
Lists are one of the oldest and most common data structures in
programming.
They were first used in a language called Lisp, the first functional
programming language.
Over time, lists became part of many functional languages (like Scheme,
Common Lisp, ML, F#), and more recently, also became available in
imperative languages like Python and C#.
Example:
(A B C D)
You can also create nested lists (a list inside another list).
Example:
(A (B C) D)
For example:
(A B C)
and C .
6th chapter 29
Breaking Down a List
CAR: Gets the first element of a list.
Example:
Note: The quote ' before the list tells the interpreter to treat it as data,
not as code.
Example:
Building a List
CONS: Adds an element to the beginning of a list.
Example:
Example:
6th chapter 30
Example:
[5, 7, 9]
Creating a List in ML
To add an item to the front of an existing list, use the double colon :: .
Example:
Example:
Example:
hd [5, 7, 9] ; returns 5
tl (short for “tail”) gives the rest of the list without the first element:
Example:
Lists in F#
F# lists are based on ML, but with a few changes.
6th chapter 31
Elements in a list are separated by semicolons instead of commas.
Example:
[1; 3; 5; 7]
The hd and tl functions are still used but written like methods:
Example:
Lists in Python
Python has a built-in list data type that also works like arrays.
Example:
Accessing a value:
Modifying a List
You can update a list item like this:
myList[1] = 6.5
6th chapter 32
To delete an item from a list:
del myList[1]
It comes from the set notation style used in the Haskell language.
Syntax:
Example:
[x * x for x in range(12) if x % 3 == 0]
[n * n | n <- [1..10]]
6th chapter 33
List Comprehensions in F#
F# also supports list comprehensions, and they can be used to create
arrays too.
Example:
It would be helpful if one field in the table could hold any of these
types, instead of making separate fields.
In this case, we use a union to create a variable that can hold either an
int ,a float , or a Boolean , one at a time.
So, we say this variable’s type is the union of these three types.
6th chapter 34
6.10.1 Design Issues
The main design challenge with union types is type checking.
C/C++ Example:
union flexType {
int intEl;
float floatEl;
};
el1.intEl = 27;
float x = el1.floatEl; // No type checking happens here
The compiler doesn’t stop this, even though it’s incorrect — it just uses the
raw bits, which results in nonsense.
Unions like this are called free unions, because the programmer has
complete freedom and no safety (no checks are done).
Discriminated Unions
To make unions safer, some languages include a type indicator, called a
tag or discriminant.
6th chapter 35
Modern languages that support discriminated unions:
ML
Haskell
F#
6.10.3 Unions in F#
In F#, you create a union using the type keyword and | (OR) symbol to list
the possible types.
F# Example:
type intReal =
| IntValue of int
| RealValue of float
To read the value from a union, F# uses pattern matching with the match
keyword.
let a = 7;;
let b = "grape";;
6th chapter 36
| _, "grape" -> "grape"
| _ -> "fruit";;
If we call:
6.10.4 Evaluation
Free unions (like in C/C++) are unsafe, because:
The system can’t tell which type the value actually is,
This is one reason C and C++ are not strongly typed languages.
In contrast, languages like ML, Haskell, and F# have safe union types,
using tags and pattern matching.
6th chapter 37
6.10.5 Implementation of Union Types
When a union is stored in memory:
The memory size given to the union is large enough to hold the biggest
type that could be stored in it.
When variables are created from the heap at runtime, they are called heap-
dynamic variables.
Pointers are different from normal variables (called value types) because:
6th chapter 38
Value types store actual data.
Should the language have only pointer types, only reference types, or
both?
Example in C++:
int *ptr;
int x = 10;
ptr = &x; // Assignment: ptr now holds the address of x
int y = *ptr; // Dereferencing: y gets the value stored at that address (10)
6th chapter 39
Explicit (you write ptr in C/C++), or
*(ptr + 1) == array[1]
Example in C++:
6th chapter 40
Deallocate memory (e.g., free() in C, delete in C++).
int x = 0;
int &ref = x; // ref is a reference to x
ref = 100; // sets x to 100
x and ref are now aliases — both refer to the same memory location.
In C++, references:
Are always implicitly dereferenced (you don’t use to get the value).
6th chapter 41
There are no pointers in Java.
In C#:
On older systems (like early Intel CPUs), an address had two parts:
A segment,
And an offset.
a. Tombstones
6th chapter 42
Add an extra pointer (tombstone) between the actual pointer and the
memory.
When memory is deallocated, the lock and key don’t match anymore.
You couldn’t tell if zero was a real value or just a placeholder for
“nothing.”
C#
F#
6th chapter 43
Swift
Optional Types in C#
In C#, variables are divided into two categories:
Reference Types
Reference types are optional by default.
Value Types
Value types are not optional by default.
But you can make them optional by adding a question mark ( ? ) after the
type.
Example:
if (x == null)
Console.WriteLine("x has no value");
else
Console.WriteLine("The value of x is: {0}", x);
6th chapter 44
Optional Types in Swift
Swift has similar behavior but uses the keyword nil instead of null .
if x == nil {
print("x has no value")
} else {
print("The value of x is: \(x!)")
}
The ! is used to force access to the value if you're sure it's not nil .
Optional types help make programs more flexible and safe, by clearly
distinguishing between variables that have values and those that don’t yet.
6th chapter 45
And the right-hand side (RHS) (like 10 ) is the other operand.
Type checking is the process where the system makes sure the operands
used with an operator are of the right type.
What is Coercion?
Coercion is when the compiler or interpreter automatically changes the
type of a value so it fits correctly.
Example in Java:
int a = 5;
float b = 3.2f;
float c = a + b; // 'a' is coerced to float
Example:
because the compiler did not check whether the types matched.
Examples of Languages:
Static type checking is used in:
Java
C++
C#
Ada
JavaScript
PHP
Which Is Better?
It is better to detect errors at compile time than at runtime because:
Tricks and hacks that bypass type safety are not allowed.
Lead to bugs,
6th chapter 47
Make code harder to read and maintain.
In such cases, even if a language does static type checking for most
variables:
Static type checking = done before running the program (more safe and
preferred).
Dynamic type checking = done while the program runs (more flexible, but
risky).
Languages like C++, even though they use static typing, still can’t catch all
errors at compile time due to union types.
6th chapter 48
Strong typing is now widely accepted as a very important feature in a
programming language.
Simple Definition
A programming language is strongly typed if type errors are always
caught (either at compile time or run time).
For example:
The system must check the actual type before using the variable.
6th chapter 49
So, it's possible to access the wrong type by mistake, and the system
won't stop you.
Even if the type of a function parameter is not known at compile time, the
language ensures that type errors cannot occur.
Why? Because:
If this casting is wrong, it can still cause a type error during runtime.
What is Coercion?
Coercion is the automatic conversion of one type into another when
needed.
Example in Java:
int a = 5;
float b = 3.14f;
float result = a + b; // a is automatically converted to float
While coercion makes coding easier, it also reduces the power of strong
typing.
6th chapter 50
result = a + b;
So, even in a language that seems strongly typed (like Java), coercion can
make it less reliable.
Coercion can weaken strong typing by letting incorrect types slip through.
C and C++ are flexible but unsafe due to unions and unchecked coercion.
6th chapter 51
6.15 Type Equivalence
But for structured types (like arrays, records, and user-defined types),
coercion is rare or not allowed.
Why It Matters:
The rules of type equivalence affect:
You can assign a value from one variable to another, as if they are the
same type.
6th chapter 52
Two Main Approaches to Type Equivalence
There are two ways to define whether two types are equivalent:
❗ Example in Ada:
type Indextype is 1..100;
count : Integer;
index : Indextype;
Here, even though both count and index are just ranges of numbers, they are
not considered equivalent, because they have different names.
Local declarations with the same structure are not accepted — the
compiler checks names, not structure.
Also, name type equivalence only works if all types have names.
6th chapter 53
✅ Pros:
More flexible.
Allows the use of values across different types as long as they look the
same internally.
❌ Cons:
Harder to implement.
The compiler must compare the entire structure of two types — not just
names.
Are two records equal if the fields are the same type but have different
names?
Is an array 0..10 the same as one with 1..11 if both have the same number
of elements?
Enumeration types:
Are they the same if they have the same number of elements, but the
element names are different?
These two types have the same structure, but mean different things.
Structure type equivalence would treat them as the same — which can
cause logical errors in a program.
6th chapter 54
How Ada Handles This
Ada uses a restrictive form of name type equivalence but also provides
subtypes and derived types to solve problems.
❗ Example:
type Celsius is new Float;
type Fahrenheit is new Float;
Even though both are based on Float , they are not equivalent.
A literal value like 3.0 is treated specially — it has a universal real type and
is considered equivalent to any floating-point type.
Subtypes in Ada
A subtype is a type that has restrictions (like a smaller valid range), but is
still equivalent to the original type.
✅ Example:
subtype Small_type is Integer range 0..99;
6th chapter 55
Feature Subtype Derived Type
Even though they look the same, A and B are not equivalent.
They are both anonymous types — the compiler assigns separate internal
types to them.
6th chapter 56
type List_10 is array (1..10) of Integer;
C, D : List_10;
Now C and D share the same type, because they use a named type.
C’s typedef does not create a new type — it just gives a new name to an old
type.
Other Languages
Fortran and COBOL:
Final Thoughts
Type equivalence helps determine:
6th chapter 57
Some languages use strict naming rules, others focus on structure.
Safety
Flexibility
Ease of implementation
Mathematics
Logic
Computer Science
Philosophy
Complicated
Lengthy
In Computer Science
Type theory is used in two main ways:
6th chapter 58
Involves things like:
Combinators
Existential types
Higher-order polymorphism
Every programming language that uses types has its own type system.
A set of types
Check types
6th chapter 59
A type map
A variable name
Example:
x : int
y : float
z : bool
Lexical analyzer
Syntax analyzer
Mathematical Background
A data type can be seen as a set.
6th chapter 60
Union
Cartesian product
Subsets
Finite Mapping
A finite mapping is a function that connects one value to another.
In programming:
Example:
In a simple array:
x is from S1
y is from S2
Example:
S1 = {1, 2}
S2 = {a, b}
S1 × S2 = {(1, a), (1, b), (2, a), (2, b)}
This models:
6th chapter 61
Tuples in Python, ML, Swift, and F#
struct intFloat {
int myInt;
float myFloat;
};
Set Union
The union of two sets includes all elements from both sets.
Subsets
A subset is a part of another set.
In Ada:
Subtypes are like subsets, but must include contiguous values (no
gaps).
Conclusion
This section introduced some basic mathematical models behind data
types.
6th chapter 62
Understanding how compilers work
6th chapter 63