CS403 Lectures

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 328

CS 403: A Brief (and Pretty Incomplete) History of

Programming Languages

Stefan D. Bruda

Fall 2021
H ISTORY OF PROGRAMMING LANGUAGES

“Prehistory”
The 1940s: von Neumann and Zuse
The 1950s: The first programming language
The 1960s: An explosion in programming languages
The 1970s: Simplicity, abstraction, study
The 1980s: Consolidation and new directions
The 1990s: The explosion of the World Wide Web
The 21st century

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 1 / 21
P REHYSTORY

Cuneiform writing used in the Babylon, founded by Hammurabi around


1790 BC
poems, stories, contracts, records, astronomy, math

Famous Babylonian math


tablet (Plimpton 322)
involving Pythagorean
triples, a2 + b2 = c 2 – with
a mistake! (or bug)

Weird math (base 60!)


two characters to express a (base-60) digit
decimal point not specified (must be figured out from context)

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 2 / 21
W RITTEN LANGUAGE TO DESCRIBE COMPUTATIONAL
PROCEDURES

A cistern.
The length equals the height.
A certain volume of dirt has been excavated.
The cross-sectional area plus this volume comes to 110.
The length is 30. What is the width?
You should multiply the length, 30, by . . .
— Translation by Donald Knuth

No variables
Instead, numbers serve as a running example of the procedure being
described
“This is the procedure”
Programming is among the earliest uses to which written language was
put
Programming languages design has tried to get as close to that as possible
from the very beginning. . .

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 3 / 21
A LGORITHMS

Abū ’Abdallāh Muh.ammad ibn Mūsā al-Khwārizmı̄, or Mohammed


Al-Khorezmi for short (Baghdad, 780–850 BC)
One little book: “The Compendious Book on Calculation by Completion and
Balancing”
Compilation and extension of known rules for solving quadratic equations and
other problems
Used as a mathematics text in Europe for eight hundred years
The book is considered the foundation of algebra
Invention of the notions of algorithms and data structures
Other early algorithms:
Euclid (300 BC): an algorithm for computing the GCD of two numbers
Eratosthenes (about same time): one of the most efficient algorithms for
finding small primes (the sieve of Eratosthenes)
Alexander de Villa Dei (1220 AD): Canto de Algorismo = algorithms in Latin
verse
Natural language (even poetry!) plus math rather than programming
languages

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 4 / 21
T HE FIRST PROGRAMMING ENVIRONMENTS

Jacquard loom (early 1800s) translated


card patterns into cloth designs
Charles Babbage’s Analytical Engine
(1830s & 40s)
First programmer: Augusta Ada King,
Countess of Lovelace (today commonly
known as Ada Lovelace)
The engine can arrange and combine
its numerical quantities exactly as if they
were letters or any other general symbols;
and in fact might bring out its results in al-
gebraic notation, were provision made.
Programs were punched cards containing
data and operations

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 5 / 21
T HE 1940 S : VON N EUMANN AND Z USE
Harvard Mark I (1943) – Howard Aiken (IBM), Grace Hopper (Navy) →
first electro-mechanical computer
Harvard Mark II: First computer bug
ENIAC (1946) – Presper Eckert, John Mauchly (U. Penn.) → First
electronic computer
Programming was manual, with switches and cables
John von Neumann led a team that built computers with stored programs
and a central processor (as we know them today)
Konrad Zuse designed the first programming language as we know it
(Plankalkul = program calculus)
In Germany, in isolation because of the war; work finally published in 1972
Advanced data type features: floating point, arrays, records
Invariants for correctness
Rather cumbersome notation
5 ∗ B ⇒ A
A[7] := 5 × B[6] → V 6 7 (subscripts)
S 1.n 1.n (data types)

Never implemented
CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 6 / 21
T HE FIRST COMPUTER BUG !

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 7 / 21
T HE 1950 S : T HE FIRST PROGRAMMING LANGUAGES

FORTRAN (1957, John Backus)


FORmula TRANslator – designed for scientific programming
Many new features over time: FORTRAN II, FORTRAN IV, FORTRAN 66,
FORTRAN 77 (structured programs, char’s), Fortran 90 (arrays, modules),
Fortran 2003 (objects), Fortran 2008 (concurrent programming)
Very efficient compilation into fast machine code
COBOL (1960, Grace Hopper)
mathematical programs should be written in mathematical notation,
data processing programs should be written in English statements —
G. Hopper, 1953
Committee sponsored by US Department of Defence
Biggest contribution was the idea that programs should be written in a way
that is easily understood
Adopted widely by businesses for record-keeping applications
Record structure, separation of data from execution part, versatile formatting
of output using “pictures”
ANSI standards (1968, 1974, 1985)

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 8 / 21
FORTRAN EXAMPLE

IMPLICIT INTEGER (A-Z)


DIMENSION ORD(N),POPLST(2,20)
INTEGER X,XX,Z,ZZ,Y
INTEGER A(N)
NDEEP=0
U1=N
L1=1
DO 1 I=1,N
1 ORD(I)=I
2 IF (U1.LE.L1) RETURN
3 L=L1
U=U1
4 P=L
Q=U
X=A(ORD(P))
Z=A(ORD(Q))
IF (X.LE.Z) GO TO 5 ....
5
CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 9 / 21
COBOL EXAMPLE
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE-COMPUTER. IBM-4381.
OBJECT-COMPUTER. IBM-4381.

DATA DIVISION.
WORKING-STORAGE SECTION.
01 INPUT-FIELD.
05 INPUT-VALUE PIC 99 VALUE ZERO.
01 CALCULATION-FIELD.
05 SUM-VALUE PIC 9(03) VALUE ZERO.
05 AVERAGE-VALUE PIC 9(03)V99 VALUE ZERO.
01 OUTPUT-FIELD.
05 EDIT-FIELD PIC ZZ9.99 VALUE ZERO.

PROCEDURE DIVISION.
1000-MAIN.
PERFORM 2000-INPUT-ADD 10 TIMES.
DIVIDE 10 INTO SUM-VALUE GIVING AVERAGE-VALUE.
2000-INPUT-ADD. ...
CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 10 / 21
T HE 1950 S : T HE FIRST LANGUAGES ( CONT ’ D )

Algol 60
General, expressive language; most current imperatives are derivatives
Introduced many modern concepts
structured programming reserved keywords type declarations
recursion stack dynamic arrays call-by-value
user defined types free-format
Stack-based run time environment
Great success and also great failure (ahead of its time, too complex, lack of
I/O, lack of support from IBM) → entrenchment of Fortran
LISP (John McCarthy, MIT)
LISt Processing → the main data structure is the (singly linked) list
Untyped, messy language, but good for problems we solve by trial and error
(quick prototyping) → used in many AI applications
Historically inefficient on Von Neumann machines
Main processing unit: the recursive function → influenced the modern
functional languages such as ML, Miranda, Haskell
Contemporary variants include Common Lisp, Scheme, Emacs Lisp

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 11 / 21
A LGOL EXAMPLE

procedure Absmax(a) Size:(n, m) Result:(y) Subscripts:(i, k);


value n, m;
array a;
integer n, m, i, k;
real y;
comment The absolute greatest element of the matrix a, of size
n by m is transferred to y, and the subscripts of this element
to i and k;
begin integer p, q;
y := 0; i := k := 1;
for p:=1 step 1 until n do
for q:=1 step 1 until m do
if abs(a[p, q]) > y then
begin y := abs(a[p, q]);
i := p; k := q
end
end Absmax

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 12 / 21
L ISP EXAMPLE

(defun mapcar (fun list)


"Applies FUN on every element of LIST and returns the
list of results (iterative version)."
(let ((results nil))
(dolist (x list)
(setq results (cons (apply #’fun x) results)))
(reverse results)))

(defun mapcar (fun list)


"Applies FUN on every element of LIST and returns the
list of results (recursive version)."
(cons (apply #’fun (car list))
(mapcar fun (cdr list))))

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 13 / 21
T HE 1960 S : A N EXPLOSION IN LANGUAGES

Hundreds of languages were developed

PL/1 (1964)
Combined features of FORTRAN, COBOL, Algol 60 and more!
Translators were slow, huge, and unreliable
Some say it was ahead of its time. . .
Algol 68 → still ahead of its time!
Simula (or what would be called today Object-oriented Algol)
BASIC
etc.

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 14 / 21
T HE 1970 S : S IMPLICITY, ABSTRACTION , STUDY

Algol-W then Pascal (Nicklaus Wirth and C.A.R.Hoare) → small, simple,


efficient (reaction against the 60s), ideal for teaching
C (Dennis Ritchie)
Constructed as a portable assembler to build Unix for various architectures
But also has modern features (structured programming, data structures, etc.)
The primary API for Unix (Mac OS, Linux, etc.) is still C!
Euclid (University of Toronto, 1977)
Main goal → formal program verification
extends Pascal to include abstract data types
Scheme (1978, MIT) → simplified, cleaner Lisp

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 15 / 21
C EXAMPLE
#include <stdio.h>
main(t,_,a)
char*a;
{return!0<t?t<3?
main(-79,-13,a+
main(-87,1-_,
main(-86, 0, a+1 )
+a)):
1,
t<_?
main(t+1, _, a )
:3,
main ( -94, -27+t, a )
&&t == 2 ?_
<13 ?
main ( 2, _+1, "%s %d %d\n" )
:9:16:
t<0?
t<-72?
main( _, t,
"@n’+,#’/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l,+,/n{n+,/+#n+,/#;\
#q#n+,/+k#;*+,/’r :’d*’3,}{w+K w’K:’+}e#’;dq#’l q#’+d’K#!/+k#;\
q#’r}eKK#}w’r}eKK{nl]’/#;#q#n’){)#}w’){){nl]’/+#n’;d}rw’ i;# ){nl]!/n{n#’; \
r{#w’r nc{nl]’/#{l,+’K {rw’ iK{;[{nl]’/w#q#\
\
n’wk nw’ iwk{KK{nl]!/w{%’l##w#’ i; :{nl]’/*{q#’ld;r’}{nlwb!/*de}’c ;;\
{nl’-{}rw]’/+,}##’*}#nc,’,#nw]’/+kd’+e}+;\
#’rdq#w! nr’/ ’) }+}{rl#’{n’ ’)# }’+}##(!!/")
:
t<-50?
_==*a ?
putchar(31[a]):
main(-65,_,a+1)
:
main((*a == ’/’) + t, _, a + 1 )
:
0<t?
main ( 2, 2 , "%s")
:*a==’/’||
CSmain(0,
403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 16 / 21
T HE 1980 S : C ONSOLIDATION & NEW DIRECTIONS

ML → mostly functional language (like Lisp) with cleaner (math-like)


syntax
Prolog (Université Aix Marseille)
PROgrammation en LOGique / PROgramming in LOGic → describes the
problem at hand as known facts and inference rules
Notable industrial uses: IBM Watson, Apache UIMA, and the. . . Buran
spacecraft
Objects ’r’ Us:
Smalltalk → the purest example of object-oriented language
C++ → extend a popular language (C) with strongly typed object system
Eiffel → object-oriented Pascal

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 17 / 21
T HE 1990 S AND 200 S

1990s
Java → eliminate the non-object-oriented features of C++
Haskell → purely functional programming language
quicksort [] = []
quicksort (x:xs) = quicksort [y|x <- xs, y < x] ++ [x] ++
quicksort [y|x <- xs, y >= x]
2000s
Python → multi-paradigm language (procedural, object-oriented, functional,
etc.)
Languages for the Web
Java applets
Languages within Web pages (PHP, server-side includes)
Emphasis on cross-platform development
Develop on PC, run on cell phones, game consoles, and toasters

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 18 / 21
2010s: Connectivity Explosion

Computing devices are ubiquitous, and so is the Internet and Web


C and C++ are the most widely used system programming languages
Java had peaked. . . and then came Android
Most students learn C / C++ or Java
Web 2.0 programming (server side over client side rendering)
COBOL and Java are used for business applications
Fortran is the main language on supercomputers
We already have Object-Oriented Fortran!
C++ is growing
Several non-mainstream (but cleaner) languages rising (Ruby, Python,
Haskell, Go, Swift, C#, Scratch)

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 19 / 21
W HERE ARE WE NOW

* Python has risen to be one of the most used languages


* Business Intelligence Applications
* Automation
* Quick Prototyping
* Rust developed to deal with C++ shortcomings
* Web programming with frameworks (e.g. React)
* FORTAN still main language for supercomputers
* Matlab, and Python used as scientific programming languages
* Scratch is the leading language for teaching
* So much more...

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 19 / 21
S TRANGE LANGUAGES
Strange languages definitely exist
Case in point: Brainf**k
A Brainf**k program has an implicit byte pointer, called “the pointer”, which is
free to move around within an array of 30,000 bytes, initially all set to zero
The pointer is initialized to point to the beginning of this array
The Brainf**k programming language consists of eight commands, each of
which is represented as a single character
> Increment the pointer
< Decrement the pointer
+ Increment the byte at the pointer
- Decrement the byte at the pointer
. Output the byte at the pointer
, Input a byte and store it in the byte at the pointer
[ Jump past the matching ] if the byte at the pointer is zero
] Jump to the matching [
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..++
+.[-]>++++++++[<++++>-] <.#>+++++++++++[<+++++>-]<.>+++
+++++[<+++>-]<.+++.------.--------.[-]>++++++++ [ <++++
>-]<+.[-]++++++++++.
CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 20 / 21
C HRONOLOGY
Fortran

Lisp
1960 Algol 60 COBOL APL
Snobol 4

Simula PL/1

Algol 68

1970 Pascal
Smalltalk Prolog
Clu C
Scheme

ML
Ada ICON
1980 Modula 2
Miranda
C++
Eiffel

Oberon

Haskell

1990

Java

CS 403: A Brief (and Pretty Incomplete) History of Programming Languages (S. D. Bruda) Fall 2021 21 / 21
CS 403: Introduction to functional programming

Gregory Mierzwinski

Fall 2022
F UNCTIONAL PROGRAMMING IS PROGRAMMING
WITHOUT . . .

Selective assignments (a[i] = 6 is not allowed)


The goal of an imperative program is to change the state [of the machine]
The goal of a functional programs is to evaluate (reduce, simplify)
expressions
More generally updating assignments (y = x + 1 is fine but x = x + 1 is
not fine)
A variable in an imperative program: a name for a container
There is no proper concept of “variable” in functional programs. What is
called “variable” is a name for an expression
Explicit pointers, storage and storage management
Input/output
Control structures (loops, conditional statements)
Jumps (break, goto, exceptions)

CS 403: Introduction to functional programming Fall 2022 1 / 51


W HAT ’ S LEFT ?

Expressions (without side effects)


Referential transparency (i.e., substitutivity, congruence)
Definitions (of constants, functions)
Functions defined almost as in mathematics
Math Haskell

square(x) = x × x

Types (including higher-order, polymorphic, and recursively-defined)


tuples, lists, trees, shared sub-structures, implicit cycles
Automatic storage management (garbage collection)
Actually we do not care about storage at all; we abstract over the physical
machine instead!

CS 403: Introduction to functional programming Fall 2022 2 / 51


W HAT ’ S LEFT ?

Expressions (without side effects)


Referential transparency (i.e., substitutivity, congruence)
Definitions (of constants, functions)
Functions defined almost as in mathematics
Math Haskell
square : N → N
square(x) = x × x

Types (including higher-order, polymorphic, and recursively-defined)


tuples, lists, trees, shared sub-structures, implicit cycles
Automatic storage management (garbage collection)
Actually we do not care about storage at all; we abstract over the physical
machine instead!

CS 403: Introduction to functional programming Fall 2022 2 / 51


W HAT ’ S LEFT ?

Expressions (without side effects)


Referential transparency (i.e., substitutivity, congruence)
Definitions (of constants, functions)
Functions defined almost as in mathematics
Math Haskell
square : N → N square :: Integer -> Integer
square(x) = x × x square x = x * x

Types (including higher-order, polymorphic, and recursively-defined)


tuples, lists, trees, shared sub-structures, implicit cycles
Automatic storage management (garbage collection)
Actually we do not care about storage at all; we abstract over the physical
machine instead!

CS 403: Introduction to functional programming Fall 2022 2 / 51


W HAT ’ S LEFT ?

Expressions (without side effects)


Referential transparency (i.e., substitutivity, congruence)
Definitions (of constants, functions)
Functions defined almost as in mathematics
Math Haskell
square : N → N square :: Integer -> Integer
square(x) = x × x square x = x * x
A function is defined by a type definition and a set of rewriting rules
The type definition may appear optional, but is not
Types (including higher-order, polymorphic, and recursively-defined)
tuples, lists, trees, shared sub-structures, implicit cycles
Automatic storage management (garbage collection)
Actually we do not care about storage at all; we abstract over the physical
machine instead!

CS 403: Introduction to functional programming Fall 2022 2 / 51


S ESSIONS , SCRIPTS , EVALUATION

< godel:306/slides > ghci


GHCi, version 7.4.1: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done. (file example.hs)
Loading package base ... linking ... done.
Prelude> 66
66 -- a value (of type Integer):
Prelude> 6 * 7
42 infty :: Integer
Prelude> square 35567 infty = infty + 1
<interactive>:4:1: Not in scope:
‘square’ -- a function
Prelude> :load example -- (from Integer to Integer):
[1 of 1] Compiling Main
( example.hs, interpreted ) square :: Integer -> Integer
Ok, modules loaded: Main. square x = x * x
*Main> square 35567
1265011489 -- another function:
*Main> square (smaller (5, 78))
25 smaller :: (Integer,Integer)->Integer
*Main> square (smaller (5*10, 5+10)) smaller (x,y) = if x<=y then x else y
225
*Main>

CS 403: Introduction to functional programming Fall 2022 3 / 51


W HAT ’ S LEFT ? ( CONT ’ D )

Functions are first order objects


twice :: (Integer -> Integer) -> (Integer -> Integer)
twice f = g
where g x = f (f x)
A program (or script) is a collection of definitions
Predefined data types in a nutshell:
Numerical: Integer , Int, Float, Double
Logical: Bool (values: True, False)
Characters: Char (’a’, ’b’, etc.)
Composite:
Functional: Integer → Integer ;
Tuples: (Int, Int, Float);
Combinations: (Int, Float) → (Float, Bool), Int → (Int → Int)
Sole solution for iterative/repeated computations:

CS 403: Introduction to functional programming Fall 2022 4 / 51


W HAT ’ S LEFT ? ( CONT ’ D )

Functions are first order objects


twice :: (Integer -> Integer) -> (Integer -> Integer)
twice f = g
where g x = f (f x)
A program (or script) is a collection of definitions
Predefined data types in a nutshell:
Numerical: Integer , Int, Float, Double
Logical: Bool (values: True, False)
Characters: Char (’a’, ’b’, etc.)
Composite:
Functional: Integer → Integer ;
Tuples: (Int, Int, Float);
Combinations: (Int, Float) → (Float, Bool), Int → (Int → Int)
Sole solution for iterative/repeated computations: recursion

CS 403: Introduction to functional programming Fall 2022 4 / 51


S CRIPTS

A script is a collection of definitions of values (including functions)


Syntactical sugar: definitions by guarded equations:
smaller :: (Integer, Integer) -> Integer
smaller (x,y)
| x <= y = x
| x > y = y
Recursive definitions:
fact :: Integer -> Integer
fact x = if x==0 then 1 else x * fact (x-1)
Syntactical sugar: definitions by pattern matching (aka by cases):
fact :: Integer -> Integer
fact 0 = 1
fact x = x * fact (x-1)

CS 403: Introduction to functional programming Fall 2022 5 / 51


L OCAL DEFINITIONS

Two forms:
let v1 = e1 definition
v2 = e2 where v1 = e1
. v2 = e2
. .
. .
vk = ek .
in expression vk = ek
Definitions are qualified by where clauses, while expressions are
qualified by let clauses

CS 403: Introduction to functional programming Fall 2022 6 / 51


S COPING

Haskell uses static scoping.


cylinderArea :: Float -> Float -> Float
cylinderArea h r = h * 2 * pi * r + 2 * pi * r * r

cylinderArea1 :: Float -> Float -> Float


cylinderArea1 h r = x + 2 * y
where x = h * circLength r
y = circArea r
circArea x = pi * x * x
circLength x = 2 * pi * x

cylinderArea2 :: Float -> Float -> Float


cylinderArea2 h r = let x = h * circLength r
y = circArea r
in x + 2 * y
where circArea x = pi * x * x
circLength x = 2 * pi * x

CS 403: Introduction to functional programming Fall 2022 7 / 51


T YPES

Each type has associated operations that are not necessarily meaningful
to other types
Arithmetic operations (+, −, ∗, /) can be applied to numerical types, but it
does not make any sense to apply them on, say, values of type Bool
It does, however make sense to compare (using = (==), 6= (/=), ≤ (<=), <,
etc.) both numbers and boolean values
Every well formed expression can be assigned a type (strong typing)
The type of an expression can be inferred from the types of the constituents
of that expression
Those expression whose type cannot be inferred are rejected by the
compiler
badType x fact :: Integer -> Integer
| x == 0 = 0 fact x
| x > 0 = ’p’ | x < 0 = error "Negative argument."
| x < 0 = ’n’ | x == 0 = 1
| x > 0 = x * fact (x-1)
What is the type of error?

CS 403: Introduction to functional programming Fall 2022 8 / 51


T WO DATA TYPES

Booleans. Values: True, False


operations on Bool: logic operators: ∨ (||), ∧ (&&), ¬ (not); comparisons: =
(==), 6= (/=); relational <, ≤ (<=), >, ≥ (>=)
Characters. Values: 256 of them, e.g., ’a’, ’b’, ’\n’
Oerations on characters: comparison, relational, plus:
ord :: Char -> Int Prelude> import Data.Char
chr :: Int -> Char Prelude Data.Char> ord ’a’
97
Prelude Data.Char> chr 100
’d’
toLower :: Char -> Char
toLower c | isUpper c = chr (ord c - (ord ’A’ - ord ’a’))
| True = c
where isUpper c = ’A’ <= c && c <= ’Z’

CS 403: Introduction to functional programming Fall 2022 9 / 51


L ISTS

A list is an ordered set of values.

[1, 2, 3] :: [Int] [[1, 2], [3]] :: [[Int]] [0 h0 ,0 i 0 ] :: [Char ]

[div , rem] :: ?? [1,0 h0 ] :: ?? [ ] :: ??


Syntactical sugar:
Prelude> [’h’,’i’]
"hi"
Prelude> "hi" == [’h’,’i’]
True
Prelude> [[’h’,’i’],"there"]
["hi","there"]

CS 403: Introduction to functional programming Fall 2022 10 / 51


C ONSTRUCTING LISTS
Constructors: [ ] (the empty list) and : (constructs a longer list)
Prelude> 1:[2,3,4]
[1,2,3,4]
Prelude> ’h’:’i’:[]
"hi"
The operator : (pronounced “cons”) is right associative
The operator : does not concatenate lists together! (++ does this instead)
Prelude> [1,2,3] : [4,5]
No instance for (Num [t0])
arising from the literal ‘4’
Possible fix: add an instance declaration for (Num [t0])
In the expression: 4
In the second argument of ‘(:)’, namely ‘[4, 5]’
In the expression: [1, 2, 3] : [4, 5]
Prelude> [1,2,3] : [[4,5]]
[[1,2,3],[4,5]]
Prelude> [1,2,3] ++ [4,5]
[1,2,3,4,5]
Prelude>
CS 403: Introduction to functional programming Fall 2022 11 / 51
O PERATIONS AND PATTERN MATCHING ON LISTS

Comparisons (<, ≥, ==, etc.), if possible, are made in lexicographical


order
Subscript operator: !! (e.g., [1, 2, 3] !! 1 evaluates to 2) – expensive
Arguably the most common list processing: Given a list, do something
with each and every element of that list
In fact, such a processing is so common that there exists the predefined
map that does precisely this:
map f [] = []
map f (x:xs) = f x : map f xs
This is also an example of pattern matching on lists
Variant to pattern matching: head and tail (predefined)
head (x:xs) = x map f l = if l == []
tail (x:xs) = xs then []
else f (head l) : map f (tail l)

CS 403: Introduction to functional programming Fall 2022 12 / 51


T UPLES

While lists are homogenous, tuples group values of (posibly) diferent


types
divRem :: Integer -> Integer -> (Integer, Integer)
divRem x y = (div x y, rem x y)

divRem1 :: (Integer, Integer) -> (Integer, Integer)


divRem1 (x, 0) = (0, 0)
divRem1 (x, y) = (div x y, rem x y)
The latter variant is also an example of pattern matching on tuples

CS 403: Introduction to functional programming Fall 2022 13 / 51


O PERATORS AND FUNCTIONS

An operator contains symbols from the set !#$%&*+./<=>?@\ˆ|: (− and ˜


may also appear, but only as the first character)
Some operators are predefined (+, −, etc.), but you can define your own
as well
An (infix) operator becomes (prefix) function if surrounded by brackets. A
(prefix) function becomes operator if surrounded by backquotes:
divRem :: Integer -> Integer -> (Integer, Integer) Main> 3 %% 2
(1,1)
x ‘divRem‘ y = (div x y, rem x y) Main> (%%) 3 2
(1,1)
-- precisely equivalent to Main> divRem 3 2
(1,1)
-- divRem x y = (div x y, rem x y) Main> 3 ‘divRem‘ 2
(1,1)
Main>
(%%) :: Integer -> Integer -> (Integer, Integer)
(%%) x y = (div x y, rem x y)
-- precisely equivalent to
-- x %% y = (div x y, rem x y)
These are just lexical conventions

CS 403: Introduction to functional programming Fall 2022 14 / 51


I DENTIFIERS

Identifiers consist in letters, numbers, simple quotes (’), and underscores


( ), but they must start with a letter
For the time being, they must actually start with a lower case letter
A Haskell idenitifer starting with a capital letter is considered a type (e.g.,
Bool) or a type constructor (e.g., True)—we shall talk at length about those
later
By convention, types (i.e., class names) in Java start with capital letters, and
functions (i.e., method names) start with a lower case letter. What is a
convention in Java is the rule in Haskell!
Some identifiers are language keywords and cannot be redefined (if,
then, else, let, where, etc.).
Some identifiers (e.g., either) are defined in the standard prelude and
possibly cannot be redefined (depending on implementation, messages like
“Definition of variable "either" clashes with import”)

CS 403: Introduction to functional programming Fall 2022 15 / 51


I NDUCTION AND RECURSIVE FUNCTIONS

An inductive proof for a fact P(n), for all n ≥ α consists in two steps:
Proof of the base case P(α), and
The inductive step: assume that P(n − 1) is true and show that P(n) is also
true

Example
Proof that all the crows have the same colour: For all sets C of crows, |C| ≥ 1,
it holds that all the crows in set C are identical in colour
Base case, |C| = 1: immediate.
For a set of crows C, |C| = n, remove a crow for the set; the remaining (a set of
size n − 1) have the same colour by inductive assumption. Repeat by removing
other crow. The desired property follows
Note: According to the Webster’s Revised Unabridged Dictionary, a crow is “A
bird, usually black, of the genus Corvus [. . . ].”

CS 403: Introduction to functional programming Fall 2022 16 / 51


I NDUCTION AND RECURSIVE FUNCTIONS ( CONT ’ D )

The same process is used for building recursive functions: One should
provide the base case(s) and the recursive definition(s):
To write a function f :: Integer → Integer , write the base case (definition for
f 0) and the inductive case (use f (n − 1) to write a definition for f n)
Example: computing the factorial
Base case: fact 0 = 1
Induction step: fact n = n * fact (n-1)
To write a function f :: [a] → β, use induction over the length of the
argument; the base case is f [] and the inductive case is f (x : xs) defined
using f xs
Example: function that concatenates two lists together; we perform induction
on the length of the first argument:
Base case: concat [] ys = ys
Induction step: concat (x:xs) ys = x : concat xs ys
Induction is also an extremely useful tool to prove functions that are
already written

CS 403: Introduction to functional programming Fall 2022 17 / 51


E XAMPLE : L ISTS AS SETS

Membership (x ∈ A):
member x [] = False
member x (y:ys) | x == y = True
| True = member x ys
Union (A ∪ B), intersection (A ∩ B), difference (A \ B):
union [] t = t
union (x:xs) t | member x t = union xs t
| True = x : union xs t
intersection [] t = []
intersection (x:xs) t | member x t = x : intersection xs t
| True = intersection xs t
difference [] t = []
difference (x:xs) t | member x t = difference xs t
| True = x : difference xs t
Constructor: no recursion. makeSet x = [x]

CS 403: Introduction to functional programming Fall 2022 18 / 51


H IGHER ORDER FUNCTIONS

In Haskell, all objects (including functions) are first class citizens. That is,
all objects can be named,
all objects can be members of a list/tuple,
all objects can be passed as arguments to functions,
all objects can be returned from functions,
all objects can be the value of some expression
twice :: (a -> a) -> (a -> a) twice :: (a -> a) -> a -> a
twice f = g twice f x = f (f x)
where g x = f (f x)

compose f g = h compose f g x = f (g x)
where h x = f (g x) -- or --
compose f g = f.g

CS 403: Introduction to functional programming Fall 2022 19 / 51


TO CURRY OR NOT TO CURRY
curried form: uncurried form:
compose :: (a->b) -> (c->a) -> c->b compose :: (a->b, c->a) -> c->b
compose f g = f.g compose (f,g) = f.g
In Haskell, any function takes one argument and returns one value.
What if we need more than one argument?
Uncurried We either present the arguments packed in a tuple, or
Curried We use partial application: we build a function that takes one
argument and that return a function which in turn takes one
argument and returns another function which in turn. . .
curried: uncurried:
add :: (Num a) => a -> a -> a add :: (Num a) => (a, a) -> a
add x y = x + y add (x,y) = x + y
-- equivalent to the explicit version
-- add x = g
-- where g y = x + y

incr :: (Num a) => a -> a incr :: (Num a) => a -> a


incr = add 1 incr x = add (1,x)

CS 403: Introduction to functional programming Fall 2022 20 / 51


TO CURRY OR NOT TO CURRY ( CONT ’ D )

Curying is made possible by lexical closures →all the values existing


when a particular function is defined will exist when the function is run
What if we have a curried function and we want an uncurried one (or the
other way around)?
The following two functions are predefined:
curry f = g
where g a b = f (a,b) -- lexical closure:
-- f available inside g
uncurry g = f
where f (a,b) = g a b

CS 403: Introduction to functional programming Fall 2022 21 / 51


TO CURRY OR NOT TO CURRY ( CONT ’ D )

Curying is made possible by lexical closures →all the values existing


when a particular function is defined will exist when the function is run
What if we have a curried function and we want an uncurried one (or the
other way around)?
The following two functions are predefined:
curry f = g
where g a b = f (a,b) -- lexical closure:
-- f available inside g
uncurry g = f
where f (a,b) = g a b
Alternatively,
curry f a b = f (a,b)
uncurry g (a,b) = g a b
Note that the two functions are curried themselves. . .

CS 403: Introduction to functional programming Fall 2022 21 / 51


G LOBAL VARIABLES
Given a nonnegative number x √
:: Float, write a function mySqrt that
computes an approximation of x with precision  = 0.0001

Newton says that, if yn is an approximation of x, then a better
approximation is yn+1 = (yn + x/yn )/2

CS 403: Introduction to functional programming Fall 2022 22 / 51


G LOBAL VARIABLES
Given a nonnegative number x √
:: Float, write a function mySqrt that
computes an approximation of x with precision  = 0.0001

Newton says that, if yn is an approximation of x, then a better
approximation is yn+1 = (yn + x/yn )/2
So we have:
mySqrt :: Float -> Float
mySqrt x = sqrt’ x
where sqrt’ y = if good y then y else sqrt’ (improve y)
good y = abs (y*y - x) < eps
improve y = (y + x/y)/2
eps = 0.0001
x is very similar to a global variable in procedural programming

CS 403: Introduction to functional programming Fall 2022 22 / 51


G LOBAL VARIABLES
Given a nonnegative number x √
:: Float, write a function mySqrt that
computes an approximation of x with precision  = 0.0001

Newton says that, if yn is an approximation of x, then a better
approximation is yn+1 = (yn + x/yn )/2
So we have:
mySqrt :: Float -> Float
mySqrt x = sqrt’ x
where sqrt’ y = if good y then y else sqrt’ (improve y)
good y = abs (y*y - x) < eps
improve y = (y + x/y)/2
eps = 0.0001
x is very similar to a global variable in procedural programming
Even closer to procedural programming:
mySqrt :: Float -> Float until :: (a -> Bool) ->
mySqrt x = until done improve x (a -> a) -> a -> a
where done y = abs (y*y - x) until p f x
< eps | p x = x
improve y = (y + x/y)/2 | True = until p f (f x)
eps = 0.0001

CS 403: Introduction to functional programming Fall 2022 22 / 51


ACCUMULATING RESULTS

1 mystery x = aux x []
where aux [] ret = ret
aux (x:xs) ret = aux xs (x:ret)

CS 403: Introduction to functional programming Fall 2022 23 / 51


ACCUMULATING RESULTS

1 mystery x = aux x []
where aux [] ret = ret
aux (x:xs) ret = aux xs (x:ret)
2 reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
What is the difference between these two implementations?

CS 403: Introduction to functional programming Fall 2022 23 / 51


ACCUMULATING RESULTS

1 mystery x = aux x []
where aux [] ret = ret
aux (x:xs) ret = aux xs (x:ret)
2 reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
What is the difference between these two implementations?
An accumulating argument is used for efficiency purposes
It basically transforms a general recursion into a tail recursion

CS 403: Introduction to functional programming Fall 2022 23 / 51


M APS

map applies a function to each element in a list


map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
For example:
upto m n = if m > n then [] else m: upto (m+1) n
square x = x * x

Prelude> map ((<) 3) [1,2,3,4]


[True,True,False,False]
Prelude> sum (map square (upto 1 10))
385
Prelude>

CS 403: Introduction to functional programming Fall 2022 24 / 51


M APS ( CONT ’ D )

Intermission: zip and unzip


Prelude> zip [0,1,2,3,4] "hello"
[(0,’h’),(1,’e’),(2,’l’),(3,’l’),(4,’o’)]
Prelude> zip [0,1,2,3,4,5,6] "hello"
[(0,’h’),(1,’e’),(2,’l’),(3,’l’),(4,’o’)]
Prelude> unzip [(0,’h’),(1,’e’),(2,’l’),(4,’o’)]
([0,1,2,4],"helo")
Prelude>
A more complex (and useful) example of map:
mystery :: (Ord a) => [a] -> Bool
mystery xs = and (map (uncurry (<=)) (zip xs (tail xs)))

CS 403: Introduction to functional programming Fall 2022 25 / 51


M APS ( CONT ’ D )

Intermission: zip and unzip


Prelude> zip [0,1,2,3,4] "hello"
[(0,’h’),(1,’e’),(2,’l’),(3,’l’),(4,’o’)]
Prelude> zip [0,1,2,3,4,5,6] "hello"
[(0,’h’),(1,’e’),(2,’l’),(3,’l’),(4,’o’)]
Prelude> unzip [(0,’h’),(1,’e’),(2,’l’),(4,’o’)]
([0,1,2,4],"helo")
Prelude>
A more complex (and useful) example of map:
mystery :: (Ord a) => [a] -> Bool
mystery xs = and (map (uncurry (<=)) (zip xs (tail xs)))
This finds whether the argument list is in nondecreasing order

CS 403: Introduction to functional programming Fall 2022 25 / 51


F ILTERS

filter :: (a -> Bool) -> [a] -> [a]


filter p [] = []
filter p (x:xs) = if p x then x : filter p xs else filter p xs

Example:
mystery :: [(String,Int)] -> [String]
mystery xs = map fst (filter ( ((<=) 80) . snd ) xs)

CS 403: Introduction to functional programming Fall 2022 26 / 51


F ILTERS

filter :: (a -> Bool) -> [a] -> [a]


filter p [] = []
filter p (x:xs) = if p x then x : filter p xs else filter p xs

Example:
mystery :: [(String,Int)] -> [String]
mystery xs = map fst (filter ( ((<=) 80) . snd ) xs)

Prelude> mystery [("a",70),("b",80),("c",91),("d",79)]


["b","c"]
Suppose that the final grades for some course are kept as a list of pairs
(student name, grade). This then finds all the students that got an A

CS 403: Introduction to functional programming Fall 2022 26 / 51


F OLDS

foldr
[l1 , l2 , . . . , ln ] −→ l1 • (l2 • (l3 • (· · · • (ln • id) · · · )))
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr op id [] = id
foldr op id (x:xs) = x ‘op‘ (foldr op id xs)

foldl
[l1 , l2 , . . . , ln ] −→ (· · · (((id • l1 ) • l2 ) • l3 ) • · · · • ln )
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl op id [] = id
foldl op id (x:xs) = foldl op (x ‘op‘ id) xs

Almost all the interesting functions on lists are or can be implemented


using foldr or foldl:
and = foldr (&&) True concat = foldr (++) []
sum = foldr (+) 0 length = foldr oneplus 0
map f = foldr ((:).f) [] where oneplus x n = 1 + n

CS 403: Introduction to functional programming Fall 2022 27 / 51


L IST COMPREHENSION

Examples:
triples :: Int -> [(Int,Int,Int)]
triples n = [(x,y,z) | x <- [1..n],y <- [1..n],z <- [1..n]]
-- or [(x,y,z) | x <- [1..n],y <- [x..n],z <- [z..n]]
pyth :: (Int,Int,Int) -> Bool
pyth (x,y,z) = x*x + y*y == z*z
triads :: Int -> [(Int,Int,Int)]
triads n = [(x,y,z) | (x,y,z) <- triples n, pyth (x,y,z)]
General form:

[exp|gen1 , gen2 , . . . , genn , guard1 , guard2 , . . . guardp ]

Quicksort:
qsort :: (Ord a) => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y <= x] ++ [x] ++
qsort [y | y <- xs, y > x]

CS 403: Introduction to functional programming Fall 2022 28 / 51


P OLYMORPHIC TYPES

Some functions have a type definition involving only type names:


and :: [Bool] -> Bool
and = foldr (&&) True
These functions are monomorphic
It is however useful sometimes to write functions that can work on data of
more than one type. These are polymorphic functions
length :: [a] -> Int -- For any type a: length :: [a]->Int
map :: (a -> b) -> [a] -> [b]
Restricted polymorphism: What is the most general type of a function
that sorts a list of values, and why?
qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y <= x] ++ [x] ++
qsort [y | y <- xs, y > x]

CS 403: Introduction to functional programming Fall 2022 29 / 51


P OLYMORPHIC TYPES

Some functions have a type definition involving only type names:


and :: [Bool] -> Bool
and = foldr (&&) True
These functions are monomorphic
It is however useful sometimes to write functions that can work on data of
more than one type. These are polymorphic functions
length :: [a] -> Int -- For any type a: length :: [a]->Int
map :: (a -> b) -> [a] -> [b]
Restricted polymorphism: What is the most general type of a function
that sorts a list of values, and why?
qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y <= x] ++ [x] ++
qsort [y | y <- xs, y > x]
qsort :: (Ord a) => [a] -> [a]
([a] → [a] for any type a such that an order is defined over a)

CS 403: Introduction to functional programming Fall 2022 29 / 51


T YPE SYNONYMS

A function that adds two polynomials with floating point coefficients:


polyAdd :: [Float] -> [Float] -> [Float]
polyAdd :: Poly -> Poly -> Poly would have been nicer though. . .
This can be done by defining “Poly” as a type synonym for [Float]:
type Poly = [Float]
Type synonyms can also be parameterized:

type Stack a = [a] Main> aCharStack


"ab"
newstack :: Stack a Main> push ’x’ aCharStack
newstack = [] "xab"
Main> :t aCharStack
push :: a -> Stack a -> Stack a aCharStack :: Stack Char
push x xs = x:xs

aCharStack :: Stack Char


aCharStack = push ’a’ (push ’b’ newstack)

CS 403: Introduction to functional programming Fall 2022 30 / 51


A LGEBRAIC TYPES
Remember when we defined functions using induction (aka recursion)?
Types can be defined in a similar manner (the general form of
mathematical induction is called structural induction): Take for example
natural numbers:
data Nat = Zero | Succ Nat
deriving Show

-- Operations:
addNat,mulNat :: Nat -> Nat -> Nat
addNat m Zero = m
addNat m (Succ n) = Succ (addNat m n)
mulNat m Zero = Zero
mulNat m (Succ n) = addnat (mulNat m n) m
Again, type definitions can be parameterized:
data List a = Nil | Cons a (List a)
-- data [a] = [] | a : [a]
data BinTree a = Null | Node a (BinTree a) (BinTree a)
CS 403: Introduction to functional programming Fall 2022 31 / 51
T YPE CLASSES

Each type may belong to a type class that define general operations. This
also offers a mechanism for overloading
Type classes in Haskell are similar with abstract classes in Java
data Nat = Zero | Succ Nat Main> one
deriving (Eq,Show) Succ Zero
Main> two
instance Ord Nat where Succ (Succ Zero)
Zero <= x = True Main> three
x <= Zero = False Succ (Succ (Succ Zero))
(Succ x) <= (Succ y) = x <= y Main> one > two
False
one,two,three :: Nat Main> one > Zero
one = Succ Zero True
two = Succ one Main> two < three
three = Succ two True
Main>

CS 403: Introduction to functional programming Fall 2022 32 / 51


Ord Nat WORKS BECAUSE . . .
. . . The class Ord is defined in the standard prelude as follows:
class (Eq a) => Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>=), (>) :: a -> a -> Bool
max, min :: a -> a -> a

-- Minimal complete definition: (<=) or compare


-- using compare can be more efficient for complex types
compare x y | x==y = EQ
| x<=y = LT
| otherwise = GT

x <= y = compare x y /= GT
x < y = compare x y == LT
x >= y = compare x y /= LT
x > y = compare x y == GT

max x y | x >= y = x
| otherwise = y
min x y | x <= y = x
| otherwise = y
CS 403: Introduction to functional programming Fall 2022 33 / 51
T YPE CLASSES ( CONT ’ D )

data Nat = Zero | Succ Nat Main> one + two


deriving (Eq,Ord,Show) Succ (Succ (Succ Zero))
Main> two * three
instance Num Nat where Succ (Succ (Succ (Succ
m + Zero = m (Succ (Succ Zero)))))
m + (Succ n) = Succ (m + n) Main> one + two == three
m * Zero = Zero True
m * (Succ n) = (m * n) + m Main> two * three == one
False
one,two,three :: Nat Main> three - two
one = Succ Zero
two = Succ one ERROR - Control stack
three = Succ two overflow

CS 403: Introduction to functional programming Fall 2022 34 / 51


D EFINITION OF Num

class (Eq a, Show a) => Num a where


(+), (-), (*) :: a -> a -> a
negate :: a -> a
abs, signum :: a -> a
fromInteger :: Integer -> a
fromInt :: Int -> a

-- Minimal complete definition: All, except negate or (-)


x - y = x + negate y
fromInt = fromIntegral
negate x = 0 - x

CS 403: Introduction to functional programming Fall 2022 35 / 51


S UBTRACTION
instance Num Nat where
m + Zero = m
m + (Succ n) = Succ (m + n)
m * Zero = Zero
m * (Succ n) = (m * n) + m

m - Zero = m
(Succ m) - (Succ n) = m - n

Nat> one - one


Zero
Nat> two - one
Succ Zero
Nat> two - three

Program error: pattern match failure:


instNum_v1563_v1577 Nat_Zero one
CS 403: Introduction to functional programming Fall 2022 36 / 51
OTHER INTERESTING TYPE CLASSES

class Enum a where


succ, pred :: a -> a
toEnum :: Int -> a
fromEnum :: a -> Int
enumFrom :: a -> [a] -- [n..]
enumFromThen :: a -> a -> [a] -- [n,m..]
enumFromTo :: a -> a -> [a] -- [n..m]
enumFromThenTo :: a -> a -> a -> [a] -- [n,n’..m]

-- Minimal complete definition: toEnum, fromEnum


succ = toEnum . (1+) . fromEnum
pred = toEnum . subtract 1 . fromEnum
enumFrom x = map toEnum [ fromEnum x ..]
enumFromTo x y = map toEnum [ fromEnum x .. fromEnum y ]
enumFromThen x y = map toEnum [ fromEnum x, fromEnum y ..]
enumFromThenTo x y z = map toEnum [ fromEnum x, fromEnum y ..
fromEnum z ]

CS 403: Introduction to functional programming Fall 2022 37 / 51


OTHER INTERESTING TYPE CLASSES ( CONT ’ D )

class Show a where


show :: a -> String
showsPrec :: Int -> a -> ShowS
showList :: [a] -> ShowS

-- Minimal complete definition: show or showsPrec


show x = showsPrec 0 x ""
showsPrec _ x s = show x ++ s
showList [] = showString "[]"
showList (x:xs) = showChar ’[’ . shows x . showl xs
where showl [] = showChar ’]’
showl (x:xs) = showChar ’,’ .
shows x . showl xs

instance Show Nat where


show Zero = "0"
show (Succ n) = "1 + " ++ show n

CS 403: Introduction to functional programming Fall 2022 38 / 51


E XAMPLE OF TYPE CLASSES
Eq Show Ord Enum

+ - *
negate
abs Num
signum

Real
Bounded div
mod Fractional
minBound Integral even
maxBoun odd /
d recip

RealFrac Floating
pi exp log
sqrt
logBase
RealFloat sin, cos, etc.

Int Integer Float Double

CS 403: Introduction to functional programming Fall 2022 39 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments:
Inspect the argument patterns if any:
parse definitions top to bottom, right to left:
“q : []” =⇒
“(f q x)” =⇒
“scanl f (f q x) xs” =⇒
“q : scanl f (f q x) xs” =⇒
So the overall type is

CS 403: Introduction to functional programming Fall 2022 40 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments: α → β → γ → δ
Inspect the argument patterns if any:
parse definitions top to bottom, right to left:
“q : []” =⇒
“(f q x)” =⇒
“scanl f (f q x) xs” =⇒
“q : scanl f (f q x) xs” =⇒
So the overall type is

CS 403: Introduction to functional programming Fall 2022 40 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments: α → β → γ → δ
Inspect the argument patterns if any: α → β → [γ] → δ
parse definitions top to bottom, right to left:
“q : []” =⇒
“(f q x)” =⇒
“scanl f (f q x) xs” =⇒
“q : scanl f (f q x) xs” =⇒
So the overall type is

CS 403: Introduction to functional programming Fall 2022 40 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments: α → β → γ → δ
Inspect the argument patterns if any: α → β → [γ] → δ
parse definitions top to bottom, right to left:
“q : []” =⇒ no extra information
“(f q x)” =⇒
“scanl f (f q x) xs” =⇒
“q : scanl f (f q x) xs” =⇒
So the overall type is

CS 403: Introduction to functional programming Fall 2022 40 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments: α → β → γ → δ
Inspect the argument patterns if any: α → β → [γ] → δ
parse definitions top to bottom, right to left:
“q : []” =⇒ no extra information
“(f q x)” =⇒ f is a function with 2 arguments: (β → γ → η) → β → [γ] → δ
“scanl f (f q x) xs” =⇒
“q : scanl f (f q x) xs” =⇒
So the overall type is

CS 403: Introduction to functional programming Fall 2022 40 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments: α → β → γ → δ
Inspect the argument patterns if any: α → β → [γ] → δ
parse definitions top to bottom, right to left:
“q : []” =⇒ no extra information
“(f q x)” =⇒ f is a function with 2 arguments: (β → γ → η) → β → [γ] → δ
“scanl f (f q x) xs” =⇒ β = δ i.e. (β → γ → β) → β → [γ] → δ
“q : scanl f (f q x) xs” =⇒
So the overall type is

CS 403: Introduction to functional programming Fall 2022 40 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments: α → β → γ → δ
Inspect the argument patterns if any: α → β → [γ] → δ
parse definitions top to bottom, right to left:
“q : []” =⇒ no extra information
“(f q x)” =⇒ f is a function with 2 arguments: (β → γ → η) → β → [γ] → δ
“scanl f (f q x) xs” =⇒ β = δ i.e. (β → γ → β) → β → [γ] → δ
“q : scanl f (f q x) xs” =⇒ δ = [β] i.e. (β → γ → β) → β → [γ] → [β]
So the overall type is

CS 403: Introduction to functional programming Fall 2022 40 / 51


T YPE INFERENCE

Different from type checking; in fact precedes type checking


allows the compilers to find the types automatically
Example:
scanl f q [] = q : []
scanl f q (x : xs) = q : scanl f (f q x) xs
First count the arguments: α → β → γ → δ
Inspect the argument patterns if any: α → β → [γ] → δ
parse definitions top to bottom, right to left:
“q : []” =⇒ no extra information
“(f q x)” =⇒ f is a function with 2 arguments: (β → γ → η) → β → [γ] → δ
“scanl f (f q x) xs” =⇒ β = δ i.e. (β → γ → β) → β → [γ] → δ
“q : scanl f (f q x) xs” =⇒ δ = [β] i.e. (β → γ → β) → β → [γ] → [β]
So the overall type is
scanl :: (a -> b -> a) -> a -> [b] -> [a]

CS 403: Introduction to functional programming Fall 2022 40 / 51


T HE LAMBDA NOTATION

Recall that a Haskell function accepts one argument and returns one
result
peanuts → chocolate-covered peanuts
raisins → chocolate-covered raisins
ants → chocolate-covered ants
Using the lambda calculus, a general “chocolate-covering” function (or
rather λ-expression) is described as follows:

λx.chocolate-covered x

Then we can get chocolate-covered ants by applying this function:


(λx.chocolate-covered x) ants → chocolate-covered ants

CS 403: Introduction to functional programming Fall 2022 41 / 51


T HE LAMBDA NOTATION ( CONT ’ D )

A general covering function:

λy.λx.y-covered x

The result of the application of such a function is itself a function:

(λy.λx.y-covered x) caramel → λx.caramel-covered x

((λy.λx.y-covered x) caramel) ants → (λx.caramel-covered x) ants


→ caramel-covered ants
Functions can also be parameters to other functions:

λf .(f ) ants

((λf .(f ) ants) λx.chocolate-covered) x


→ (λx.chocolate-covered x) ants
→ chocolate-covered ants

CS 403: Introduction to functional programming Fall 2022 42 / 51


L AMBDA C ALCULUS

The lambda calculus is a formal system designed to investigate function


definition, function application and recursion
Introduced by Alonzo Church and Stephen Kleene in the 1930s
We start with a countable set of identifiers, e.g.,
{a, b, c, . . . , x, y, z, x1, x2, . . .} and we build expressions using the
following rules:

LE XPRESSION → I DENTIFIER
LE XPRESSION → λI DENTIFIER.LE XPRESSION (abstraction)
LE XPRESSION → (LE XPRESSION)LE XPRESSION (combination)
LE XPRESSION → (LE XPRESSION)

In an expression λx.E, x is called a bound variable; a variable that is not


bound is a free variable
Syntactical sugar: Normally, no literal constants exist in lambda calculus.
We use, however, literals for clarity
Further sugar: H ASKELL

CS 403: Introduction to functional programming Fall 2022 43 / 51


R EDUCTIONS

In lambda calculus, an expression (λx.E)F can be reduced to E[F /x]


E[F /x] stands for the expression E, where F is substituted for all the bound
occurrences of x
In fact, there are three reduction rules:
α: λx.E reduces to λy .E[y /x] if y is not free in E (change of variable)
β: (λx.E)F reduces to E[F /x] (functional application)
γ: λx.(Fx) reduces to F if x is not free in F (extensionality)
The purpose in life of a Haskell program, given some expression, is to
repeatedly apply these reduction rules in order to bring that expression to
its “irreducible” form or normal form

CS 403: Introduction to functional programming Fall 2022 44 / 51


H ASKELL AND THE L AMBDA C ALCULUS
In a Haskell program, we write functions and then apply them
Haskell programs are nothing more than collections of λ-expressions, with
added sugar for convenience (and diabetes)
We write a Haskell program by writing λ-expressions and giving names to
them:
succ x = x + 1 succ = \ x -> x + 1

length = foldr onepl 0 length = foldr (\ x -> \ n -> 1+n) 0


where onepl x n = 1+n -- shorthand: (\ x n -> 1+n)

Main> succ 10 Main> (\ x -> x + 1) 10


11 11

Another example: map (\ x -> x+1) [1,2,3] maps (i.e., applies) the
λ-expression λx.x + 1 to all the elements of the list, thus producing
[2,3,4]
In general, for some expression E, λx.E (in Haskell-speak: \ x -> E)
denotes the function that maps x to the (value of) E
CS 403: Introduction to functional programming Fall 2022 45 / 51
M ULTIPLE REDUCTIONS

More than one order of reduction is usually possible in lambda calculus


(and thus in Haskell):
square :: Integer -> Integer
square x = x * x

smaller :: (Integer, Integer) -> Integer


smaller (x,y) = if x<=y then x else y

square (smaller (5, 78))


square (smaller (5, 78)) ⇒ (def. square)
⇒ (def. smaller ) (smaller (5, 78)) × (smaller (5, 78))
square 5 ⇒ (def. smaller )
⇒ (def. square) 5 × (smaller (5, 78))
5×5 ⇒ (def. smaller )
⇒ (def. ×) 5×5
25 ⇒ (def. ×)
25

CS 403: Introduction to functional programming Fall 2022 46 / 51


M ULTIPLE REDUCTIONS ( CONT ’ D )

Sometimes it even matters:


three :: Integer -> Integer
three x = 3

infty :: Integer
infty = infty + 1

three infty
⇒ (def. infty)
three (infty + 1)
⇒ (def. infty) three infty
three ((infty + 1) + 1) ⇒ (def. three)
⇒ (def. infty) 3
three (((infty + 1) + 1) + 1)
..
.

CS 403: Introduction to functional programming Fall 2022 47 / 51


L AZY H ASKELL

Haskell uses the second variant, called lazy evaluation (normal order,
outermost reduction), as opposed to eager evaluation (applicative order,
innermost reduction):
Main> three infty
3
Why is good to be lazy:
Doesn’t hurt: If an irreducible form can be obtained by both kinds of
reduction, then the results are guaranteed to be the same
More robust: If an irreducible form can be obtained, then lazy evaluation is
guaranteed to obtain it
Even useful: It is sometimes useful (and, given the lazy evaluation, possible)
to work with infinite objects

CS 403: Introduction to functional programming Fall 2022 48 / 51


I NFINITE OBJECTS

[1 .. 100] produces the list of numbers between 1 and 100, but what is
produced by [1 .. ]?
Prelude> [1 ..] !! 10
11
Prelude> [1 .. ] !! 12345
12346
Prelude> zip [’a’ .. ’g’] [1 ..]
[(’a’,1),(’b’,2),(’c’,3),(’d’,4),(’e’,5),(’f’,6),(’g’,7)]
A stream of prime numbers:
primes :: [Integer]
primes = sieve [2 .. ]
where sieve (x:xs) = x : [n | n <- sieve xs, mod n x /= 0]
-- alternate:
-- sieve (x:xs) = x : sieve (filter (\ n -> mod n x /= 0) xs)

Main> take 20 primes


[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71]

CS 403: Introduction to functional programming Fall 2022 49 / 51


M EMO FUNCTIONS

Streams can also be used to improve efficiency (dramatically!)


Take the Fibonacci numbers:
fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
Complexity?
Now take them again, using a memo stream:
fastfib :: Integer -> Integer
fastfib n = fibList %% n
where fibList = 1 : 1 : zipWith (+) fibList (tail fibList)
(x:xs) %% 0 = x
(x:xs) %% n = xs %% (n - 1)
Complexity?

CS 403: Introduction to functional programming Fall 2022 50 / 51


M EMO FUNCTIONS

Streams can also be used to improve efficiency (dramatically!)


Take the Fibonacci numbers:
fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
Complexity? O(2n )
Now take them again, using a memo stream:
fastfib :: Integer -> Integer
fastfib n = fibList %% n
where fibList = 1 : 1 : zipWith (+) fibList (tail fibList)
(x:xs) %% 0 = x
(x:xs) %% n = xs %% (n - 1)
Complexity? O(n)
Typical application: dynamic programming

CS 403: Introduction to functional programming Fall 2022 50 / 51


F UNCTIONAL PROGRAMMING

Functional programming Ordinary programming


1. Identify problem Identify problem
2. Assemble information Assemble information
3. Write functions that define the problem Figure out solution
4. Coffee break Program solution
5. Encode problem instance Encode problem instance
as data as data
6. Apply function to data Apply program to data
7. Mathematical analysis Debug procedural errors

CS 403: Introduction to functional programming Fall 2022 51 / 51


CS 403: Introduction to logic programming

Gregory Mierzwinski

Fall 2022
K NOWLEDGE REPRESENTATION
A proposition is a logical statement that can be either false or true
To work with propositions one needs a formal system i.e., a symbolic logic
Predicate calculus or first-order logic is one such a logic
A term is a constant, structure, or variable
An atomic proposition (or predicate) denotes a relation. It is composed of a
functor that names the relation, and an ordered list of terms (parameters):
secure(room), likes(bob, steak ), black(crow), capital(ontario, toronto)
Variables can appear only as arguments. They are free:
capital(ontario, X )
unless bounded by one of the quantifiers ∀ and ∃:
∃ X : capital(ontario, X ) ∀ Y : capital(Y , toronto)
A compound proposition (formula) is composed of atomic propositions,
connected by logical operators: ¬, ∧, ∨, → (⇒). Variables are bound using
quantifires
∀X .(crow(X ) → black(X ))
∃X .(crow(X ) ∧ white(X ))
∀X .(dog(fido) ∧ (dog(X ) → smelly(X )) → smelly(fido))

CS 403: Introduction to logic programming Fall 2022 1 / 41


S EMANTICS OF THE P REDICATE C ALCULUS

The meaning is in the eye of the beholder


Sentences are true with respect to a model and an interpretation
The model contains objects and relations among them (your view of the
world)
An interpretation is a triple I = (D, φ, π), where
D (the domain) is a nonempty set; elements of D are individuals
φ is a mapping that assigns to each constant an element of D
π is a mapping that assigns to each predicate with n arguments a function
p : D n → {True, False} and to each function of k arguments a function
f : Dk → D
The interpretation specifies the following correspondences:
constant symbols → objects (individuals)
predicate symbols → relations
function symbols → functional relations
An atomic sentence predicate(term1 , . . . , termn ) is true iff the objects referred to
by term1 , . . . , termn are in the relation referred to by predicate

CS 403: Introduction to logic programming Fall 2022 2 / 41


S EMANTICS OF THE P REDICATE C ALCULUS ( CONT )

Objects (richard, kingJohn, leg1, leg2), predicates or relations (brother),


functions (leftLegOf)

CS 403: Introduction to logic programming Fall 2022 3 / 41


K NOWLEDGE REPRESENTATION IN P ROLOG

Prolog is a logic/descriptive language


Allows the specification of the problem to be solved using
Known facts about the objects in the universe of the problem (unit clauses):
locked(window).
dark(window).
capital(ontario,toronto).
Rules for inferring new facts from the old ones
Queries or goals about objects and their properties
The system answers such queries, based on the existing facts and rules
?- locked(window).
No
?- [’test.pl’].
Yes
?- locked(window).
Yes
?- locked(door).
No

CS 403: Introduction to logic programming Fall 2022 4 / 41


C ONSTANTS AND VARIABLES

A variable in Prolog is anything that starts with a capital letter or an


underscore (“ ”)
A constant is a number or atom. An atom is:
Anything that starts with a lower case letter followed by letters, digits, and
underscores
Any number of symbols +,-,*,/,\,~,<,>,=,’,^,:,.,?,@,#,$,$,&
Any of the special atoms [],{},!,;,%
Anything surrounded by single quotes: ’atom surrounded by quotes!.’
Escape sequence: just double the escaped character:
’insert ’’ in an atom’
NB: The predicate calculus is called first-order logic because no
predicate can take as argument another predicate, and no predicate can
be a variable

CS 403: Introduction to logic programming Fall 2022 5 / 41


P ROLOG RULES

A Horn clause is a conjunction in which exactly one atomic proposition is


not negated

A ∨ ¬B ∨ ¬C ∨ ¬D
B∧C∧D →A

A sentence that contain exactly one atomic proposition is also a (degenerate


form of a) Horn clause
Note in passing that not all the FOL formulae can be converted into a set of
Horn clauses
A Prolog program is a set of Horn clauses

CS 403: Introduction to logic programming Fall 2022 6 / 41


RULES

Natural Language:
The window is locked. If the light is off and the door is locked, the
room is secure. The light is off if the window is dark. The window is
dark.
Horn clauses:
locked(window)
dark(window)
off(light) ∧ locked(door ) → secure(room)
dark(window) → off(light)

The Prolog program:


dark(window).
locked(window).
secure(room) :- off(light), locked(door).
off(light) :- dark(window).

CS 403: Introduction to logic programming Fall 2022 7 / 41


Q UERIES
Now, one can ask something:
?- off(light).
Yes

?- secure(room).
No

?- locked(door).
No

?- locked(Something).
Something = window
Yes

?- locked(Something).
Something = window ;
No
Query variables are all existentially quantified
CS 403: Introduction to logic programming Fall 2022 8 / 41
C ONJUNCTIVE RULES
A family tree:
parent(ann,bob). parent(ann,calvin).
parent(bob,dave). parent(dave,helen).
parent(X,Y)

parent(ann,bob) parent(ann,calvin) parent(bob,dave) parent(dave,helen)

Other family relations:


grandparent(X,Y) :- parent(X,Z), parent(Z,Y).
siblings(X,Y) :- parent(Z,X), parent(Z,Y) .

All the rule variables are universally quantified


CS 403: Introduction to logic programming Fall 2022 9 / 41
C ONJUNCTIVE RULES
A family tree:
parent(ann,bob). parent(ann,calvin).
parent(bob,dave). parent(dave,helen).
parent(X,Y)

parent(ann,bob) parent(ann,calvin) parent(bob,dave) parent(dave,helen)

Other family relations:


grandparent(X,Y) :- parent(X,Z), parent(Z,Y).
siblings(X,Y) :- parent(Z,X), parent(Z,Y) , not(X = Y).
grandparent(X,Y) siblings(X,Y)

parent(X,Z) parent(Z,Y) parent(Z,X) parent(Z,Y) not (X = Y)

All the rule variables are universally quantified


CS 403: Introduction to logic programming Fall 2022 9 / 41
D ISJUNCTIVE RULES
Yet another family relation:
ancestor(X,Y) :- parent(X,Y).
ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y).
ancestor(X,Y)

parent(X,Y) parent(X,Z) ancestor(Z,Y)

A person is happy if she is healthy, wealthy, or wise:


happy(Smb) :- person(Smb), healthy(Smb).
happy(Smb) :- person(Smb), wealthy(Smb).
happy(Smb) :- person(Smb), wise(Smb).
happy(Smb)

person(Smb) healthy(Smb) person(Smb) wealthy(Smb) person(Smb) wise(Smb)

CS 403: Introduction to logic programming Fall 2022 10 / 41


P REDICATE C ALCULUS PROOFS

Inference rules → sound generation of new sentences from old


Prolog uses generalized modus ponens as inference rule
α1 , . . . , α n α1 ∧ · · · ∧ αn → β
(modus ponens)
β

α1 , . . . , α n
α10 ∧ · · · ∧ αn0 → β
∃ σ : (α1 )σ = (α10 )σ ∧ · · · ∧ (αn )σ = (αn0 )σ (generalized
βσ modus ponens)
Proof → a sequence of applications of inference rules
Generates in effect a logically sound traversal of the proof tree

CS 403: Introduction to logic programming Fall 2022 11 / 41


P ROOF BY CONTRADICTION

KB
Bob is a buffalo 1. buffalo(bob)
Pat is a pig 2. pig(pat)
Buffaloes outrun pigs 3. buffalo(X ) ∧ pig(Y ) → faster (X , Y )
Query
Is something outran
by something else? faster (U, V )
Negated query: 4. faster (U, V ) → 

(1), (2), and (3) with


σ = {X /bob, Y /pat} 5. faster (bob, pat)
(4) and (5) with
σ = {U/bob, V /pat} 

All the substitutions regarding variables appearing in the query are


typically reported (why?)

CS 403: Introduction to logic programming Fall 2022 12 / 41


I NFERENCE AND M ULTIPLE S OLUTIONS
ancestor(ann,X) −> 5 6

{A/ann, B/X} {A/ann, C/X}

parent(ann,X) −> 1 ancestor(ann,B) ancestor(B,X) −> 5 5

{X/bob}

parent(ann,B) parent(B,X) −> 2


{X/bob}
{B/cecil}

parent(cecil,X) −> 3

{X/dave}

(1) parent(ann, bob)


(2) parent(ann, cecil) {X/dave}
(3) parent(cecil, dave)
(4) parent(cecil, eric)
(5) parent(A, B) → ancestor (A, B)
(6) ancestor (A, B) ∧ ancestor (B, C) → ancestor (A, C)
CS 403: Introduction to logic programming Fall 2022 13 / 41
S EARCHING THE KNOWLEDGE BASE
parent(ann,calvin). 2 ?- trace(parent).
parent(ann,bob). parent/2: call redo exit fail
parent(bob,dave). Yes
parent(dave,helen). [debug] 3 ?- parent(ann,X).
T Call: ( 7) parent(ann, _G365)
T Exit: ( 7) parent(ann, calvin)

X = calvin ;
T Redo: ( 7) parent(ann, _G365)
T Exit: ( 7) parent(ann, bob)

X = bob ;
No
?− parent(ann,X).
(1)
X=calvin ;

Call Exit X=bob ;


(2)
(1)

Fail Redo
(2)
No parent(ann,X)
CS 403: Introduction to logic programming Fall 2022 14 / 41
S EARCHING THE KNOWLEDGE BASE ( CONT ’ D )
parent(ann,calvin). parent(ann,bob).
parent(bob,dave). parent(dave,helen).
grandparent(X,Y) :- parent(X,Z),parent(Z,Y).

[debug] 8 ?- grandparent(X,Y). T Redo: (8) parent(_G382, _L224)


T Call: (7) grandparent(_G382, _G383) T Exit: (8) parent(bob, dave)
T Call: (8) parent(_G382, _L224) T Call: (8) parent(dave, _G383)
T Exit: (8) parent(ann, calvin) T Exit: (8) parent(dave, helen)
T Call: (8) parent(calvin, _G383) T Exit: (7) grandparent(bob, helen)
T Fail: (8) parent(calvin, _G383)
T Redo: (8) parent(_G382, _L224) X = bob
T Exit: (8) parent(ann, bob) Y = helen ;
T Call: (8) parent(bob, _G383) T Redo: (8) parent(_G382, _L224)
T Exit: (8) parent(bob, dave) T Exit: (8) parent(dave, helen)
T Exit: (7) grandparent(ann, dave) T Call: (8) parent(helen, _G383)
T Fail: (8) parent(helen, _G383)
X = ann T Fail: (7) grandparent(_G382, _G383)
Y = dave ;
No
CS 403: Introduction to logic programming Fall 2022 15 / 41
S EARCHING THE KNOWLEDGE BASE ( CONT ’ D )

Call Exit Call Exit

Fail Redo Fail Redo

parent(X,Z) parent(Z,Y)

grandparent(X,Y) fail
exit
X/ann
call exit Z/bob
redo Z/dave
X/ann call
Z/calvin X/ann
exit Z/calvin parent(Z,Y)
X/ann call
Z/bob X/ann
parent(X,Z)
Z/bob

call
call exit
exit
X/ann
X/ann Z/bob
Z/calvin
parent(bob,dave)
parent(ann,calvin) parent(ann,bob) parent(dave,helen)

CS 403: Introduction to logic programming Fall 2022 16 / 41


R ECURSIVE PREDICATES

ancestor(X,Y) :- parent(X,Y).
ancestor(X,Y) :- parent(X,Z),ancestor(Z,Y).

A recursive call is treated as a brand new call, with all the variables
renamed

Call Exit Call Exit

Fail Redo Fail Redo

parent(X,Z) ancestor(Z,Y)

ancestor(X,Y)

Call Exit Call Exit

Fail Redo Fail Redo

parent(Z,Z1) ancestor(Z1,Y)

ancestor(Z,Y)

CS 403: Introduction to logic programming Fall 2022 17 / 41


U NIFICATION
There is no explicit assignment in Prolog
Bindings to variables are made through the process of unification, which
is done automatically most of the time
The predicate =/2 is used to request an explicit unification of its two
arguments
?- book(prolog,X) = book(Y,brna).
X = brna
Y = prolog
The binding {X/brna,Y/prolog} is the most general unifier
The most general unifier can contain free variables: the general unifier of
book(prolog,X) = book(Y,Z) is {Y/brna,X/Z}
even if {Y/prolog,X/brna,Z/brna} is also a unifier, it is not the most general
In passing, note that the following predicates are different, even if they
have the same name
tuple(1,2). % tuple/2 ?- tuple(X,Y).
tuple(1,2,3). % tuple/3 X = 1
tuple(a,b,c). % tuple/3 Y = 2 ;
tuple(a,b,c,d). % tuple/4 No
CS 403: Introduction to logic programming Fall 2022 18 / 41
U NIFICATION ALGORITHM
algorithm U NIFY(T1 , T2 , S) returns substitution or FAILURE:
Input: T1 , T2 : the structures to unify; S: the substitution representing the
variable bindings that are already in place
Initial call is typically made with an empty substitution: U NIFY(T1 , T2 , ∅)
Output: A new substitution (including S) or the special value FAILURE
specifying that the unification has failed
1 if T1 and T2 are both atoms, or bound to atoms in S and T1 == T2
then return S
2 if T1 is a free variable then return S ∪ {T1 /T2 }
3 if T2 is a free variable then return S ∪ {T2 /T1 }
4 if T1 == p(a1 , a2 , . . . , an ) and T2 == p(b1 , b2 , . . . , bn )
(by themselves or because they are bound in S to such values) then
1 for i = 1 to n do
1 let A = U NIFY(ai , bi , S), S = S ∪ A
2 if A == FAILURE then return FALURE
2 return S
5 return FAILURE

CS 403: Introduction to logic programming Fall 2022 19 / 41


U NIFICATION ( CONT ’ D )
Unification can be attempted between any two Prolog entities. Unification
succeeds of fails. As a side effect, free variables may become bound
[debug] 10 ?- parent(ann,Y). [debug] 11 ?- parent(X,ann).
T Call: ( 7) parent(ann, _G371) T Call: ( 7) parent(_G370, ann)
T Exit: ( 7) parent(ann, calvin) T Fail: ( 7) parent(_G370, ann)

Y = calvin No
Yes
Once a variable is bound through some unification process, it cannot
become free again
[debug] 15 ?- X=1, X=2.
T Call: ( 7) _G340=1
T Exit: ( 7) 1=1
T Call: ( 7) 1=2
T Fail: ( 7) 1=2

No
Do not confuse =/2 with assignment!

CS 403: Introduction to logic programming Fall 2022 20 / 41


U NIFICATION AND STRUCTURES
What is the result of X = pair(1,2)?

A structure has the same syntax as a predicate. The difference is that a


structure appears as a parameter
You do not have to define a structure, you just use it.
This is possible because of the unification process

CS 403: Introduction to logic programming Fall 2022 21 / 41


U NIFICATION AND STRUCTURES
What is the result of X = pair(1,2)?
?- X = pair(1,2).
X = pair(1, 2)
A structure has the same syntax as a predicate. The difference is that a
structure appears as a parameter
You do not have to define a structure, you just use it.
This is possible because of the unification process
Example: binary search trees
% if I found the element, then succeed.
member_tree(X,tree(X,L,R)).

% Otherwise, if my element is larger than the current key, then I


% search in the right child.
member_tree(X,tree(Y,L,R)) :- X > Y, member_tree(X,R).

% Eventually (otherwise) search in the left child.


member_tree(X,tree(Y,L,R)) :- X < Y, member_tree(X,L).

% An empty tree cannot contain any element, so anything else fails.


CS 403: Introduction to logic programming Fall 2022 21 / 41
S EARCH TREES ( CONT ’ D )
?- member_tree(3,nil).
No

[debug] ?- member_tree(3,tree(2,tree(1,nil,nil),tree(3,nil,nil))).
T Call: ( 7) member_tree(3, tree(2, tree(1, nil, nil), tree(3, nil, nil)))
T Call: ( 8) member_tree(3, tree(3, nil, nil))
T Exit: ( 8) member_tree(3, tree(3, nil, nil))
T Exit: ( 7) member_tree(3, tree(2, tree(1, nil, nil),tree(3, nil, nil)))
Yes

[debug] ?- member_tree(5,tree(2,tree(1,nil,nil),tree(3,nil,nil))).
T Call: ( 7) member_tree(5, tree(2, tree(1, nil, nil),tree(3, nil, nil)))
T Call: ( 8) member_tree(5, tree(3, nil, nil))
T Call: ( 9) member_tree(5, nil)
T Fail: ( 9) member_tree(5, nil)
T Redo: ( 8) member_tree(5, tree(3, nil, nil))
T Fail: ( 8) member_tree(5, tree(3, nil, nil))
T Redo: ( 7) member_tree(5, tree(2, tree(1, nil, nil),tree(3, nil, nil)))
T Fail: ( 7) member_tree(5, tree(2, tree(1, nil, nil),tree(3, nil, nil)))
No
CS 403: Introduction to logic programming Fall 2022 22 / 41
L ISTS
Lists are nothing special, just a structure named “.”, and containing two
parameters
The first one is the elements at the head of the list,
The second is a structure “.”, or the empty list “[]”
That is, .(X,XS) is equivalent to Haskell’s (x::xs)
The difference from Haskell is given by the absence of types in Prolog: A
list can contain any kind of elements
As in Haskell, there is some syntactic sugar:
One can enumerate the elements: [1,[a,4,10],3]
The expression [X|Y] is equivalent to .(X,Y)
We also have the equivalence between [X,Y,Z|R] and .(X,.(Y,.(Z,R))),
and so on
?- [b,a,d] = [d,a,b].
?- [X|Y] = [a,b,c].
?- [X|Y] = [].
?- [[X1|X2]|X3] = [[1,2,3],4,5].
The absence of types in Prolog is brought to extremes: the list [1] is the
structure .(1,[]). However, the empty list [] is an atom!
CS 403: Introduction to logic programming Fall 2022 23 / 41
L ISTS
Lists are nothing special, just a structure named “.”, and containing two
parameters
The first one is the elements at the head of the list,
The second is a structure “.”, or the empty list “[]”
That is, .(X,XS) is equivalent to Haskell’s (x::xs)
The difference from Haskell is given by the absence of types in Prolog: A
list can contain any kind of elements
As in Haskell, there is some syntactic sugar:
One can enumerate the elements: [1,[a,4,10],3]
The expression [X|Y] is equivalent to .(X,Y)
We also have the equivalence between [X,Y,Z|R] and .(X,.(Y,.(Z,R))),
and so on
?- [b,a,d] = [d,a,b]. → unification failure
?- [X|Y] = [a,b,c]. → X=a,Y=[b,c]
?- [X|Y] = []. → unification failure
?- [[X1|X2]|X3] = [[1,2,3],4,5]. → X1=1,X2=[2,3],X3=[4,5]
The absence of types in Prolog is brought to extremes: the list [1] is the
structure .(1,[]). However, the empty list [] is an atom!
CS 403: Introduction to logic programming Fall 2022 23 / 41
L IST PROCESSING

Membership: member/2

CS 403: Introduction to logic programming Fall 2022 24 / 41


L IST PROCESSING

Membership: member/2
member(X,[X| ]).
member(X,[ |Y]) :- member(X,Y).
What is the answer to the query ?- member(X,[1,2,3,4]).

CS 403: Introduction to logic programming Fall 2022 24 / 41


L IST PROCESSING

Membership: member/2
member(X,[X| ]).
member(X,[ |Y]) :- member(X,Y).
What is the answer to the query ?- member(X,[1,2,3,4]).
Both arguments of member/2 may be bound – if so we write member(+E,+L)
In fact, member/2 also works as member(-E,+L) (first argument free)
The general specification is member(?E,+L) (last argument is bound, but the
first is not necessarily bound)

CS 403: Introduction to logic programming Fall 2022 24 / 41


L IST PROCESSING

Membership: member/2
member(X,[X| ]).
member(X,[ |Y]) :- member(X,Y).
What is the answer to the query ?- member(X,[1,2,3,4]).
Both arguments of member/2 may be bound – if so we write member(+E,+L)
In fact, member/2 also works as member(-E,+L) (first argument free)
The general specification is member(?E,+L) (last argument is bound, but the
first is not necessarily bound)
There are no functions in Prolog. What if we want that our program to
compute a value?

CS 403: Introduction to logic programming Fall 2022 24 / 41


L IST PROCESSING

Membership: member/2
member(X,[X| ]).
member(X,[ |Y]) :- member(X,Y).
What is the answer to the query ?- member(X,[1,2,3,4]).
Both arguments of member/2 may be bound – if so we write member(+E,+L)
In fact, member/2 also works as member(-E,+L) (first argument free)
The general specification is member(?E,+L) (last argument is bound, but the
first is not necessarily bound)
There are no functions in Prolog. What if we want that our program to
compute a value?
We invent a new variable that will be bound to the result by various
unification processes

CS 403: Introduction to logic programming Fall 2022 24 / 41


L IST PROCESSING

Membership: member/2
member(X,[X| ]).
member(X,[ |Y]) :- member(X,Y).
What is the answer to the query ?- member(X,[1,2,3,4]).
Both arguments of member/2 may be bound – if so we write member(+E,+L)
In fact, member/2 also works as member(-E,+L) (first argument free)
The general specification is member(?E,+L) (last argument is bound, but the
first is not necessarily bound)
There are no functions in Prolog. What if we want that our program to
compute a value?
We invent a new variable that will be bound to the result by various
unification processes
A predicate for concatenating (”appending”) two lists: append/3
append([],L,L).
append([X|R],L,[X|R1]) :- append(R,L,R1).

CS 403: Introduction to logic programming Fall 2022 24 / 41


L IST PROCESSING

Membership: member/2
member(X,[X| ]).
member(X,[ |Y]) :- member(X,Y).
What is the answer to the query ?- member(X,[1,2,3,4]).
Both arguments of member/2 may be bound – if so we write member(+E,+L)
In fact, member/2 also works as member(-E,+L) (first argument free)
The general specification is member(?E,+L) (last argument is bound, but the
first is not necessarily bound)
There are no functions in Prolog. What if we want that our program to
compute a value?
We invent a new variable that will be bound to the result by various
unification processes
A predicate for concatenating (”appending”) two lists: append/3
append([],L,L).
append([X|R],L,[X|R1]) :- append(R,L,R1).
What is the result of the query ?- append(X,Y,[1,2,3,4]).

CS 403: Introduction to logic programming Fall 2022 24 / 41


N UMBERS AND OPERATIONS ON NUMBERS

What means “3+4” to Prolog? (as in ?- X = 3 + 4.)

CS 403: Introduction to logic programming Fall 2022 25 / 41


N UMBERS AND OPERATIONS ON NUMBERS

What means “3+4” to Prolog? (as in ?- X = 3 + 4.)


In order to actually evaluate an arithmetic expression, one must use the
operator is(?Var,+Expr):
?- X is 3+4
X = 7
Yes
Example: A Prolog program that receives one number n and computes n!
fact(1,1).
fact(N,R) :- R is N*fact(N-1,R1).

13 ?- fact(1,X).
X = 1
Yes

CS 403: Introduction to logic programming Fall 2022 25 / 41


N UMBERS AND OPERATIONS ON NUMBERS

What means “3+4” to Prolog? (as in ?- X = 3 + 4.)


In order to actually evaluate an arithmetic expression, one must use the
operator is(?Var,+Expr):
?- X is 3+4
X = 7
Yes
Example: A Prolog program that receives one number n and computes n!
fact(1,1).
fact(N,R) :- R is N*fact(N-1,R1).

13 ?- fact(1,X).
X = 1
Yes
14 ?- fact(2,X).
[WARNING: Arithmetic: ‘fact/2’ is not a function]
Exception: ( 8) G185 is 2*fact(2-1, G274) ?
[WARNING: Unhandled exception]

CS 403: Introduction to logic programming Fall 2022 25 / 41


N UMBERS AND OPERATIONS ON NUMBERS

What means “3+4” to Prolog? (as in ?- X = 3 + 4.)


In order to actually evaluate an arithmetic expression, one must use the
operator is(?Var,+Expr):
?- X is 3+4
X = 7
Yes
Example: A Prolog program that receives one number n and computes n!
fact(1,1).

fact(N,R) :- N1 is N-1, fact(N1,R1), R is N*R1.

CS 403: Introduction to logic programming Fall 2022 25 / 41


N UMBERS ( CONT ’ D )

All the expected operators on numbers work as expected


One (annoying) difference: the operator for ≤ is not <=, but =< instead

Given the call fact(5,X), what happens if one requests a new solution
after Prolog answers X=120? Why? How to fix?
fact(1,1).
fact(N,R) :- N1 is N-1, fact(N1,R1), R is N*R1.

?- fact(5,X).

X = 120 ;
???

CS 403: Introduction to logic programming Fall 2022 26 / 41


N EGATION AS FAILURE

Negation in Prolog: not/1 or \+/1


Prolog assumes the closed world paradigm. The negation is therefore
different from logical negation:
?- member(X,[1,2,3]).
X = 1 ;
X = 2 ;
X = 3 ;
No

?- not(member(X,[1,2,3])).
No

?- not(not(member(X,[1,2,3]))).
X = _G332 ;
No
not/1 fails upon resatisfaction (a goal can fail in only one way)
not/1 does not bind variables

CS 403: Introduction to logic programming Fall 2022 27 / 41


N EGATION IN CASE SELECTIONS
positive(X) :- X > 0.
negative(X) :- X < 0.

sign(X,+) :- positive(X).
sign(X,-) :- negative(X).
sign(X,0).

sign1(X,+) :- positive(X).
sign1(X,-) :- negative(X).
sign1(X,0) :- not(positive(X)), not(negative(X)).

?- sign(1,X).
X = + ;
X = 0 ;
No

?- sign1(1,X).
X = + ;
No
CS 403: Introduction to logic programming Fall 2022 28 / 41
S TATE SPACE SEARCH
The concept of state space search is widely used in AI
Idea: a problem can be solved by examining the steps which might be taken
towards its solution
Each action takes the solver to a new state
The solution to such a problem is a list of steps leading from the initial state
to a goal state
Classical example: A Farmer who needs to transport a Goat, a Wolf and
some Cabbage across a river one at a time. The Wolf will eat the Goat if
left unsupervised. Likewise the Goat will eat the Cabbage. How will they
all cross the river?
A state is described by the positions of the Farmer, Goat, Wolf, and Cabbage
The solver can move between states by making a “legal” move (which does
not result in something being eaten)
General form for a state space search problem:
Input:
1 The start state
2 One (or more) goal states or final states
3 The state transition function, or how to get from one state to another
Output: a list of moves or state transitions that lead from the initial state to
one of the final states
CS 403: Introduction to logic programming Fall 2022 29 / 41
S TATE SPACE SEARCH IN P ROLOG

Prolog already does it:


search(Final,Final,[]).
search(Current,Final,[M|Result]) :-
move(Current,SomeState,M),
search(SomeState,Final,Result).
The only trick is that Prolog does not explain how it reached the goal
state; it just states whether a goal state is reachable or not
So we also need to provide a way to report the list of moves (hence the
third parameter)

CS 403: Introduction to logic programming Fall 2022 30 / 41


A SIMPLE STATE SPACE SEARCH PROBLEM

Finding a path in a directed, acyclic graph:


A state is a vertex of the graph
a
distance(a,f,5).
distance(f,g,2). 5 1
distance(a,b,1).
f b
distance(a,d,2).
2
distance(b,c,2).
2 c 3 2
distance(c,d,3).
g d
distance(d,e,6).
6
move(A,B,to(A,B)) :- distance(A,B,_). e

?- search(a,e,R).
R = [to(a,b),to(b,c),to(c,d),to(d,e)] ;
R = [to(a,d),to(d,e)] ;
No
?- search(e,a,R).
No

CS 403: Introduction to logic programming Fall 2022 31 / 41


S EARCHING A STATE SPACE , REVISED
Often, the search space contains cycles. Then, Prolog search strategy
may fail to produce a solution.
move(A,B,to(A,B)) :- distance(A,B,_).
move(A,B,to(A,B)) :- distance(B,A,_).

?- search(a,e,R).
ERROR: Out of local stack
We can use then a generate and test technique:
We keep track of the previously visited states
Then, we generate a new state (as before), but we also test that we haven’t
been in that state already; we proceed forward only if the test succeeds

search(Initial,Final,Result) :- ?- search(a,e,R).
search(Initial,Final,[Initial],Result). R = [to(a, b), to(b, c),
search(Final,Final,_,[]). to(c, d), to(d, e)] ;
search(Crt,Final,Visited,[M|Result]) :- R = [to(a, d), to(d, e)] ;
move(Crt,AState,M), % generate No
not(member(AState,Visited)), % test
search(AState,Final,[AState|Visited],
Result).
CS 403: Introduction to logic programming Fall 2022 32 / 41
T HE PROBLEM - DEPENDENT DEFINITIONS

Things to do for solving a specific state space search problem:


Establish what is a state for your problem and how will you represent it in
Prolog
Establish your state transition function; that is, define the move/3
predicate
Such a predicate should receive a state, and return another state together
with the move that generates it
Upon resatisfaction, a new state should be returned
If no new state is directly accessible from the current one, move/3 should fail

CS 403: Introduction to logic programming Fall 2022 33 / 41


L IMITATIONS

The predicate search/3 works on any finite search space


It exploits the fact that Prolog performs by itself a depth-first search.
Since the depth-first search is not guaranteed to terminate on an infinite search
space, neither is search/3
It is possible to implement a breadth-first search in Prolog
However, this cannot take advantage of the search strategy which is built in the
Prolog interpreter (in fact, it sidesteps it altogether)
Such an implementation is thus more complicated and exceeds the scope of this
course (but if you are really curious, contact me)

CS 403: Introduction to logic programming Fall 2022 34 / 41


O N G OATS , W OLVES , AND C ABBAGE
% A state: [Boat,Cabbage,Goat,Wolf]
% Moving around. We use the "generate and test" paradigm:
move(A,B,M) :- move_attempt(A,B,M), legal(B).

% first, attempt to move the Cabbage, then the Goat, then the Wolf:
move_attempt([B,B,G,W],[B1,B1,G,W], moved(cabbage,B,B1)) :- opposite(B,B1).
move_attempt([G,B,G,W],[G1,B,G1,W], moved(goat,G,G1)) :- opposite(G,G1).
move_attempt([W,B,G,W],[W1,B,G,W1], moved(wolf,W,W1)) :- opposite(W,W1).
%... eventually, move the empty boat:
move_attempt([X,C,G,W],[Y,C,G,W], moved(nothing,X,Y)) :- opposite(X,Y).

opposite(south,north). opposite(north,south).

% Make sure that nothing gets eaten:


legal(State) :- not(conflict(State)).
% we cannot allow the Cabbage and the Goat on the same shore unsupervised
conflict([B,C,C,W]) :- opposite(C,B).
% ... nor the Goat and the Wolf...
conflict([B,C,W,W]) :- opposite(W,B).
% ... but anything else is fine.
CS 403: Introduction to logic programming Fall 2022 35 / 41
O N G OATS , W OLVES , AND C ABBAGE ( CONT ’ D )
?- search([north,north,north,north],
[south,south,south,south], R).

R = [moved(goat, north, south),


moved(nothing, south, north),
moved(cabbage, north, south),
moved(goat, south, north),
moved(wolf, north, south),
moved(nothing, south, north),
moved(goat, north, south)] ;

R = [moved(goat, north, south),


moved(nothing, south, north),
moved(wolf, north, south),
moved(goat, south, north),
moved(cabbage, north, south),
moved(nothing, south, north),
moved(goat, north, south)] ;

CS 403: Introduction to logic programming Fall 2022 36 / 41


O N K NIGHTS AND THEIR TOURS
% The board size is given by the predicate size/1
size(3).
% The position of the Knight is represented by the structure -(X,Y)
% (or X-Y), where X and Y are the coordinates of the square where the
% Knight is located. We represent a move by the position it generates.

% We use, again, the generate and test technique:


move(A,B,B) :- move_attempt(A,B), inside(B).
% There are 8 possible moves in the middle of the board:
move_attempt(I-J, K-L) :- K is I+1, L is J-2.
move_attempt(I-J, K-L) :- K is I+1, L is J+2.
move_attempt(I-J, K-L) :- K is I+2, L is J+1.
move_attempt(I-J, K-L) :- K is I+2, L is J-1.
move_attempt(I-J, K-L) :- K is I-1, L is J+2.
move_attempt(I-J, K-L) :- K is I-1, L is J-2.
move_attempt(I-J, K-L) :- K is I-2, L is J+1.
move_attempt(I-J, K-L) :- K is I-2, L is J-1.
% However, if the Knight is somwhere close to board’s margins, then
% some moves might fall out of the board.
inside(A-B) :- size(Max), A > 0, A =< Max, B > 0, B =< Max.
CS 403: Introduction to logic programming Fall 2022 37 / 41
O N K NIGHTS AND THEIR TOURS ( CONT ’ D )

?- search(1-1,3-3,R).

R = [2-3, 3-1, 1-2, 3-3] ;

R = [3-2, 1-3, 2-1, 3-3] ;

No

3 3
2 2
1 1
1 2 3 1 2 3

CS 403: Introduction to logic programming Fall 2022 38 / 41


VARIATIONS ON A SEARCH THEME
Since our search/3 predicate generates all the possible solutions, we
can use it within another generate and test process!
On a 4 × 4 board, a Knight moves from one square S to another square
D. For a given N, find all the paths between S and D in which the Knight
does not make more than N moves.
search_shorter(S,D,N,Result) :- search(S,D,Result), % generate
length(Result,L), L =< N. % test
% length([],0).
% length([_|T],L) :- length(T,L1), L is L1+1.
?- search_shorter(1-1,4-3,5,R).

R = [2-3, 3-1, 4-3] ; R = [3-2, 2-4, 4-3] ;


R = [2-3, 3-1, 1-2, 2-4, 4-3] ; R = [3-2, 2-4, 1-2, 3-1, 4-3] ;
R = [2-3, 4-4, 3-2, 2-4, 4-3] ; R = [3-2, 1-3, 3-4, 2-2, 4-3] ;
R = [2-3, 4-2, 3-4, 2-2, 4-3] ; No
R = [3-2, 4-4, 2-3, 3-1, 4-3] ;

?- search_shorter(1-1,4-3,4,R).

R = [2-3, 3-1, 4-3] ; R = [3-2, 2-4, 4-3] ; No


CS 403: Introduction to logic programming Fall 2022 39 / 41
VARIATIONS ON A SEARCH THEME ( CONT ’ D )

Given some integer n and two vertices A and B, is there a path from A to
B of weight smaller than n?
a
distance(a,f,5).
distance(f,g,2). 5 1
distance(a,b,1). f b
distance(a,d,2). 2
2 c 2
distance(b,c,2). 3
g d
distance(c,d,3).
6
distance(d,e,6). e
move(A,B,to(A,B,C)) :- distance(A,B,C).
move(A,B,to(A,B,C)) :- distance(B,A,C).
weight([],0).
weight([to(_,_,C)|P],W) :- weight(P,W1), W is W1+C.

smaller(A,B,N,Result) :- search(A,B,Result),
weight(Result,W), W =< N.

CS 403: Introduction to logic programming Fall 2022 40 / 41


L OGIC PROGRAMMING

Logic programming Ordinary programming


1. Identify problem Identify problem
2. Assemble information Assemble information
3. Coffee break Figure out solution
4. Encode information in KB Program solution
5. Encode problem instance as facts Encode problem instance as data
6. Ask queries Apply program to data
7. Find false facts Debug procedural errors

CS 403: Introduction to logic programming Fall 2022 41 / 41


CS 403: Scanning and Parsing

Gregory Mierzwinski

Fall 2022
T HE C OMPILATION P ROCESS
Character stream
Scanner (lexical analysis)

Token stream
Parser (syntax analysis)

Parse tree
Semantic analysis
Abstract syntax tree
Intermediate code optimization

Modified intermediate form


Target code generation
Target language
Target code optimization

Modified target language

Symbol table

CS 403: Scanning and Parsing Fall 2022 1 / 29


T HE L EXICAL A NALYZER
Main role: split the input character stream into tokens
Usually even interacts with the symbol table, inserting identifiers in it
(especially useful for languages that do not require declarations)
This simplifies the design and portability of the parser
A token is a data structure that contains:
The token name = abstract symbol representing a kind of lexical unit
A possibly empty set of attributes
A pattern is a description of the form recognized in the input as a
particular token
A lexeme is a sequence of characters in the source program that matches
a particular pattern of a token and so represents an instance of that token
Most programming languages feature the following tokens
One token for each keyword
One token for each operator or each class of operators (e.g., relational
operators)
One token for all identifiers
One or more tokens for literals (numerical, string, etc.)
One token for each punctuation symbol (parentheses, commas, etc.)
CS 403: Scanning and Parsing Fall 2022 2 / 29
E XAMPLE OF TOKENS AND ATTRIBUTES
printf("Score = %d\n", score);

E = M * C ** 2

CS 403: Scanning and Parsing Fall 2022 3 / 29


E XAMPLE OF TOKENS AND ATTRIBUTES
printf("Score = %d\n", score);
Lexeme Token Attribute
printf id pointer to symbol table entry
( open paren
"Score = %d\n" string
, comma
score id pointer to symbol table entry
) cls paren
; semicolon
E = M * C ** 2

CS 403: Scanning and Parsing Fall 2022 3 / 29


E XAMPLE OF TOKENS AND ATTRIBUTES
printf("Score = %d\n", score);
Lexeme Token Attribute
printf id pointer to symbol table entry
( open paren
"Score = %d\n" string
, comma
score id pointer to symbol table entry
) cls paren
; semicolon
E = M * C ** 2
Lexeme Token Attribute
E id pointer to symbol table entry
= assign
M id pointer to symbol table entry
* mul
C id pointer to symbol table entry
** exp
2 int num numerical value 2
CS 403: Scanning and Parsing Fall 2022 3 / 29
S PECIFICATION OF TOKENS

Token patterns are simple enough so that they can be specified using
regular expressions
Alphabet Σ: a finite set of symbols (e.g. binary digits, ASCII)

CS 403: Scanning and Parsing Fall 2022 4 / 29


S PECIFICATION OF TOKENS

Token patterns are simple enough so that they can be specified using
regular expressions
Alphabet Σ: a finite set of symbols (e.g. binary digits, ASCII)
Strings (not sets!) over an alphabet; empty string: ε
Useful operation: concatenation (· or juxtaposition)
ε is the identity for concatenation (εw = wε = w)

CS 403: Scanning and Parsing Fall 2022 4 / 29


S PECIFICATION OF TOKENS

Token patterns are simple enough so that they can be specified using
regular expressions
Alphabet Σ: a finite set of symbols (e.g. binary digits, ASCII)
Strings (not sets!) over an alphabet; empty string: ε
Useful operation: concatenation (· or juxtaposition)
ε is the identity for concatenation (εw = wε = w)
Language: a countable set of strings
Abuse of notation: For a ∈ Σ we write a instead of {a}
Useful elementary operations: union (∪, +, |) and concatenation (· or
juxtaposition): L1 L2 = L1 · L2 = {w1 w2 : w1 ∈ L1 ∧ w2 ∈ L2 }
Exponentiation: Ln = {w1 w2 · · · wn : ∀ 1 ≤ i ≤ n : wi ∈ L} (so that L0 = )

CS 403: Scanning and Parsing Fall 2022 4 / 29


S PECIFICATION OF TOKENS

Token patterns are simple enough so that they can be specified using
regular expressions
Alphabet Σ: a finite set of symbols (e.g. binary digits, ASCII)
Strings (not sets!) over an alphabet; empty string: ε
Useful operation: concatenation (· or juxtaposition)
ε is the identity for concatenation (εw = wε = w)
Language: a countable set of strings
Abuse of notation: For a ∈ Σ we write a instead of {a}
Useful elementary operations: union (∪, +, |) and concatenation (· or
juxtaposition): L1 L2 = L1 · L2 = {w1 w2 : w1 ∈ L1 ∧ w2 ∈ L2 }
Exponentiation: Ln = {w1 w2 · · · wn : ∀ 1 ≤ i ≤ n : wi ∈ L} (so that L0 = {ε})

CS 403: Scanning and Parsing Fall 2022 4 / 29


S PECIFICATION OF TOKENS

Token patterns are simple enough so that they can be specified using
regular expressions
Alphabet Σ: a finite set of symbols (e.g. binary digits, ASCII)
Strings (not sets!) over an alphabet; empty string: ε
Useful operation: concatenation (· or juxtaposition)
ε is the identity for concatenation (εw = wε = w)
Language: a countable set of strings
Abuse of notation: For a ∈ Σ we write a instead of {a}
Useful elementary operations: union (∪, +, |) and concatenation (· or
juxtaposition): L1 L2 = L1 · L2 = {w1 w2 : w1 ∈ L1 ∧ w2 ∈ L2 }
0
Exponentiation: Ln = {wS 1 w2 ·n· · wn : ∀ 1 ≤ i ≤ n : wi ∈ L} (so that L = {ε})
Kleene closure: L = n≥0 L

Positive closure: L+ = n>0 Ln


S

CS 403: Scanning and Parsing Fall 2022 4 / 29


S PECIFICATION OF TOKENS

Token patterns are simple enough so that they can be specified using
regular expressions
Alphabet Σ: a finite set of symbols (e.g. binary digits, ASCII)
Strings (not sets!) over an alphabet; empty string: ε
Useful operation: concatenation (· or juxtaposition)
ε is the identity for concatenation (εw = wε = w)
Language: a countable set of strings
Abuse of notation: For a ∈ Σ we write a instead of {a}
Useful elementary operations: union (∪, +, |) and concatenation (· or
juxtaposition): L1 L2 = L1 · L2 = {w1 w2 : w1 ∈ L1 ∧ w2 ∈ L2 }
0
Exponentiation: Ln = {wS 1 w2 ·n· · wn : ∀ 1 ≤ i ≤ n : wi ∈ L} (so that L = {ε})
Kleene closure: L = n≥0 L

Positive closure: L+ = n>0 Ln


S

An expression containing only symbols from Σ, ε, ∅, union,


concatenation, and Kleene closure is called a regular expression
A language described by a regular expression is a regular language

CS 403: Scanning and Parsing Fall 2022 4 / 29


S YNTACTIC S UGAR FOR R EGULAR E XPRESSIONS

Notation Regular expression


r+ rr ∗ one or more instances (positive closure)
r? r |ε or r + ε or r ∪ ε zero or one instance
[a1 a2 · · · an ] a1 |a2 | · · · |an character class
[a1 − an ] a1 |a2 | · · · |an provided that a1 , a2 , . . . an are in se-
quence
[ˆa1 a2 · · · an ] anything except a1 , a2 , . . . an
[ˆa1 − an ]

The tokens in a programming language are usually given as regular


definitions = collection of named regular languages

CS 403: Scanning and Parsing Fall 2022 5 / 29


E XAMPLES OF R EGULAR D EFINITIONS

letter = [A − Za − z ]
digit = [0 − 9]
id = letter (letter | digit)∗
digits = digit+
fraction = . digits
exp = E [+−]? digits
number = digits fraction? exp?
if = if
then = then
else = else
rel op = < | > | <= | >= | == | ! =

CS 403: Scanning and Parsing Fall 2022 6 / 29


S TATE T RANSITION D IAGRAMS
In order for regular expressions to be used for lexical analysis they must
be “compiled” into state transition diagrams
Also called deterministic finite automata (DFA)
Finite directed graph
Edges (transitions) labeled with symbols from an alphabet
Nodes (states) labeled only for convenience 1

One initial state 0 s0 s1 0

Several accepting states (double circles) 1

A string c1 c2 c3 . . . cn is accepted by a state transition diagram if there


exists a path from the starting state to an accepting state such that the
sequence of labels along the path is c1 , c2 , . . . , cn
c1 c2 c3 cn

Same state might be visited more than once


Intermediate states might be final
The set of exactly all the strings accepted by a state transition diagram is
the language accepted (or recognized) by the state transition diagram
CS 403: Scanning and Parsing Fall 2022 7 / 29
S OFTWARE R EALIZATION
Big practical advantages of DFA: easy and efficient implementation:
Interface to define a vocabulary and a function to obtain the input tokens
typename vocab; /* alphabet + end-of-string */
const vocab EOS; /* end-of-string pseudo-token */
vocab getchr(void); /* returns next symbol */
Variable (state) changed by a simple switch statement as we go along
int main (void) {
typedef enum {S0, S1, ... } state;
state s = S0; vocab t = getchr();
while ( t != EOS ) {
switch (s) {
case S0: if (t == ...) s = ...; break;
if (t == ...) s = ...; break;
...
case S1: ...
...
} /* switch */
t = getchr(); } /* while */
/* accept iff the current state s is final */
}
CS 403: Scanning and Parsing Fall 2022 8 / 29
E XAMPLES OF S TATE T RANSITION D IAGRAMS
< =
0 1 2 return 〈relop, LE〉
> digit
digit
3 return 〈relop, NE〉 other
= oth .

> 4 * return 〈relop, LT〉


digit E
5 return 〈relop, EQ〉
digit
7 return 〈relop, GE〉 other
= E

6 8 * return 〈relop, GT〉


oth +|−
dgt
digit
digit

other
When returning from *-ed states must re-
tract last character
CS 403: Scanning and Parsing Fall 2022 9 / 29
P RACTICAL E XAMPLE : L EX
The L EX language is a programming language particularly suited for
working with regular expressions
Actions can also be specified as fragments of C/C++ code
The L EX compiler compiles the L EX language (e.g., scanner.l) into
C/C++ code (lex.yy.c)
The resulting code is then compiled to produce the actual lexical analyzer
The use of this lexical analyzer is through repeatedly calling the function
yylex() which will return a new token at each invocation
The attribute value (if any) is placed in the global variable yylval
Additional global variable: yytext (the lexeme)

Structure of a L EX program: Declarations include variables,


Declarations constants, regular definitions
%% Transition rules have the form
translation rules
Pattern { Action }
%%
auxiliary functions where the pattern is a regular
expression and the action is
arbitrary C/C++ code
CS 403: Scanning and Parsing Fall 2022 10 / 29
L EX B EHAVIOUR

L EX compile the given regular expressions into one big state transition
diagram, which is then repeatedly run on the input
L EX conflict resolution rules:
Always prefer a longer to a shorter lexeme
If the longer lexeme matches more than one pattern then prefer the pattern
that comes first in the L EX program
L EX always reads one character ahead, but then retracts the lookahead
character upon returning the token
Only the lexeme itself in therefore consumed

CS 403: Scanning and Parsing Fall 2022 11 / 29


C ONTEXT-F REE G RAMMARS
A context-free grammar is a tuple G = (N, Σ, R, S), where
Σ is an alphabet of terminals
N alphabet of symbols called by contrast nonterminals
Traditionally nonterminals are capitalized or surrounded by h and i, everything
else being a terminal
S ∈ N is the axiom (or the start symbol)
R ⊆ N × (N ∪ Σ)∗ is the set of (rewriting) rules or productions
Common ways of expressing (α, β) ∈ R: α → β or α ::= β
Often terminals are quoted (which makes the h and i unnecessary)
Examples:
hstmti ::= ;
hexpi ::= CONST
| VAR = hexpi ;
| VAR
| if ( hexpi ) hstmti else hstmti
| hexpi hopi hexpi
| while ( hexpi ) hstmti
| ( hexpi )
| { hseqi }
hopi ::= +|−|∗|/
hseqi ::= ε | hstmti hseqi

hbalancedi ::= ε
hbalancedi ::= 0 hbalancedi 1

CS 403: Scanning and Parsing Fall 2022 12 / 29


D ERIVATIONS

G = (N, Σ, R, S)
A rewriting rule A ::= v 0 ∈ R is used to rewrite its left-hand side (A) into its
right-hand side (v 0 ):
u⇒v iff ∃ x, y ∈ (N ∪ Σ)∗ : ∃ A ∈ N : u = xAy , v = xv 0 y , A ::= v 0 ∈ R
Rewriting can be chained (⇒∗ , the reflexive and transitive closure of ⇒ =
derivation)
s ⇒∗ s0 iff s = s0 , s ⇒ s0 , or there exist strings s1 , s2 , . . . , sn such that
s ⇒ s1 ⇒ s2 ⇒ · · · ⇒ sn ⇒ s0
hpali ⇒ 0hpali0 ⇒ 01hpali10 ⇒ 010hpali010 ⇒ 0101010

hpali ::= ε | 0 | 1 | 0 hpali 0 | 1 hpali 1

The language generated by grammar G: exactly all the terminal strings


generated from S: L(G) = {w ∈ Σ∗ : S ⇒∗ w}

CS 403: Scanning and Parsing Fall 2022 13 / 29


PARSE T REES

Definition:
1 For every a ∈ N ∪ Σ the following is a parse tree (with yield a): a
2 For every A ::= ε ∈ R the following is a parse tree (with yield ε): A
ε
3 If the following are parse trees (with yields y1 , y2 , . . . , yn , respectively):
A1 A2 An
T1 T2 ... Tn
and A ::= A1 A2 . . . An ∈ R, then the following is a parse tree (w/ yield
y1 y2 . . . yn ):
A

A1 A2 An
T1 T2 ... Tn

Yield: concatenation of leaves in inorder

CS 403: Scanning and Parsing Fall 2022 14 / 29


D ERIVATIONS AND PARSE T REES
Every derivation starting from some nonterminal has an associated parse
tree (rooted at the starting nonterminal)
Two derivations are similar iff only the order of rule application varies =
can obtain one derivation from the other by repeatedly flipping
consecutive rule applications
Two similar derivations have identical parse trees
L R
Can use a “standard” derivation: leftmost (A ⇒∗ w) or rightmost (A ⇒∗ w)

Theorem
The following statements are equivalent:
there exists a parse tree with root A and yield w
A ⇒∗ w
L
A ⇒∗ w
R
A ⇒∗ w

Ambiguity of a grammar: there exists a string that has two derivations


that are not similar (i.e., two derivations with diferent parse trees)
Can be inherent or not — impossible to determine algorithmically
CS 403: Scanning and Parsing Fall 2022 15 / 29
I NHERENT A MBIGUITY IN C++ T EMPLATES

Consider the following code:


int y;
template <class T> void g(T& v) {
T::x(y);
}
The statement T::x(y) can be

CS 403: Scanning and Parsing Fall 2022 16 / 29


I NHERENT A MBIGUITY IN C++ T EMPLATES

Consider the following code:


int y;
template <class T> void g(T& v) {
T::x(y);
}
The statement T::x(y) can be
the function call (member function x of T applied to y), or
the declaration of y as a variable of type T::x.

CS 403: Scanning and Parsing Fall 2022 16 / 29


I NHERENT A MBIGUITY IN C++ T EMPLATES

Consider the following code:


int y;
template <class T> void g(T& v) {
T::x(y);
}
The statement T::x(y) can be
the function call (member function x of T applied to y), or
the declaration of y as a variable of type T::x.
Resolution: unless otherwise stated, an identifier is assumed to refer to
something that is not a type or template.
If we want something else, we use the keyword typename:
T::x(y); // function x of T applied to y
typename T::x(y); // y is a variable of type T::x

CS 403: Scanning and Parsing Fall 2022 16 / 29


PARSING

Interface to lexical analysis:


typename vocab; /* alphabet + end-of-string */
const vocab EOS; /* end-of-string pseudo-token */
vocab gettoken(void); /* returns next token */
Parsing = determining whether the current input belongs to the given
language
In practice a parse tree is constructed in the process as well
General method: Not as efficient as for finite automata
Several possible derivations starting from the axiom, must choose the right
one
Careful housekeeping (dynamic programming) reduces the otherwise
exponential complexity to O(n3 )
We want linear time instead, so we want to determine what to do next based
on the next token in the input

CS 403: Scanning and Parsing Fall 2022 17 / 29


R ECURSIVE D ESCENT PARSING
Construct a function for each nonterminal
Decide which function to call based on the next input token = linear
complexity
vocab t;

void MustBe (vocab ThisToken) {


if (t != ThisToken) { printf("reject"); exit(0); }
t = gettoken();
}
void Balanced (void) { int main (void) {
switch (t) { t = gettoken();
case EOS: Balanced();
case ONE: /* <empty> */ /* accept iff
break; t == EOS */
default: /* 0 <balanced> 1 */ }
MustBe(ZERO);
Balanced();
MustBe(ONE);
}
} /* Balanced */
CS 403: Scanning and Parsing Fall 2022 18 / 29
R ECURSIVE D ESCENT E XAMPLE
typedef enum { VAR, EQ, IF, ELSE, WHILE, OPN_BRACE, CLS_BRACE,
OPN_PAREN, CLS_PAREN, SEMICOLON, EOS } vocab;
vocab gettoken() {...}
vocab t;
void MustBe(vocab ThisToken) {...}

void Statement();
void Sequence();

int main() {
t = gettoken();
Statement();
if (t != EOS) printf("String not accepted\n");
return 0; }
void Sequence() {
if (t == CLS_BRACE) /* <empty> */ ;
else { /* <statement> <sequence> */
Statement();
Sequence();
} }
CS 403: Scanning and Parsing Fall 2022 19 / 29
R ECURSIVE D ESCENT E XAMPLE ( CONT ’ D )
void Statement() {
switch(t) {
case SEMICOLON: /* ; */
t = gettoken();
break;
case VAR: /* <var> = <exp> */
t = gettoken();
MustBe(EQ);
Expression();
MustBe(SEMICOLON);
break;
case IF: /* if (<expr>) <statement> else <statement> */
t = gettoken();
MustBe(OPEN_PAREN);
Expression();
MustBe(CLS_PAREN);
Statement();
MustBe(ELSE);
Statement();
break;
CS 403: Scanning and Parsing Fall 2022 20 / 29
R ECURSIVE D ESCENT E XAMPLE ( CONT ’ D )

case WHILE: /* while (exp) <statement> */


t = gettoken();
MustBe(OPEN_PAREN);
Expression();
MustBe(CLS_PAREN);
Statement();
break;
default: /* { <sequence } */
MustBe(OPN_BRACE);
Sequence();
MustBe(CLS_BRACE);
} /* switch */
} /* Statement () */

CS 403: Scanning and Parsing Fall 2022 21 / 29


PARSE T REES VS . A BSTRACT S YNTAX T REES
In practice the output of a parser is a somehow simplified parse tree
called abstract syntax tree (AST)
Some tokens in the program being parsed have only a syntactic role (to
identify the respective language construct and its components)
Node information might be augmented to replace them
These tokens have no further use and so they are omitted form the AST
Other than this omission the AST looks exactly like a parse tree
Examples of parse trees versus AST
Conditional (parse tree): Consitional (AST):
〈stmt〉 〈if〉

IF OPN PAREN 〈exp〉 CLS PAREN 〈stmt〉 ELSE 〈stmt〉 〈exp〉 〈stmt〉 〈stmt〉

Assignment (parse tree): Assignment (AST):


〈stmt〉 〈assign〉

VAR EQ 〈exp〉 VAR 〈exp〉

CS 403: Scanning and Parsing Fall 2022 22 / 29


C ONSTRUCTING THE PARSE T REE

The parse tree/AST can be constructed through the recursive calls:


Each function creates a current node
The children are populated through recursive calls
The current node is then returned

class Node {...};

Node* Sequence() {
Node* current = new Node(SEQ, ...);
if (t == CLS_BRACE) /* <empty> */ ;
else { /* <statement> <sequence> */
current.addChild(Statement());
current.addChild(Sequence());
}
return current;
}

CS 403: Scanning and Parsing Fall 2022 23 / 29


C ONSTRUCTING THE PARSE T REE ( CONT ’ D )
Node* Statement() {
Node* current;
switch(t) {
case SEMICOLON: /* ; */
t = gettoken();
return new Node(EMPTY);
break;
case VAR: /* <var> = <exp> */
current = new Node(ASSIGN, ...);
current.addChild(VAR, ...);
t = gettoken();
MustBe(EQ);
current.addChild(Expression());
MustBe(SEMICOLON);
break;
case IF: /* if (<expr>) <statement> else <statement> */
current = new Node(COND, ...);
/* ... */
}
return current;
}
CS 403: Scanning and Parsing Fall 2022 24 / 29
R ECURSIVE D ESCENT PARSING : L EFT FACTORING
Not all grammars are suitable for recursive descent:

hstmti ::= ε
| VAR := hexpi
| IF hexpi THEN hstmti ELSE hstmti
| WHILE hexpi DO hstmti
| BEGIN hseqi END
hseqi ::= hstmti | hstmti ; hseqi

CS 403: Scanning and Parsing Fall 2022 25 / 29


R ECURSIVE D ESCENT PARSING : L EFT FACTORING
Not all grammars are suitable for recursive descent:

hstmti ::= ε
| VAR := hexpi
| IF hexpi THEN hstmti ELSE hstmti
| WHILE hexpi DO hstmti
| BEGIN hseqi END
hseqi ::= hstmti | hstmti ; hseqi

Both rules for hseqi begin with the same nonterminal


Impossible to decide which one to apply based only on the next token

CS 403: Scanning and Parsing Fall 2022 25 / 29


R ECURSIVE D ESCENT PARSING : L EFT FACTORING
Not all grammars are suitable for recursive descent:

hstmti ::= ε
| VAR := hexpi
| IF hexpi THEN hstmti ELSE hstmti
| WHILE hexpi DO hstmti
| BEGIN hseqi END
hseqi ::= hstmti | hstmti ; hseqi

Both rules for hseqi begin with the same nonterminal


Impossible to decide which one to apply based only on the next token
Fortunately concatenation is distributive over union so we can fix the
grammar (left factoring):

hseqi ::= hstmti hseqTaili


hseqTaili ::= ε | ; hseqi

CS 403: Scanning and Parsing Fall 2022 25 / 29


R ECURSIVE D ESCENT PARSING : A MBIGUITY

Some programming constructs are inherently ambiguous

hstmti ::= if ( hexpi ) hstmti


| if ( hexpi ) hstmti else hstmti

CS 403: Scanning and Parsing Fall 2022 26 / 29


R ECURSIVE D ESCENT PARSING : A MBIGUITY

Some programming constructs are inherently ambiguous

hstmti ::= if ( hexpi ) hstmti


| if ( hexpi ) hstmti else hstmti

Solution: choose one path and stick to it (e.g., match the else-statement
with the nearest else-less if statement)
case IF:
t = gettoken();
MustBe(OPEN_PAREN);
Expression();
MustBe(CLS_PAREN);
Statement();
if (t == ELSE) {
t = gettoken();
Statement();
}

CS 403: Scanning and Parsing Fall 2022 26 / 29


R ECURSIVE D ESCENT PARSING : C LOSURE , ETC.
Any left recursion in the grammar will cause the parser to go into an
infinite loop:
hexpi ::= hexpi haddopi htermi | htermi

CS 403: Scanning and Parsing Fall 2022 27 / 29


R ECURSIVE D ESCENT PARSING : C LOSURE , ETC.
Any left recursion in the grammar will cause the parser to go into an
infinite loop:
hexpi ::= hexpi haddopi htermi | htermi
Solution: eliminate left recursion using a closure
hexpi ::= htermi hclosurei
hclosurei ::= ε
| haddopi htermi hclosurei

Not the same language theoretically, but differences not relevant in practice
This being said, some languages are simply not parseable using
recursive descent
hpalindromei ::= ε | 0 | 1 | 0 hpalindromei 0 | 1 hpalindromei 1

No way to know when to choose the ε rule


No way to choose between the second and the fourth rule
No way to choose between the third and the fifth rule
CS 403: Scanning and Parsing Fall 2022 27 / 29
R ECURSIVE D ESCENT PARSING : S UFFICIENT
C ONDITIONS
first(α) = set of all initial tokens in the strings derivable from α
follow(hNi) = set of all initial tokens in nonempty strings that may follow
hNi (possibly including EOS)
Sufficient conditions for a grammar to allow recursive descent parsing:
For hNi ::= α1 | α2 | . . . | αn must have first(αi ) ∩ first(αj ) = ∅,
1≤i <j ≤n
Whenever hNi ⇒∗ ε must have follow(hNi) ∩ first(hNi) = ∅
Grammars that do not have these properties may be fixable using left
factoring, closure, etc.

CS 403: Scanning and Parsing Fall 2022 28 / 29


R ECURSIVE D ESCENT PARSING : S UFFICIENT
C ONDITIONS
first(α) = set of all initial tokens in the strings derivable from α
follow(hNi) = set of all initial tokens in nonempty strings that may follow
hNi (possibly including EOS)
Sufficient conditions for a grammar to allow recursive descent parsing:
For hNi ::= α1 | α2 | . . . | αn must have first(αi ) ∩ first(αj ) = ∅,
1≤i <j ≤n
Whenever hNi ⇒∗ ε must have follow(hNi) ∩ first(hNi) = ∅
Grammars that do not have these properties may be fixable using left
factoring, closure, etc.
Method for constructing the recursive descent function N() for the
nonterminal hNi with rules hNi ::= α1 | α2 | . . . | αn :
1 For αi 6= ε apply the rewriting rule hNi ::= αi whenever the next token in the
input is in F IRST(αi )
2 For αi = ε apply the rewriting rule hNi ::= αi (that is, hNi ::= ε) whenever
the next token in the input is in F OLLOW(hNi)
3 Signal a syntax error in all the other cases

CS 403: Scanning and Parsing Fall 2022 28 / 29


S CANNING AND PARSING

Steps to parse a programming language:


Construct a scanner
Express the lexical structure of the language as regular expressions
Convert those regular expressions into a finite automaton (can be
automated) = the scanner
Construct a parser
Express the syntax of the language as a context-free grammar
Adjust the grammar so that it is suitable for recursive descent
Construct the recursive descent parser for the grammar (can be automated)
= the parser
Run the parser on a particular program
This implies calls to the scanner to obtain the tokens
The result is a parse tree, that will be used in the subsequent steps of the
compilation process

CS 403: Scanning and Parsing Fall 2022 29 / 29


CS 403: Semantic Analysis

Gregory Mierzwinski

Fall 2022
T HE C OMPILATION P ROCESS
Character stream
Scanner (lexical analysis)

Token stream
Parser (syntax analysis)

Parse tree
Semantic analysis
Abstract syntax tree
Intermediate code optimization

Modified intermediate form


Target code generation
Target language
Target code optimization

Modified target language

Symbol table

CS 403: Semantic Analysis Fall 2022 1 / 26


S YNTAX D IRECTED T RANSLATION

Syntax-directed translation → the source language translation is


completely driven by the parser
The parsing process and parse trees/AST used to direct semantic analysis
and the translation of the source program
Separate phase of a compiler or grammar augmented with information to
control the semantic analysis and translation (attribute grammars)
Attribute grammars → associate attributes with each grammar symbol
An attribute has a name and an associated value: string, number, type,
memory location, register — whatever information we need.
Examples
Attributes for a variable include type (as declared, useful later in type-checking)
An integer constant will have an attribute value (used later to generate code)
With each grammar rule we also give semantic rules or actions,
describing how to compute the attribute values associated with each
grammar symbol in the rule
An attribute value for a parse node may depend on information from its
children nodes, its siblings, and its parent

CS 403: Semantic Analysis Fall 2022 2 / 26


ATTRIBUTE G RAMMARS AND ACTIONS

Grammar Action(s)

hdigiti ::= 0 {hdigiti.value = 0; }


| 1 {hdigiti.value = 1; }
| 2 {hdigiti.value = 2; }
...
| 9 {hdigiti.value = 9; }

hinti ::= hdigiti {hinti0 .value = hdigiti.value; }


| hintihdigiti {hinti0 .value = hinti1 .value ∗ 10 + hdigiti.value; }

Attributes are computed during the construction of the parse tree and are
typically included in the node objects of that tree
Two general classes of attributes:
Synthesized: passed up in the parse tree
Inherited: passed down the parse tree

CS 403: Semantic Analysis Fall 2022 3 / 26


ATTRIBUTE G RAMMARS AND ACTIONS ( CONT ’ D )
Synthesized attributes: the left-side attribute is computed from the
right-side attributes.
〈int〉
e = 4 ∗ 10 + 2 = 42
X ::= Y1 Y2 . . . Yn
X .a = f (Y1 .a, Y2 .a, . . . , Yn .a)
〈int〉 〈digit〉
e = 4 e = 2
The lexical analyzer supplies the attributes of
terminals 2
〈digit〉
The attributes for nonterminals are built up and e = 4
passed up the tree
4
Inherited attributes: the right-side attributes are derived from the left-side
attributes or other right-side attributes
X ::= Y1 Y2 . . . Yn
Yk .a = f (X .a, Y1 .a, Y2 .a, . . . , Yk−1 .a, Yk +1 .a, . . . , Yn .a)

Used for passing information about the context to nodes further down the
tree
CS 403: Semantic Analysis Fall 2022 4 / 26
I NHERITED ATTRIBUTES ( CONT ’ D )

hPi ::= hDihSi {hSi.dl = hDi.dl; }


hDi ::= var hVi ; hDi {hDi0 .dl = addList(hVi.name, hDi1 .dl); }
| ε {hDi0 .dl = NULL; }
hSi ::= hVi := hEi ; hSi {check(hVi.name, hSi0 .dl); hSi1 .dl = hSi0 .dl; }
| ε {}
hVi ::= x {hVi.name = ”x”; }
| y {hVi.name = ”y”; }
| z {hVi.name = ”z”; }

CS 403: Semantic Analysis Fall 2022 5 / 26


I NHERITED ATTRIBUTES ( CONT ’ D )

hPi ::= hDihSi {hSi.dl = hDi.dl; }


hDi ::= var hVi ; hDi {hDi0 .dl = addList(hVi.name, hDi1 .dl); }
| ε {hDi0 .dl = NULL; }
hSi ::= hVi := hEi ; hSi {check(hVi.name, hSi0 .dl); hSi1 .dl = hSi0 .dl; }
| ε {}
hVi ::= x {hVi.name = ”x”; }
| y {hVi.name = ”y”; }
| z {hVi.name = ”z”; }
Two attributes: name for the name of the variable and dl for the list of
declarations
Each time a new variable is declared a synthesized attribute for its name
is attached to it
That name is added to a list of variables declared so far in the
synthesized attribute dl created from the declaration block

CS 403: Semantic Analysis Fall 2022 5 / 26


I NHERITED ATTRIBUTES ( CONT ’ D )

hPi ::= hDihSi {hSi.dl = hDi.dl; }


hDi ::= var hVi ; hDi {hDi0 .dl = addList(hVi.name, hDi1 .dl); }
| ε {hDi0 .dl = NULL; }
hSi ::= hVi := hEi ; hSi {check(hVi.name, hSi0 .dl); hSi1 .dl = hSi0 .dl; }
| ε {}
hVi ::= x {hVi.name = ”x”; }
| y {hVi.name = ”y”; }
| z {hVi.name = ”z”; }
Two attributes: name for the name of the variable and dl for the list of
declarations
Each time a new variable is declared a synthesized attribute for its name
is attached to it
That name is added to a list of variables declared so far in the
synthesized attribute dl created from the declaration block
The list of variables is then passed as an inherited attribute to the
statements following the declarations so that it can be checked that
variables are declared before use
CS 403: Semantic Analysis Fall 2022 5 / 26
ATTRIBUTE I MPLEMENTATION

Typically handling of attributes: associate with each symbol some sort of


structure (e.g., list) with all the necessary attributes
Then have such a list as a member variable in each node structure
Insert code in each nonterminal function to carry on the attribute
computations
Also need some convention for referring to individual symbols in a rule
while defining the associated action
Typical convention in compiler generators: $$ to refer to the left hand side
and $i to refer to the i-th component of the right hand side:
P -> DS { $2.list = $1.list; }
D -> var V; D { $$.list = add_to_list($2.name, $4.list); }
| { $$.list = NULL; }
S -> V := E; S { check($1.name, $$.list); $5.list = $$.list; }
|
V -> x { $$.name = "x"; }
| y { $$.name = "y"; }
| z { $$.name = "z"; }

CS 403: Semantic Analysis Fall 2022 6 / 26


S EMANTIC A NALYSIS
Parsing only verifies that the program consists of tokens arranged in a
syntactically valid combination – now we move to check whether they
form a sensible set of instructions in the programming language →
semantic analysis
Any noun phrase followed by some verb phrase makes a syntactically
correct English sentence, but a semantically correct one
has subject-verb agreement
has proper use of gender
the components go together to express a sensible idea
For a program to be semantically valid:
all variables, functions, classes, etc. must be properly defined
expressions and variables must be used in ways that respect the type
system
access control must be respected
etc.
Note however that a valid program is not necessariy correct
int Fibonacci(int n) {
if (n <= 1) return 0;
return Fibonacci(n - 1) + Fibonacci(n - 2); }
int main() { Print(Fibonacci(40)); }

CS 403: Semantic Analysis Fall 2022 7 / 26


S EMANTIC A NALYSIS
Parsing only verifies that the program consists of tokens arranged in a
syntactically valid combination – now we move to check whether they
form a sensible set of instructions in the programming language →
semantic analysis
Any noun phrase followed by some verb phrase makes a syntactically
correct English sentence, but a semantically correct one
has subject-verb agreement
has proper use of gender
the components go together to express a sensible idea
For a program to be semantically valid:
all variables, functions, classes, etc. must be properly defined
expressions and variables must be used in ways that respect the type
system
access control must be respected
etc.
Note however that a valid program is not necessariy correct
int Fibonacci(int n) {
if (n <= 1) return 0;
return Fibonacci(n - 1) + Fibonacci(n - 2); }
int main() { Print(Fibonacci(40)); }
Valid but not correct!
CS 403: Semantic Analysis Fall 2022 7 / 26
C HALLENGES IN S EMANTIC A NALYSIS

Reject the largest number of incorrect programs


Accept the largest number of correct programs

CS 403: Semantic Analysis Fall 2022 8 / 26


C HALLENGES IN S EMANTIC A NALYSIS

Reject the largest number of incorrect programs


Accept the largest number of correct programs
Do so quickly!

http://xkcd.com/303/

CS 403: Semantic Analysis Fall 2022 8 / 26


I MPLEMENTATON OF S EMANTIC A NALYSIS

Some semantic analysis done during parsing (syntax directed translation)

Some languages specifically designed for exclusive syntax directed


translation (one-pass compilers)
Other languages require repeat traversals of the AST after parsing

Sample components of semantic analysis: type and scope checking

CS 403: Semantic Analysis Fall 2022 9 / 26


T YPES AND D ECLARATIONS

A type is a set of values and a set of operations operating on those values


Three categories of types in most programming languages:
Base types (int, float, double, char, bool, etc.) → primitive types provided
directly by the underlying hardware
Compound types (enums, arrays, structs, classes, etc.) → types are
constructed as aggregations of the base types
Complex types (lists, stacks, queues, trees, heaps, tables, etc) → abstract
data types, may or may not exist in a language
In many languages the programmer must first establish the name, type,
and lifetime of a data object (variable, function, etc.) through declarations
double calculate(int a, double b); // function declaration (prototype)
int x = 0; // global variables
double y; // (throughout the program)
int main() {
int m[3]; // local variables
char *n; // (available only in main())
...
}

CS 403: Semantic Analysis Fall 2022 10 / 26


T YPE C HECKING
The bulk of semantic analysis = the process of verifying that each
operation respects the type system of the language
Generally means that all operands in any expression are of appropriate
types and number
Sometimes the rules are defined by other parts of the code (e.g., function
prototypes), and sometimes such rules are a part of the language itself (e.g.,
“both operands of a binary arithmetic operation must be of the same type”)
Type checking can be done during compilation, execution, or across both
A language is considered strongly typed if each and every type error is
detected during compilation
Static type checking is done at compile time
The information needed is obtained (e.g., from declarations) and stored in a
symbol table
The types involved in each operation are then checked
It is very difficult for a language that only does static type checking to meet the
full definition of strongly typed (particularly dangerous: casting)
Dynamic type checking is implemented by including type information for
each data location at runtime
For example, a variable of type double would contain both the actual double
value and some kind of tag indicating ”double type”
The execution of any operation begins by first checking these type tags and is
performed only if everything checks out
CS 403: Semantic Analysis Fall 2022 11 / 26
T YPE C HECKING VARIANTS
Static type checking done in most programming languages
Dynamic type checking is done in e.g., LISP, Perl
Many languages have built-in functionality for correcting the simplest of
type errors (implicit type conversion), but others are very strict (Ada,
Pascal, Haskell, etc.)
Implicit conversions can be handy but may also hide serious errors
Classical example in PL/1: declare A, B, C as 3-character arrays, initialize two
and add them together
DECLARE (A, B, C) CHAR(3);
B = "123"; C = "456"; A = B + C;

CS 403: Semantic Analysis Fall 2022 12 / 26


T YPE C HECKING VARIANTS
Static type checking done in most programming languages
Dynamic type checking is done in e.g., LISP, Perl
Many languages have built-in functionality for correcting the simplest of
type errors (implicit type conversion), but others are very strict (Ada,
Pascal, Haskell, etc.)
Implicit conversions can be handy but may also hide serious errors
Classical example in PL/1: declare A, B, C as 3-character arrays, initialize two
and add them together
DECLARE (A, B, C) CHAR(3);
B = "123"; C = "456"; A = B + C;
The result of B+C will be 579 (inplicit conversion to numbers)!

CS 403: Semantic Analysis Fall 2022 12 / 26


T YPE C HECKING VARIANTS
Static type checking done in most programming languages
Dynamic type checking is done in e.g., LISP, Perl
Many languages have built-in functionality for correcting the simplest of
type errors (implicit type conversion), but others are very strict (Ada,
Pascal, Haskell, etc.)
Implicit conversions can be handy but may also hide serious errors
Classical example in PL/1: declare A, B, C as 3-character arrays, initialize two
and add them together
DECLARE (A, B, C) CHAR(3);
B = "123"; C = "456"; A = B + C;
The result of B+C will be 579 (inplicit conversion to numbers)!
Can we assign numbers to strings? Sure, why not! The default width for such a
conversion in PL/1 is 8
So the conversion of 579 back to string will result in " 579"

CS 403: Semantic Analysis Fall 2022 12 / 26


T YPE C HECKING VARIANTS
Static type checking done in most programming languages
Dynamic type checking is done in e.g., LISP, Perl
Many languages have built-in functionality for correcting the simplest of
type errors (implicit type conversion), but others are very strict (Ada,
Pascal, Haskell, etc.)
Implicit conversions can be handy but may also hide serious errors
Classical example in PL/1: declare A, B, C as 3-character arrays, initialize two
and add them together
DECLARE (A, B, C) CHAR(3);
B = "123"; C = "456"; A = B + C;
The result of B+C will be 579 (inplicit conversion to numbers)!
Can we assign numbers to strings? Sure, why not! The default width for such a
conversion in PL/1 is 8
So the conversion of 579 back to string will result in " 579"
Still, the size of A is only 3, so the string gets truncated implicitly
Thus the resulting value stored in A is the counterintuitive " "

CS 403: Semantic Analysis Fall 2022 12 / 26


T YPE C HECKING VARIANTS
Static type checking done in most programming languages
Dynamic type checking is done in e.g., LISP, Perl
Many languages have built-in functionality for correcting the simplest of
type errors (implicit type conversion), but others are very strict (Ada,
Pascal, Haskell, etc.)
Implicit conversions can be handy but may also hide serious errors
Classical example in PL/1: declare A, B, C as 3-character arrays, initialize two
and add them together
DECLARE (A, B, C) CHAR(3);
B = "123"; C = "456"; A = B + C;
The result of B+C will be 579 (inplicit conversion to numbers)!
Can we assign numbers to strings? Sure, why not! The default width for such a
conversion in PL/1 is 8
So the conversion of 579 back to string will result in " 579"
Still, the size of A is only 3, so the string gets truncated implicitly
Thus the resulting value stored in A is the counterintuitive " "
Most type systems rely on declarations
Notable exceptions: functional languages that do not require declarations
but work hard to infer the data types of variables from the code
CS 403: Semantic Analysis Fall 2022 12 / 26
T YPE C HECKER D ESIGN
Design process defining a type system:
1 Identify the types that are available in the language
2 Identify the language constructs that have types associated with them
3 Identify the semantic rules for the language
C++-like language example (declarations required = somewhat strongly
typed)
Base types (int, double, bool, string) + compound types (arrays, classes)
Arrays can be made of any type (including other arrays)
ADTs can be constructed using classes (no need to handle them separately)
Type-related language constructs:
Constants: type given by the lexical analysis
Variables: all variables must have a declared type (base or compound)
Functions: precise type signature (arguments + return)
Expressions: each expression has a type based on the type of the composing
constant, variable, return type of the function, or type of operands
Other constructs (if, while, assignment, etc.) also have associate types (since
they have expressions inside)
Semantic rules govern what types are allowable in the various language
constructs
Rules specific to individual constructs: operand to a unary minus must either be
double or int, expression used in a loop test must be of bool type, etc.
General rules: all variables must be declared, all classes are global, etc.
CS 403: Semantic Analysis Fall 2022 13 / 26
T YPE C HECKING I MPLEMENTATION

First step: record type information with each identifier


The lexical analyzer gives the name
The parser needs to connect that name with the type (based on declaration)
This information is stored in a symbol table
Example declaration: int a; double b;
When building the node for hvari the parser
hdecli ::= hvari; hdecli
can associate the type (int) with the variable
hvari ::= htypei hidentifieri
(a) and create a suitable entry in the symbol
htypei ::= int
table
| bool
Typically the symbol table is stored outside
| double
the parse tree
| string
The class or struct entry in a symbol ta-
| hidentifieri
ble is a table in itself (recording all fields and
| htypei[ ]
their types)

CS 403: Semantic Analysis Fall 2022 14 / 26


T YPE C HECKING I MPLEMENTATION

First step: record type information with each identifier


The lexical analyzer gives the name
The parser needs to connect that name with the type (based on declaration)
This information is stored in a symbol table
Example declaration: int a; double b;
When building the node for hvari the parser
hdecli ::= hvari; hdecli
can associate the type (int) with the variable
hvari ::= htypei hidentifieri
(a) and create a suitable entry in the symbol
htypei ::= int
table
| bool
Typically the symbol table is stored outside
| double
the parse tree
| string
The class or struct entry in a symbol ta-
| hidentifieri
ble is a table in itself (recording all fields and
| htypei[ ]
their types)
Second step: verify language constructs for type consistency
Can be done while parsing (in such a case declarations must precede use)
Can also be done in a subsequent parse tree traversal (more flexible on the
placement of declarations)

CS 403: Semantic Analysis Fall 2022 14 / 26


T YPE C HECKING I MPLEMENTATION ( CONT ’ D )
Second step: verify language constructs for type consistency, continued
1 Verification based on the rules of the grammar

While examining an hexpri + hexpri node hexpri


::= hconsti
the types of the two hexpri must agree with | hidi
each other and be suitable for addition
| hexpri + hexpri
While examining a hidi = hexpri the type of | hexpri/hexpri
hexpri (determined recursively) must agree ...
with the type of hidi (retrieved from the
hstmti ::= hidi = hexpri
symbol table)
...
2 Verification based on the general type rules of the language
Examples:
The index in an array selection must be of integer type
The two operands to logical && must both have bool type; the result is bool type
The type of each actual argument in a function call must be compatible with the
type of the respective formal argument

CS 403: Semantic Analysis Fall 2022 15 / 26


T YPE C HECKING I MPLEMENTATION ( CONT ’ D )
Second step: verify language constructs for type consistency, continued
1 Verification based on the rules of the grammar

While examining an hexpri + hexpri node hexpri


::= hconsti
the types of the two hexpri must agree with | hidi
each other and be suitable for addition
| hexpri + hexpri
While examining a hidi = hexpri the type of | hexpri/hexpri
hexpri (determined recursively) must agree ...
with the type of hidi (retrieved from the
hstmti ::= hidi = hexpri
symbol table)
...
2 Verification based on the general type rules of the language
Examples:
The index in an array selection must be of integer type
The two operands to logical && must both have bool type; the result is bool type
The type of each actual argument in a function call must be compatible with the
type of the respective formal argument
Most semantic checking deals with types, but generally the semantic
analysis must enforce all the rules in the language (type-related or not)
Examples: identifiers are not re-used within the same scope, break only
appears inside a loop, etc.
CS 403: Semantic Analysis Fall 2022 15 / 26
I DENTIFIERS AND ATTRIBUTES

The major attributes of an identifier are:


Name – identify language entities
Type – determines range of values and set of operations
Value – for storable quantities (r-values)
Location (address) – places where values are stored (l-values)
The meaning of names is determined by its attributes
const n = 5; → associates to name n the attributes const and value 5
var x:integer; → associates attributes var and type integer to name x
The declaration
function square root(the integer: integer) :real;
begin . . . end
associates to the name square root:
the attribute function
the names and types of its parameters
the type of the return value
the body of code to be executed when the function is called

CS 403: Semantic Analysis Fall 2022 16 / 26


E QUIVALENCE OF C OMPOUND T YPES

The equivalence of base types is easy to establish (int is only equivalent


to int, bool is only compatible with bool, etc.)
Common technique for compound types: store compound types as a tree
structure
array (arr)

struct {
12 struct
char *s;
int n;
int nums[5]; pointer (s) int (n) array (nums)
} arr [12];
char 5 int

Then the comparison will be done recursively based on the tree structure
(very much like Prolog’s unification)

CS 403: Semantic Analysis Fall 2022 17 / 26


E QUIVALENCE OF C OMPOUND T YPES ( CONT ’ D )

bool AreEquivalent(struct typenode *tree1, struct typenode *tree2) {


if (tree1 == tree2) // if same type pointer, must be equivalent!
return true;
if (tree1->type != tree2->type) // check types first
return false;
switch (tree1->type) {
case T_INT: case T_DOUBLE: ... // same base type
return true;
case T_PTR:
return AreEquivalent(tree1->child[0], tree2->child[0]);
case T_ARRAY:
return AreEquivalent(tree1->child[0], tree2->child[0]) &&
AreEquivalent(tree1->child[1], tree2->child[1]);
...
}
}

CS 403: Semantic Analysis Fall 2022 18 / 26


E QUIVALENCE OF C OMPOUND T YPES ( CONT ’ D )

bool AreEquivalent(struct typenode *tree1, struct typenode *tree2) {


if (tree1 == tree2) // if same type pointer, must be equivalent!
return true;
if (tree1->type != tree2->type) // check types first
return false;
switch (tree1->type) {
case T_INT: case T_DOUBLE: ... // same base type
return true;
case T_PTR:
return AreEquivalent(tree1->child[0], tree2->child[0]);
case T_ARRAY:
return AreEquivalent(tree1->child[0], tree2->child[0]) &&
AreEquivalent(tree1->child[1], tree2->child[1]);
...
}
}

Also needs some way to deal with circular types, such as marking the
visited nodes so that we do not compare them ever again

CS 403: Semantic Analysis Fall 2022 18 / 26


U SER -D EFINED T YPES

When are two custom types equivalent?


Named equivalence: when the two names are identical
Equivalence assessed by name only (just like base types)
Structural equivalence: when the types hold the same kind of data (possibly
recursively)
Equivalence assessed by equivalence of the type trees (as above)
Structural equivalence is not always easy to do, especially on infinite (graph)
types
Named of structural equivalence is a feature of the language
Most (but not all) languages only support named equivalence
Modula-3 and Algol have structural equivalence.
C, Java, C++, and Ada have name equivalence.
Pascal leaves it undefined: up to the implementation

CS 403: Semantic Analysis Fall 2022 19 / 26


T YPE C OMPATIBILITY AND S UBTYPING

Some languages require equivalent types in their constructs


(expressions, assignment, etc.), but most allow for substitutions of
compatible types (implicit coercion)
An int and a double are not equivalent, but a function that takes a double
may take an int instead, since int can be converted into a double without
loss of precision
This coercion affect both the type checker (which must take the possibility
into account) and the code generator (which must generate appropriate
code)
Subtypes are a way of designating compatible types
If a type has all of the behaviour of another type so that it can be freely
substituted to that other type then it is called a subtype of that type
The type checker must be aware of this so that it allows such a substitution
Example: C’s enum is a subtype of int
Example: Inheritance in OO languages allows the definition of subtypes (a
subclass becomes a subtype of the parent class)

CS 403: Semantic Analysis Fall 2022 20 / 26


S COPE C HECKING
Scope constrains the visibility of an identifier to some subsection of the
program
Local variables are only visible in the block in this they are defined
Global variables are visible in the whole program
A scope is a section of the program enclosed by basic program delimiters
such as { } in C
Many languages allow nested scopes
The scope defined by the innermost current such a unit is called the current
scope
The scopes defined by the current scope and any enclosing program units
are open scopes
All other scopes are closed
Scope checking: given a point in the program and an identifier, determine
whether that identifier is accessible at that point
In essence, the program can only int a; // (1)
access identifiers that are in the void bubble(int a) { // (2)
currently open scopes int a; // (3)
In addition, in the event of name a = 2; // (3) wins!
clashes the innermost scope wins }
CS 403: Semantic Analysis Fall 2022 21 / 26
I MPLEMENTATION OF S COPE C HECKING

Scope checking is implemented at the symbol table level, with two


approaches
1 One symbol table per scope organized into a scope stack
When a new scope is opened, a new symbol table is created and pushed on the
stack
When a scope is closed, the top table is popped
All declared identifiers are put in the top table
To find a name we start at the top table and continue our way down until found; if
we do not find it, then the variable is not accessible
2 Single symbol table
Each scope is assigned a number
Each entry in the symbol table contains the number of the enclosing scope
A name is searched in the table in decreasing scope number (higher number
has priority) → need efficient data organization for the symbol table (hash table)
A name may appear in the table more than once as long as the scope numbers
are different
When a new scope is created, the scope number is incremented
When a scope is closed, all entries with that scope number are deleted from the
table and then the current scope number is decremented

CS 403: Semantic Analysis Fall 2022 22 / 26


I MPLEMENTATION OF S COPE C HECKING ( CONT ’ D )

1 Stack of symbol tables


Disadvantages
Overhead in maintaining the stack structure (and creating symbol tables)
Global variables at the bottom of the stack → heavy penalty for accessing
globals
Advantages
Once the symbol table is populated it remains unchanged throughout the
compilation process → more robust code
2 Single symbol table
Disadvantages
Closing a scope can be an expensive operation
Advantages
Efficient access to all scopes (including global variables)

CS 403: Semantic Analysis Fall 2022 23 / 26


S COPING RULES

1 Static (lexical) scoping → each function is called in the environment of its


definition (lexical placement in the source code)
2 Dynamic scoping → a function is called in the environment of its caller
(using the run time stack of function calls)
Static vs dynamic scoping – Food for thought
Scenario: function bubble() accesses variable x
What if there is no x in the enclosing context—can this be determined at
compile time for static scoping? How about dynamic scoping?
What kind of data structures are necessary at compile time and run time to
support static or dynamic scoping?
What can be done with static scoping but not with dynamic scoping and vice
versa?
Over time static scoping has largely won over dynamic scoping; what might
be the reason?

CS 403: Semantic Analysis Fall 2022 24 / 26


S TATIC AND DYNAMIC S COPING E XAMPLE

program static scope example; program dynamic scope example;


var x: integer; var x: integer;
var y: boolean;
procedure p;
procedure p; begin
var x: boolean; writeln(x);
procedure q; end;
var y: integer; procedure q;
begin var x: integer;
y := x; begin
end; x:= 2;
begin p;
end end
begin (*main*)
begin (* main *) x:= 1;
end q;
end

CS 403: Semantic Analysis Fall 2022 25 / 26


S TATIC VS . DYNAMIC S COPING

Static scoping
Method of non local access that works
Getting around restrictions can result in too many globals
C++, Java, Ada, Eiffel, Haskell all use static scoping
Dynamic scoping
Program must be traced to read
Clashes with static typing
Any type error becomes a run-time error!
Access to non local variables takes longer
Used by APL, SNOBOL, LISP (older)
But new LISP (and variants) use static scoping

CS 403: Semantic Analysis Fall 2022 26 / 26


S TATIC VS . DYNAMIC S COPING

Static scoping
Method of non local access that works
Getting around restrictions can result in too many globals
C++, Java, Ada, Eiffel, Haskell all use static scoping
Dynamic scoping
Program must be traced to read
Clashes with static typing
Any type error becomes a run-time error!
Access to non local variables takes longer
Used by APL, SNOBOL, LISP (older)
But new LISP (and variants) use static scoping
Overall static scoping is easier to read, is more reliable, and executes
faster

CS 403: Semantic Analysis Fall 2022 26 / 26


CS 403: Types and Classes

Stefan D. Bruda

Fall 2021
DATA T YPES
Algorithms + data structures = programs
Abstractions of data entities highly desirable
Program semantics embedded in data types
Data types enable semantic checking
Data types can enhance design
Data types can determine memory layout and allocation
Issues:
Extent to which type information is represented in program
How types are constructed
How types are checked (done)
When types are checked (done)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 1 / 25


DATA T YPES
Algorithms + data structures = programs
Abstractions of data entities highly desirable
Program semantics embedded in data types
Data types enable semantic checking
Data types can enhance design
Data types can determine memory layout and allocation
Issues:
Extent to which type information is represented in program
How types are constructed
How types are checked (done)
When types are checked (done)
Data type = set of values + operations on those values
A data type is an algebra
Set of values defined by enumeration, subrange, or mathematical
construction
Set model with membership concept (∈)
Set model of types means language type constructor equivalents for the set
operations ∈, ⊂, ∪, ×
CS 403: Types and Classes (S. D. Bruda) Fall 2021 1 / 25
DATA T YPES ( CONT ’ D )

Determining types
Explicit typing → type determined at declaration, usually invariant
throughout execution (Pascal, C, C++, Java)
Implicit typing → type determined by usage (Prolog, Lisp)
Mixed → implicit typing by default, but allows explicit declarations (Miranda,
Haskell, ML)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 2 / 25


DATA T YPES ( CONT ’ D )

Determining types
Explicit typing → type determined at declaration, usually invariant
throughout execution (Pascal, C, C++, Java)
Implicit typing → type determined by usage (Prolog, Lisp)
Mixed → implicit typing by default, but allows explicit declarations (Miranda,
Haskell, ML)
Simple types
Pre-defined (int, bool, etc.)
Enumeration
enum colour {red, green, blue};
data Colour = Red | Green | Blue
Ordinal (discrete order on elements i.e., not real)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 2 / 25


DATA T YPES ( CONT ’ D )

Determining types
Explicit typing → type determined at declaration, usually invariant
throughout execution (Pascal, C, C++, Java)
Implicit typing → type determined by usage (Prolog, Lisp)
Mixed → implicit typing by default, but allows explicit declarations (Miranda,
Haskell, ML)
Simple types
Pre-defined (int, bool, etc.)
Enumeration
enum colour {red, green, blue};
data Colour = Red | Green | Blue
Ordinal (discrete order on elements i.e., not real)
Type constructors: cartesian product
U × V = {(u, v ) : u ∈ U, v ∈ V }; p1 : U × V → U; p2 : U × V → V
Record implementation struct icr {int i; char c; double r;};
Tuple implementation type Icr = (Int, Char, Double)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 2 / 25


DATA T YPES ( CONT ’ D )
Type constructors: union
Undiscriminated unions → uses names to distinguish components
union intOrReal {int i; double r};
Direct algebraic definition data Either a b = Left a | Right b

CS 403: Types and Classes (S. D. Bruda) Fall 2021 3 / 25


DATA T YPES ( CONT ’ D )
Type constructors: union
Undiscriminated unions → uses names to distinguish components
union intOrReal {int i; double r};
Direct algebraic definition data Either a b = Left a | Right b
Type constructors: function types U → V
Array types → A[i] is a mapping from int to the type of the array
Issues regarding the type of the index:
Restrict to int? (e.g., C)
Restrict to subrange? (e.g., Pascal)
Allow any ordinal type? (e.g., Perl)
Is the index part of the type?
Function types
typedef int (*fun) (int, int)
Int -> Int -> Int

CS 403: Types and Classes (S. D. Bruda) Fall 2021 3 / 25


DATA T YPES ( CONT ’ D )
Type constructors: union
Undiscriminated unions → uses names to distinguish components
union intOrReal {int i; double r};
Direct algebraic definition data Either a b = Left a | Right b
Type constructors: function types U → V
Array types → A[i] is a mapping from int to the type of the array
Issues regarding the type of the index:
Restrict to int? (e.g., C)
Restrict to subrange? (e.g., Pascal)
Allow any ordinal type? (e.g., Perl)
Is the index part of the type?
Function types
typedef int (*fun) (int, int)
Int -> Int -> Int
Type constructors: recursive types
Least fixed point: ∅ ∪ int ∪ int × int ∪ int × int × int ∪ · · ·
struct intList {int key; intList next}; not legal in C++ (infinite
recursion with no base case)
Faked in C++: struct intList {int key; intList *next};
data IntList = Nil | Cons Int IntList
CS 403: Types and Classes (S. D. Bruda) Fall 2021 3 / 25
E XPLICIT P OLYMORPHISM

Advantages of implicit typing:


Smaller code
Can use explicit types if desired/needed
Construction of a most general type → polymorphism
Polymorphic data structures require explicit polymorphism
Example: To avoid separate definitions for IntStack, CharStack,
StringStack, etc., introduce a mechanism to parameterise the data type
declaration
C++:
template <typename T> struct StackNode {
T data;
StackNode<T> *next;
};
template <typename T> struct Stack {
StackNode<T> *theStack;
};
Haskell:
data Stack a = EmptyStack | StackNode a (Stack a)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 4 / 25


A LGEBRAIC DATA T YPE S PECIFICATION

Why data abstraction?


Easier to think about → hide what doesn’t matter
Protection → prevent access to things you shouldn’t see
Plug compatibility
Replacement of pieces often without recompilation, definitely without rewriting
libraries
Division of labor in software projects
An abstract data type specifies the allowed operations and consistency
constraints for some type without specifying an actual implementation
An abstract data type can be specified as an algebra of operations on
entities
An algebraic specification of a data type consists of signatures of
available operations, and axioms defining behaviour
Algebraic types (implicitly) parameterized
Operations divided into constructors and inspectors
Axioms contain rules for each combination of constructor and inspector
Existence of error operations (or restrictions)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 5 / 25


A BSTRACT DATA T YPES AND H ASKELL

ADT Stack Haskell implementation


type Stack(Elm) -- Stack.hs
Operators: module Stack (Stack, empty, push, pop,
top, isEmpty) where
empty : ∅ → Stack
data Stack a = Empty | Push a (Stack a)
push : Elm × Stack → Stack deriving Show
pop : Stack → Stack
empty :: Stack a
top : Stack → Elm push :: a -> Stack a -> Stack a
isEmpty : Stack → Bool pop :: Stack a -> Stack a
top :: Stack a -> a
Axioms: isEmpty :: Stack a -> Bool
pop(push(e, S)) = S
empty = Empty
top(push(e, S)) = e push a s = Push a s
isEmpty(empty) = true pop (Push e s) = s
pop (Empty) = error "pop (empty)."
isEmpty(push(e, S)) = false top (Push e s) = e
Restrictions: top (Empty) = error "top (empty)"
isEmpty (Push e s) = False
pop(empty ) isEmpty Empty = True
top(empty)


CS 403: Types and Classes (S. D. Bruda) Fall 2021 6 / 25


A BSTRACT DATA T YPES AND H ASKELL ( CONT ’ D )
-- main.hs
module Main where
import Stack

aStack = push 0 (push 1 (push 2 empty))

Main> aStack
Push 0 (Push 1 (Push 2 Empty))
Main> :type aStack
aStack :: Stack Integer
Main> isEmpty aStack
False
Main> pop aStack
Push 1 (Push 2 Empty)
Main> let aStack = push 0 empty in pop (pop aStack)

Program error: pop (empty)

Main>
CS 403: Types and Classes (S. D. Bruda) Fall 2021 7 / 25
S IMPLIFIED S TACK IN H ASKELL
-- Stack.hs Main> aStack
module Stack where Push 0 (Push 1 (Push 2 Empty))
data Stack a = Empty | Main> :r
Push a (Stack a) Main> aStack
deriving Show Push 0 (Push 1 (Push 2 Empty))
Main> :type aStack
pop :: Stack a -> Stack a aStack :: Stack Integer
top :: Stack a -> a Main> isEmpty aStack
isEmpty :: Stack a -> Bool False
Main> pop aStack
pop (Push e s) = s Push 1 (Push 2 Empty)
pop (Empty) = error "pop (empty)." Main> let aStack = Push 0 Empty
top (Push e s) = e in pop (pop aStack)
top (Empty) = error "top (empty)"
isEmpty (Push e s) = False Program error: pop (empty).
isEmpty Empty = True

-- main.hs
module Main where
import Stack

CS 403: Types and Classes (S. D. Bruda) Fall 2021 8 / 25


M ORE S AMPLE ADT S : Q UEUES

type Queue(Elm)
Operators:
create : ∅ → Queue
enqueue : Elm × Queue → Queue
dequeue : Queue → Queue
front : Queue → Elm
isEmpty : Queue → Bool
Axioms:
front(enqueue(e, Q)) = if (isEmpty(Q)) then e else front(Q)
dequeue(enqueue(e, Q)) = if (isEmpty(Q)) then Q
else enqueue(e, dequeue(Q))
isEmpty(create) = true
isEmpty(enqueue(e, Q)) = false
Restrictions:
dequeue(empty)
front(empty)


CS 403: Types and Classes (S. D. Bruda) Fall 2021 9 / 25


M ORE S AMPLE ADT S : C OMPLEX N UMBERS

type Complex
Operators:
create : Real × Real → Complex
real : Complex → Real
imaginary : Complex → Real
+ : Complex × Complex → Complex
× : Complex × Complex → Complex
...
Axioms:
real(create(r , i)) = r
imaginary(create(r , i)) = i
real(x + y ) = real(x) + real(y )
imaginary(x + y ) = imaginary(x) + imaginary(y)
real(x × y ) = real(x) × real(y ) − imaginary(x) × imaginary(y)
imaginary(x × y ) = imaginary(x) × real(y ) + real(x) × imaginary(y )
...


CS 403: Types and Classes (S. D. Bruda) Fall 2021 10 / 25


I SSUES WITH A BSTRACT DATA T YPES

1 Problem: Early binding of ADT interface to implementation


Can have only one implementation of any given ADT
Solution: dynamic binding
stack.pop() selects the right function at run time depending on the specific
implementation being used
caller needs not know what implementation is used

CS 403: Types and Classes (S. D. Bruda) Fall 2021 11 / 25


I SSUES WITH A BSTRACT DATA T YPES

1 Problem: Early binding of ADT interface to implementation


Can have only one implementation of any given ADT
Solution: dynamic binding
stack.pop() selects the right function at run time depending on the specific
implementation being used
caller needs not know what implementation is used
2 Problem: Related ADTs are different ADTs
Example: draw(), moveTo(x,y) applicable to several graphical object ADTs
(Square, Circle, Line, etc.)
Cumbersome code for drawing a list of objects of different types
case obj.tag is
when t_square => Square.draw(obj.s);
when t_circle => Circle.draw(obj.s);
when t_line => Line.draw(obj.s);
end case;
Unmaintainable code (what if we later add the Rectangle ADT?)
Solution: dynamic binding and subtyping
automatically select the right draw function for object depending on its type

CS 403: Types and Classes (S. D. Bruda) Fall 2021 11 / 25


I SSUES WITH A BSTRACT DATA T YPES ( CONT ’ D )

3 Problem: Why reimplement every ADT from scratch


ADT are often related, with substantial overlapping in implementation
Solution: inheritance
Define an ADT as a variant of another ADT and specify only the differences:
“FilledSquare is like Square, except . . . ”
Differential programming

CS 403: Types and Classes (S. D. Bruda) Fall 2021 12 / 25


I SSUES WITH A BSTRACT DATA T YPES ( CONT ’ D )

3 Problem: Why reimplement every ADT from scratch


ADT are often related, with substantial overlapping in implementation
Solution: inheritance
Define an ADT as a variant of another ADT and specify only the differences:
“FilledSquare is like Square, except . . . ”
Differential programming

ADT + Dynamic binding + inheritance = object-oriented programming


(OOP)
OOP can be added to an existing language (C++ added over C, CLOS
added over Lisp)
Languages can support OOP but have the same structure and appearance
of imperative languages (Eiffel, Java)
OOP can be integral part of the language yet a subset of the complete
language (type classes in Haskell)
Pure OOP languages (Smalltalk)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 12 / 25


OO C ONCEPTS
A class is an ADT defining
Format of object (instance variables = ”fields”)
Operations on objects (methods = functions)
Operations for creating new objects
Objects are instances of a class
A class may inherit all operations of another class, needs to override only
those that it wants to change
In the simplest case, a class inherits all of the entities of its parents
A class that inherits is a derived class or subclass
The class from which another class inherits is the parent class or
superclass
Subprograms that define operations on objects are called methods or
member functions
The entire collection of methods of an object is called its message
protocol or message interface
Messages have two parts - a method name and the destination object
(often called receiver)
Dynamic dispatch: call specifies operation name (not implementation),
system selects appropriate implementation (function) at runtime
CS 403: Types and Classes (S. D. Bruda) Fall 2021 13 / 25
OO E XAMPLE
class GraphicalObject {
private int x, y;
public void draw() { ... }
public void moveTo(int x, int y) { ... }
}
class line extends GraphicalObject {
public void draw() { ... }
}

GraphicalObject obj;
Line line;
... // initialization, etc.

obj.draw(); // dynamic dispatch


line.draw(); // dynamic dispatch
line.moveTo(0, 0); // inheritance
obj = line; // ok
line = obj; // not ok -- why?

CS 403: Types and Classes (S. D. Bruda) Fall 2021 14 / 25


OO P OLYMORPHISM

OO implements subtype polymorphism


Static type of obj: GraphicalObject
Dynamic type of obj: the static type of any subtype (actual type)
A polymorphic variable references objects of the class as declared as
well as objects of any of its descendants
When a class hierarchy includes classes that override methods and such
methods are called through a polymorphic variable, the binding to the
correct method must be dynamic
This polymorphism simplifies the addition of new methods
More polymorphic mechanisms:
A virtual or abstract method does not include a definition (only defines a
protocol)
A virtual or abstract class is one that includes at least one virtual method
A virtual or abstract class cannot be instantiated
Extreme virtual classes: interfaces (Java), pure virtual classes (C++)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 15 / 25


D ESIGN I SSUES FOR OOPL S

Single and multiple inheritance


Allocation and deallocation of objects
Dynamic and static binding
Multiple levels of hiding
The exclusivity of objects
Are subclasses subtypes?
Implementation and interface inheritance

CS 403: Types and Classes (S. D. Bruda) Fall 2021 16 / 25


M ULTIPLE I NHERITANCE

Multiple inheritance inherits from more than one class


Creates problems
Conflicting definitions (e.g., two or more superclasses define a print
method)
Repeated inheritance: same class inherited more than once
If A is multiply inherited, should A’s instance variables be repeated or not?
It depends: not allowed, can choose per field (Eiffel), can choose per class (C++)
Usefulness of multiple inheritance is not universally accepted
Smalltalk, Beta: single inheritance only
Java: single inheritance of classes but multiple inheritance of interfaces
C++, Eiffel: multiple inheritance

CS 403: Types and Classes (S. D. Bruda) Fall 2021 17 / 25


A LLOCATION AND D EALLOCATION OF O BJECTS

Where are objects allocated?


Normal allocation for the language (stack + heap) or heap only
If they all live on the heap then references to them are uniform
Is deallocation explicit or implicit?
Implicit: overhead (about 10%) and nondeterminism in running time
Explicit: no overhead, deterministic
Errors (as much as 40% of debugging time)
Whose responsibility to deallocate?

CS 403: Types and Classes (S. D. Bruda) Fall 2021 18 / 25


DYNAMIC AND S TATIC B INDING

Should all binding of messages to methods be dynamic?


If dynamic binding is not allowed then one loses the advantages of
dynamic binding
If all are then the code is inefficient
C++ default is static but can use virtual functions to get dynamic binding
Java and Eiffel default is dynamic binding (except for final methods in Java)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 19 / 25


E XCLUSSIVITY OF O BJECTS

Everything is an object
Advantage: elegance, purity, least surprise (everything works in the same
way)
Disadvantage: potential slow operations on simple objects (e.g., float)
However all modern languages where everything is an object have
implementation tricks that minimize the overhead
Notable examples: C++, Haskell
Include an imperative-style typing system for primitive types, but make
everything else objects
Advantage: fast operations on simple objects, relatively small typing system
Disadvantage: confusion caused by the existence of two separate type
systems
Can also have class wrappers for primitive types (such as Integer, Float,
Character, etc. in Java)
Single mainstream example: Java

CS 403: Types and Classes (S. D. Bruda) Fall 2021 20 / 25


S UBCLASSES VERSUS S UBTYPES
Does an is-a relationship hold between a parent class object and an
object of the subclass?
If so, then a variable of the derived type could appear anywhere that a
variable of the parent type is requested
Characteristics of a subclass that guarantees it is a subtype:
Can only add variables and methods, or redefine existing methods in a
compatible way
If the subclass hides some of the parent’s variables and functions or
modifies them in an incompatible way, then it does not create a subtype
So what is a compatible way?
By imposing some restrictions on the use of inheritance, the language can
ensure substitutability
Type extension: If the subclass is allowed to only extend the parent class,
then substitutability is guaranteed (we have subtypes)
The subclass does not modify and does not hide any method
Overriding Methods: The redefined method(s)
must have the same number of parameters
must not enforce any more requirements on the input parameters
may require stronger requirements on the result

CS 403: Types and Classes (S. D. Bruda) Fall 2021 21 / 25


OVERLOADED M ETHODS IN J AVA

Cake → ChockolateCake
%
Dessert
&
Scone → ButteredScone
Definitions
1 Feast(Dessert d, Scone s) { ... }
2 Feast(Cake c, Dessert d) { ... }
3 Feast(ChockolateCake cc, Scone s) { ... }
Usage
Feast(dessertref, sconeref)
Feast(chockolatecakeref, dessertref)
Feast(chockolatecakeref, butteredsconeref)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 22 / 25


OVERLOADED M ETHODS IN J AVA

Cake → ChockolateCake
%
Dessert
&
Scone → ButteredScone
Definitions
1 Feast(Dessert d, Scone s) { ... }
2 Feast(Cake c, Dessert d) { ... }
3 Feast(ChockolateCake cc, Scone s) { ... }
Usage
Feast(dessertref, sconeref) → uses definition 1
Feast(chockolatecakeref, dessertref) → uses definition 2
Feast(chockolatecakeref, butteredsconeref) → uses definition 3

CS 403: Types and Classes (S. D. Bruda) Fall 2021 22 / 25


C ONTRAVARIANCE AND C OVARIANCE

Contravariance for input parameters: the input parameters of the


subclass method must be supertypes of the corresponding parameters of
the overridden method
Covariance on the result: the result of the redefined method must be a
subtype of the result of the overridden method
Very few languages enforce both rules
C++, Java, Object Pascal and Modula-3 require exact identity of the two
methods
Eiffel and Ada require covariance of both the input and result parameters

CS 403: Types and Classes (S. D. Bruda) Fall 2021 23 / 25


S UBCLASS VS . S UBTYPE
Subclass = inheritance
Code reuse
Relationship between implementations
Subtype = polymorphic code
Organization of related concepts
Relationship between types/interfaces
Two different concepts but many languages unify them
Notable exception: Java
Class hierarchy (single inheritance, class B extends class A)
Interface hierarchy (multiple subtyping)
Abstract (deferred) classes = classes with incomplete implementation
Cannot be instantiated
Provide partial implementation with “holes” that are filled in by subclasses
Pure virtual functions in C++
Polymorphism may require dynamic type checking
Dynamic type checking is costly and delays error detection
But, if overriding methods are restricted to having the same type then the
type checking can be static (provided there is no change in visibility in
subclasses)
Example: C++ public inheritance
CS 403: Types and Classes (S. D. Bruda) Fall 2021 24 / 25
J AVA I NTERFACES

class Printable {
void print() { /* default implementation */ } }
class Person extends Printable {
void print() { /* overriding method */ } }

interface Printable { void print() }


class Person implements Printable {
void print() { /* body of method */ }}

With Printable as a class:


Actual parameter must be a subclass
Must inherit the implementation of Printable
Specify both interface and implementation
With Printable as an interface:
Actual parameter must be an instance of a class that implements the
Printable interface
Specifies only interface
Fewer constraints (more reusable)

CS 403: Types and Classes (S. D. Bruda) Fall 2021 25 / 25


CS 403: Subprograms

Stefan D. Bruda

Fall 2021
P ROCEDURES AND PARAMETERS
Two fundamental abstraction facilities: data abstraction and process
abstraction
In particular subprograms simplify complex programs though abstraction
Abstraction of actions
Called by name with arguments: Calculate Pay(pay rate, hours worked)
Single entry (caller is suspended for the duration)
Control always returns to the caller when the subprogram terminates
Procedures (subroutines) and functions (or methods in OOP languages)
Design issues for subprograms
Are local variables static or dynamic?
The local reference environment may be static (historical significance only)
The local reference environment may be stack-based (all modern languages)
What are the parameter passing methods?
Are the types of the actual and formal parameters checked?
Can subprograms be passed as parameters? What is the referencing
environment?
Can subprogram definitions be nested?
Can subprograms be overloaded or generic?
Are side effects allowed?
What type of variables can be returned?
CS 403: Subprograms (S. D. Bruda) Fall 2021 1 / 13
PARAMETER PASSING M ECHANISMS
Pass by value
Ada: The arguments are expressions evaluated at the time of the call
Parameters are constant values
All the parameters in the body of the procedure will be replaced by those values

CS 403: Subprograms (S. D. Bruda) Fall 2021 2 / 13


PARAMETER PASSING M ECHANISMS
Pass by value
Ada: The arguments are expressions evaluated at the time of the call
Parameters are constant values
All the parameters in the body of the procedure will be replaced by those values
Pascal, C: arguments are still expressions evaluated at the time of the call
Now the parameters are local variables, initialized by the arguments from the call
The main method in most programming languages

CS 403: Subprograms (S. D. Bruda) Fall 2021 2 / 13


PARAMETER PASSING M ECHANISMS
Pass by value
Ada: The arguments are expressions evaluated at the time of the call
Parameters are constant values
All the parameters in the body of the procedure will be replaced by those values
Pascal, C: arguments are still expressions evaluated at the time of the call
Now the parameters are local variables, initialized by the arguments from the call
The main method in most programming languages
Pass by reference
The arguments must be variables; then the location of the variable is passed
so the parameter becomes an alias for the argument
Examples of use:
var prefix in Pascal and Modula-2
A reference passed explicitly (& and *) in C and Algol
Arrays are always passed by reference in C and Ada-95
Objects are always passed by reference in Java

CS 403: Subprograms (S. D. Bruda) Fall 2021 2 / 13


PARAMETER PASSING M ECHANISMS
Pass by value
Ada: The arguments are expressions evaluated at the time of the call
Parameters are constant values
All the parameters in the body of the procedure will be replaced by those values
Pascal, C: arguments are still expressions evaluated at the time of the call
Now the parameters are local variables, initialized by the arguments from the call
The main method in most programming languages
Pass by reference
The arguments must be variables; then the location of the variable is passed
so the parameter becomes an alias for the argument
Examples of use:
var prefix in Pascal and Modula-2
A reference passed explicitly (& and *) in C and Algol
Arrays are always passed by reference in C and Ada-95
Objects are always passed by reference in Java
Pass by name/lazy evaluation
The textual representation of the argument replaces the name of the
parameter throughout the body of the function, or
Like pass by value but the argument is not evaluated until its first actual use
Examples of use: Algol60 and many functional languages
CS 403: Subprograms (S. D. Bruda) Fall 2021 2 / 13
T YPE C HECKING OF PARAMETERS

Strongly typed languages require parameters to be checked in type and


number
Procedures cannot have a variable number of parameters
Pass by reference: parameters and arguments must have the same type
Pass by value: the condition above is relaxed to assignment compatibility

CS 403: Subprograms (S. D. Bruda) Fall 2021 3 / 13


S UBPROGRAMS AS PARAMETERS
Subprogram parameters still need to be type
procedure SUB1;
checked (* The static parent of
the passed program *)
The referencing environment can be: var x: integer;
Shallow binding → the environment of the procedure SUB2;
begin
call statement that enacts the passed write(’x = ’, x)
subprogram end;
Deep binding → the environment of the procedure SUB3;
var x: integer;
definition of the passed subprogram (lexical begin
closure) x := 3;
Ad-hoc binding → the environment of the call SUB4(SUB2)
(* the call stmt
statement that passed the subprogram that "enacts"
SUB2 *)
Example: execution of SUB2 when called by end;
SUB4 procedure SUB4(SUBX);
var x: integer;
begin
x := 4;
SUBX
end;
begin
x := 1;
SUB3
end;

CS 403: Subprograms (S. D. Bruda) Fall 2021 4 / 13


S UBPROGRAMS AS PARAMETERS
Subprogram parameters still need to be type
procedure SUB1;
checked (* The static parent of
the passed program *)
The referencing environment can be: var x: integer;
Shallow binding → the environment of the procedure SUB2;
begin
call statement that enacts the passed write(’x = ’, x)
subprogram end;
Deep binding → the environment of the procedure SUB3;
var x: integer;
definition of the passed subprogram (lexical begin
closure) x := 3;
Ad-hoc binding → the environment of the call SUB4(SUB2)
(* the call stmt
statement that passed the subprogram that "enacts"
SUB2 *)
Example: execution of SUB2 when called by end;
SUB4 procedure SUB4(SUBX);
var x: integer;
Shallow binding: x = 4 (the referencing begin
environment is that of SUB4) x := 4;
SUBX
end;
begin
x := 1;
SUB3
end;

CS 403: Subprograms (S. D. Bruda) Fall 2021 4 / 13


S UBPROGRAMS AS PARAMETERS
Subprogram parameters still need to be type
procedure SUB1;
checked (* The static parent of
the passed program *)
The referencing environment can be: var x: integer;
Shallow binding → the environment of the procedure SUB2;
begin
call statement that enacts the passed write(’x = ’, x)
subprogram end;
Deep binding → the environment of the procedure SUB3;
var x: integer;
definition of the passed subprogram (lexical begin
closure) x := 3;
Ad-hoc binding → the environment of the call SUB4(SUB2)
(* the call stmt
statement that passed the subprogram that "enacts"
SUB2 *)
Example: execution of SUB2 when called by end;
SUB4 procedure SUB4(SUBX);
var x: integer;
Shallow binding: x = 4 (the referencing begin
environment is that of SUB4) x := 4;
Deep binding: x = 1 (the referencing SUBX
end;
environment is that of SUB1, the static parent begin
of SUB2) x := 1;
SUB3
end;

CS 403: Subprograms (S. D. Bruda) Fall 2021 4 / 13


S UBPROGRAMS AS PARAMETERS
Subprogram parameters still need to be type
procedure SUB1;
checked (* The static parent of
the passed program *)
The referencing environment can be: var x: integer;
Shallow binding → the environment of the procedure SUB2;
begin
call statement that enacts the passed write(’x = ’, x)
subprogram end;
Deep binding → the environment of the procedure SUB3;
var x: integer;
definition of the passed subprogram (lexical begin
closure) x := 3;
Ad-hoc binding → the environment of the call SUB4(SUB2)
(* the call stmt
statement that passed the subprogram that "enacts"
SUB2 *)
Example: execution of SUB2 when called by end;
SUB4 procedure SUB4(SUBX);
var x: integer;
Shallow binding: x = 4 (the referencing begin
environment is that of SUB4) x := 4;
Deep binding: x = 1 (the referencing SUBX
end;
environment is that of SUB1, the static parent begin
of SUB2) x := 1;
Ad-hoc binding: x = 3 (the referencing SUB3
end;
environment is that of SUB3)
CS 403: Subprograms (S. D. Bruda) Fall 2021 4 / 13
D EEP B INDING IN H ASKELL

increment :: Int -> Int


increment = (+) x -- the first argument of (+) is bound to its
where x = 1 -- value at the point of the definition of
-- increment

foo :: Int -> Int


foo x = 2 * increment x

Main> foo 10
22 -- it would be 40 with shallow binding

CS 403: Subprograms (S. D. Bruda) Fall 2021 5 / 13


ACCESSING N ONLOCAL E NVIRONMENTS
Non local variables are those variables that are visible but not locally
declared
Global variables are visible in all units
Static environments (Fortran and COBOL)
All memory allocation can be performed at load time (static)
Location of variables fixed for the duration of program execution
Functions and procedures cannot be nested
Recursion is not allowed
Stack-based environments
Block structured language with recursion → activation of procedure blocks
cannot be allocated statically
A new activation record is created on the stack when a block is entered and
is released on exit (return)
Space needs to be allocated for local variables, temporary space, and a return
pointer
A dynamic link stores the old environment pointer
A static link points to the static parent (for non-local references)
Must keep a pointer to the current activation record (stack pointer, stored in
a register)

CS 403: Subprograms (S. D. Bruda) Fall 2021 6 / 13


S TACK -B ASED E NVIRONMENT E XAMPLE
Before main calls p:
program envex:
free space
procedure q;
begin
...
end;
procedure p;
begin
...
q;
...
end
begin (*main*)
...
p;
... global data for envex
end.
Stack pointer

CS 403: Subprograms (S. D. Bruda) Fall 2021 7 / 13


S TACK -B ASED E NVIRONMENT E XAMPLE ( CONT ’ D )
p launches:
program envex:
free space
procedure q;
begin
...
end;
procedure p;
begin
...
q;
... activation record for p
end
begin (*main*) Static link
... Dynamic link
p;
... global data for envex
end.
Stack pointer

CS 403: Subprograms (S. D. Bruda) Fall 2021 8 / 13


S TACK -B ASED E NVIRONMENT E XAMPLE ( CONT ’ D )
q launches:
program envex:
free space
procedure q;
begin
...
end;
activation record for q
procedure p;
begin
Static link
...
Dynamic link
q;
... activation record for p
end
begin (*main*) Static link
... Dynamic link
p;
... global data for envex
end.
Stack pointer

CS 403: Subprograms (S. D. Bruda) Fall 2021 9 / 13


S TACK -B ASED E NVIRONMENT E XAMPLE ( CONT ’ D )
q returns:
program envex:
free space
procedure q;
begin
...
end;
activation record for q
procedure p;
begin
Static link
...
Dynamic link
q;
... activation record for p
end
begin (*main*) Static link
... Dynamic link
p;
... global data for envex
end.
Stack pointer

CS 403: Subprograms (S. D. Bruda) Fall 2021 10 / 13


S TACK -B ASED E NVIRONMENT E XAMPLE ( CONT ’ D )
p returns:
program envex:
free space
procedure q;
begin
...
end;
activation record for q
procedure p;
begin
Static link
...
Dynamic link
q;
... activation record for p
end
begin (*main*) Static link
... Dynamic link
p;
... global data for envex
end.
Stack pointer

CS 403: Subprograms (S. D. Bruda) Fall 2021 11 / 13


C ONTENT OF THE ACTIVATION R ECORD

Return address
Contains pointer back to code segment + offset of the address following the
call
Static link
Implements access to non-local variables for deep/lexical binding
Non-local references could be made by searching down the static chain
However, we cannot search at run time (no name information anymore!)
But scopes are known at compile time so the compuler knows the length of
the static chain
Thus a non-local variable is represented by an ordered pair of integers
(chain offset,local offset)
References to variables beyond static parent are costly
Dynamic link
Represents the history of the execution
Implements access to non-local variables for shallow binding
Parameters
Local variables

CS 403: Subprograms (S. D. Bruda) Fall 2021 12 / 13


B LOCKS

Blocks can be treated as parameterless subprograms


Always called from same place
But we nonetheless need to access local as well as non-local variables
The environment can be maintained in a stack-based fashion for any
block structured language with static scoping

CS 403: Subprograms (S. D. Bruda) Fall 2021 13 / 13


CS 403: Pointers, References, and Memory
Management

Stefan D. Bruda

Fall 2021
A LL A BOUT P OINTERS

http://xkcd.com/138/

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 1 / 27
P OINTERS

What is a pointer?
The index of a book contains pointers
A URL (e.g., http://cs.ubishops.ca/home/cs403) is a pointer
A street address is a pointer
What is then a forwarding address?

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 2 / 27
P OINTERS

What is a pointer?
The index of a book contains pointers
A URL (e.g., http://cs.ubishops.ca/home/cs403) is a pointer
A street address is a pointer
What is then a forwarding address? → a pointer to a pointer!
OK, so what is then a (C/C++) pointer?
Often need to refer to some object without making a copy of the object itself
Computer memory contains data which can be accessed using an address
A pointer is nothing more than such an address
Alternatively, computer memory is like an array holding data
A pointer then is an index in such an array
What are pointers physically?

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 2 / 27
P OINTERS

What is a pointer?
The index of a book contains pointers
A URL (e.g., http://cs.ubishops.ca/home/cs403) is a pointer
A street address is a pointer
What is then a forwarding address? → a pointer to a pointer!
OK, so what is then a (C/C++) pointer?
Often need to refer to some object without making a copy of the object itself
Computer memory contains data which can be accessed using an address
A pointer is nothing more than such an address
Alternatively, computer memory is like an array holding data
A pointer then is an index in such an array
What are pointers physically?
Since a pointer is an address, it is usually represented internally as
unsigned int
Pointers can (just as array indices) be stored in variables.

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 2 / 27
C/C++ P OINTERS

d vx; → vx is a variable of type d


d* px; → px is a (variable holding a) pointer to a variable of type d
&vx → denotes the address of vx (i.e., a pointer, of type d*)
*px → denotes the value from the memory location pointed at
by px, of type d (we thus dereference px)

x px ppx

Memory 10 7830 8991

7830 8991 9001


(&x) (&px) (&ppx)

int x = 10; *px = x + 1;


int* px = &x;
int** ppx = &px; cout << x; 11

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 3 / 27
P OINTER T YPES

Do we need a type for a pointer?


Why?
Always?

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 4 / 27
P OINTER T YPES

Do we need a type for a pointer?


Why? → So that we know what is the type of the thing we are referring to
Always? → Yes, unless the pointer does not point to anything (void*)
int x=10;
void* p = &x;
int * pi;
float* pf;
pi = (int*)p;
pf = (float*)p;
cout << "Pointer " << p << " holds the int: "<< *pi
<< " ...and the float: " << *pf;

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 4 / 27
P OINTER T YPES

Do we need a type for a pointer?


Why? → So that we know what is the type of the thing we are referring to
Always? → Yes, unless the pointer does not point to anything (void*)
int x=10;
void* p = &x;
int * pi;
float* pf;
pi = (int*)p;
pf = (float*)p;
cout << "Pointer " << p << " holds the int: "<< *pi
<< " ...and the float: " << *pf;
There is nothing preventing a pointer to point to garbage
Special pointer (of what type?): NULL or 0, which points to nothing

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 4 / 27
P OINTER A RITHMETIC

The types of pointers do matter:


1 We know what we get when we dereference a pointer
2 We can do meaningful pointer arithmetic
Meaningful pointer arithmetic?!?
int i=10; long j=10;
int *x = &i; long *y = &j;
int *x1 = x + 3; long *y1 = y + 3;
int *x2 = x - 2; long *y2 = y - 2;

x−2 x x+3

Memory
0x34791B
0x34791C
0x34791D
0x34791E
0x34791F
0x347920
0x347921
0x347922
0x347923
0x347924
0x437925
0x437926
0x437927
CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 5 / 27
P OINTER A RITHMETIC

The types of pointers do matter:


1 We know what we get when we dereference a pointer
2 We can do meaningful pointer arithmetic
Meaningful pointer arithmetic?!?
int i=10; long j=10;
int *x = &i; long *y = &j;
int *x1 = x + 3; long *y1 = y + 3;
int *x2 = x - 2; long *y2 = y - 2;
y
x−2 x x+3

Memory
0x34791B
0x34791C
0x34791D
0x34791E
0x34791F
0x347920
0x347921
0x347922
0x347923
0x347924
0x437925
0x437926
0x437927
CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 5 / 27
P OINTER A RITHMETIC

The types of pointers do matter:


1 We know what we get when we dereference a pointer
2 We can do meaningful pointer arithmetic
Meaningful pointer arithmetic?!?
int i=10; long j=10;
int *x = &i; long *y = &j;
int *x1 = x + 3; long *y1 = y + 3;
int *x2 = x - 2; long *y2 = y - 2;
y−2 y y+3
x−2 x x+3

Memory
0x34791B
0x34791C
0x34791D
0x34791E
0x34791F
0x347920
0x347921
0x347922
0x347923
0x347924
0x437925
0x437926
0x437927
CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 5 / 27
C A RRAYS AND P OINTERS

A C array is just a pointer to its content:


float nums[6] = {1,2,3}

nums[0]

nums[1]

nums[2]

nums[3]

nums[4]

nums[5]
nums

Memory 72466 1.0 2.0 3.0

10201 72466 72467 72468 72469 72470 72471


In addition, when you declare an array (contiguous) memory space is
reserved to hold its elements
Pointer arithmetic goes hand in hand with C arrays
float nums[6] = {1,2,3};
float* p1 = nums;
float* p2 = nums + 1;
cout << nums[1] << " " << p1[1] << " " << p2[1]; // 2.0 2.0 3.0
In fact arr[index] is perfectly equivalent with *(arr+index)

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 6 / 27
C A RRAYS VERSUS P OINTERS

The following declarations mean almost the same thing:


int* numsP;
int numsA[20];
Indeed:
numsA[2] = 17; → Good
numsP[2] = 17; → Disaster!
Prize for the most uninformative error message goes to
“Segmentation fault.”
However it is perfectly good to do: int numsP[] = {1,2,3};
In other words, one do not have to provide the dimension for an array if
one initializes it at the moment of declaration (e.g., by providing a literal
array)

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 7 / 27
A RRAYS , P OINTERS , AND F UNCTIONS

#include <iostream>
using namespace std;
void translate(char a) {
if (a == ’A’) a = ’5’; else a = ’0’;
}
void translate(char* array, int size) {
for (int i = 0; i < size; i++) {
if (array[i] == ’A’) array[i] = ’5’;
else array[i] = ’0’;
}
}

int main () {
char mark = ’A’; char marks[5] = {’A’,’F’,’A’,’F’,’F’};
translate(mark);
translate(marks,5);
cout << mark << "\n";
for (int i = 0; i < 5; i++)
cout << marks[i] << " ";
cout << "\n";
}

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 8 / 27
A RRAYS , P OINTERS , AND F UNCTIONS

#include <iostream>
using namespace std;
void translate(char a) {
if (a == ’A’) a = ’5’; else a = ’0’;
}
void translate(char* array, int size) {
for (int i = 0; i < size; i++) {
if (array[i] == ’A’) array[i] = ’5’;
else array[i] = ’0’;
}
}

int main () {
char mark = ’A’; char marks[5] = {’A’,’F’,’A’,’F’,’F’};
translate(mark);
translate(marks,5);
cout << mark << "\n";
for (int i = 0; i < 5; i++)
cout << marks[i] << " "; Output:
cout << "\n";
} A
5 0 5 0 0

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 8 / 27
A RRAYS , P OINTERS , AND F UNCTIONS ( CONT ’ D )

#include <iostream>
using namespace std;
void translate(char *a) {
if (*a == ’A’) *a = ’5’; else *a = ’0’;
}
void translate(char* array, int size) {
for (int i = 0; i < size; i++) {
if (array[i] == ’A’) array[i] = ’5’;
else array[i] = ’0’;
}
}

int main () {
char mark = ’A’; char marks[5] = {’A’,’F’,’A’,’F’,’F’};
translate(&mark);
translate(marks,5);
cout << mark << "\n";
for (int i = 0; i < 5; i++)
cout << marks[i] << " "; Output:
cout << "\n";
} 5
5 0 5 0 0

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 9 / 27
P OINTERS AND F UNCTIONS

In C and C++ an argument can be passed to a function using:


Call by value: the value of the argument is passed; argument changes will
not be seen outside the function
int aFunction(int i);
Call by reference: the pointer to the argument is passed to the function;
argument changes will be seen outside the function
int aFunction(int* i);
Used for output arguments (messy, error prone syntax)
Call by constant reference: the pointer to the argument is passed to the
function; but the function is not allowed to change the argument
int aFunction(const int* i);
Used for bulky arguments (still messy syntax)

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 10 / 27
C ALL BY R EFERENCE P OINTER

foo.cc
void increment (int* i) {
*i = *i + 1;
}

void increment1 (const int* i) {


*i = *i + 1;
}

int main () {
int n = 0;
increment(&n);
increment1(&n);
}

g++ -Wall foo.cc


foo.cc: In function ‘void increment1(const int *)’:
foo.cc:9: assignment of read-only location

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 11 / 27
C ALL BY R EFERENCE
foo.cc → no more messy syntax!
void increment (int& i) {
i = i + 1;
}

void increment1 (const int& i) {


i = i + 1;
}

int main () {
int n = 0;
increment(n);
increment1(n);
}

g++ -Wall foo.cc


foo.cc: In function ‘void increment1(const int &)’:
foo.cc:9: assignment of read-only reference ‘i’

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 12 / 27
R EFERENCES AND C ALLING C ONVENTIONS
A reference is just like a pointer, but with a nicer interface
An alias to an object, but it hides such an indirection from the programmer
Must be typed and can only refer to the declared type (int &r; can only
refer to int, etc)
Must always refer to something in C++ but may refer to the null object in
Java
Explicit in C++ (unary operator &) implicit in Java for all objects and
nonexistent for primitive types
Implicit calling conventions:
What Java C++
Primitive types value value
(int, float, etc.)
Arrays reference value
Objects reference value
In C++ everything is passed by value unless explicitly stated otherwise (by
declaring the respective parameter as a reference)
C arrays are apparently passed by reference, but only because of the array
structure (pointer + content)
In Java there is no other way to pass arguments than the implicit one
CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 13 / 27
P OINTERS AND S IMPLE L INKED L ISTS
struct cons_cell {
int car;
cons_cell* cdr; // must use pointers, else the type is infinitely recursive
};
typedef cons_cell* list;

const list nil = 0;


int null (list cons) {
return cons == nil;
}
list cons (int car, list cdr = nil) {
list new_cons = new cons_cell;
new_cons -> car = car; // (*new_cons).car = car;
new_cons -> cdr = cdr; // (*new_cons).cdr = cdr;
return new_cons;
}
int car (list cons) {
return cons -> car;
}

list cdr (list cons) {


return cons -> cdr;
}

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 14 / 27
DYNAMIC M EMORY M ANAGEMENT

new allocates memory for your data. The following are (somehow)
equivalent:
char message[256]; char* pmessage;
pmessage = new char[256];
Exception:
message takes care of itself (i.e., gets deleted when it is no longer in use),
whereas
pmessage however must be explicitly deleted when it is no longer needed:
delete[] pmessage;
Perrils of not using new:
list cons (int car,
list cdr = nil) {
cons_cell new_cons;
new_cons.car = car; int main () {
new_cons.cdr = cdr; list bad = cons(1);
return &new_cons; cout << car(bad); → Boom!
} }

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 15 / 27
DYNAMIC M EMORY M ANAGEMENT ( CONT ’ D )

cons_cell* foo () { int main () {


cons_cell c; int i;
list l = new cons_cell; foo();
return ...; } zap!!
}

Stack

main:
Heap
i int

cons_cell zap
!! foo:
c cons_cell

Memory
Conclusion: foo returns l foo returns c

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 16 / 27
S AY N O TO M EMORY L EAKS
If you create something using new then you must eventually delete it
using delete
list rmth (list cons, int which) {
list place = cons;
for (int i = 0; i < which - 1; i++) {
if (null(place))
break;
place = place -> cdr;
}
if (! null(place) ) {
if (null(cdr(place)))
place -> cdr = nil;
else {
list to delete = cdr(place);
place -> cdr = cdr(place -> cdr);
delete to delete;
}
}
return cons;
}
CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 17 / 27
T HE P ERILS OF D ELETE
An object and all the pointers to it (when they are dereferenced) alias the
same location
Assigning a value through one channel will affect all the other channels
Memory management through one channel will affect all the other channels
as well
Thou shall not leak memory, but also:
Thou shall not leave stale (dangling) pointers behind
char* str = new char[128]; → allocate memory for str
strcpy(str,"hello"); → put something in there (“hello”)
char* p = str; → p points to the same thing
delete [] p; → “hello” is gone,
str is a stale pointer!!
Thou shall not dereference deleted pointers
strcpy(str,"hi"); → str already deleted!!
Thou shall not delete a pointer more than once
delete str; → str already deleted!!
You can however delete null pointers as many times as you wish!

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 18 / 27
T HE P ERILS OF D ELETE
An object and all the pointers to it (when they are dereferenced) alias the
same location
Assigning a value through one channel will affect all the other channels
Memory management through one channel will affect all the other channels
as well
Thou shall not leak memory, but also:
Thou shall not leave stale (dangling) pointers behind
char* str = new char[128]; → allocate memory for str
strcpy(str,"hello"); → put something in there (“hello”)
char* p = str; → p points to the same thing
delete [] p; → “hello” is gone,
str is a stale pointer!!
Thou shall not dereference deleted pointers
strcpy(str,"hi"); → str already deleted!!
Thou shall not delete a pointer more than once
delete str; → str already deleted!!
You can however delete null pointers as many times as you wish!
So assign zero to deleted pointers whenever possible (not a panaceum)
CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 18 / 27
T HE P ERILS OF D ELETE ( CONT ’ D )

// Copy stefan
struct prof { bruda = new prof;
char* name;
char* dept; // (a) Shallow copying
}; bruda−>name = stefan−>name;
char *csc = new char[30]; bruda−>dept = stefan−>dept;
strcpy (csc,"Computer Science");
prof *stefan, *dimitri, *bruda; // Can we delete stefan now??
stefan = new prof; dimitri = new prof; // (b) Deep copying
stefan−>name = new char[30];
dimitri−>name = new char[30]; bruda−>name = new char[30];
strcpy(stefan−>name,"Stefan Bruda"); bruda−>dept = new char[30];
strcpy(dimitri−>name,"Dimitri Vouliouris"); strcpy(bruda.name,stefan.name);
strcpy(bruda.dept,stefan.dept);
stefan−>dept = csc;
dimitri−>dept = csc; Exogenous data // Can we delete stefan now??
// Delete dimitri
delete dimitri−>name; OK
delete dimitri−>dept;
delete dimitri; ???
Indigenous data

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 19 / 27
P OINTERS AS F IRST-O RDER C++ O BJECTS
They are objects that look and feel like pointers, but are smarter
Look like pointers = have the same interface that pointers do

Smarter = do things that regular pointers don’t

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 20 / 27
P OINTERS AS F IRST-O RDER C++ O BJECTS
They are objects that look and feel like pointers, but are smarter
Look like pointers = have the same interface that pointers do
They need to support pointer operations like dereferencing (operator*) and
indirection (operator ->)
An object that looks and feels like something else is called a proxy object, or
just proxy
Smarter = do things that regular pointers don’t

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 20 / 27
P OINTERS AS F IRST-O RDER C++ O BJECTS
They are objects that look and feel like pointers, but are smarter
Look like pointers = have the same interface that pointers do
They need to support pointer operations like dereferencing (operator*) and
indirection (operator ->)
An object that looks and feels like something else is called a proxy object, or
just proxy
Smarter = do things that regular pointers don’t
Such as memory management

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 20 / 27
P OINTERS AS F IRST-O RDER C++ O BJECTS
They are objects that look and feel like pointers, but are smarter
Look like pointers = have the same interface that pointers do
They need to support pointer operations like dereferencing (operator*) and
indirection (operator ->)
An object that looks and feels like something else is called a proxy object, or
just proxy
Smarter = do things that regular pointers don’t
Such as memory management
Simplest example: auto_ptr, included in the standard C++ library.
header: <memory>
template <class T> class auto_ptr {
T* ptr;
public:
explicit auto_ptr(T* p = 0) : ptr(p) {}
~auto_ptr() {delete ptr;}
T& operator*() {return *ptr;}
T* operator->() {return ptr;}
// ...
};
CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 20 / 27
E XAMPLE OF U SE

Instead of: We use:

void foo() void foo()


{ {
MyClass* p(new MyClass); auto_ptr<MyClass> p(new MyClass);
p->DoSomething(); p->DoSomething();
delete p;
} }

p now cleans up after itself.

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 21 / 27
W HY U SE : L ESS B UGS
Automatic cleanup: They clean after themselves, so there is no chance
you will forget to deallocate
Automatic initialization: You all know what a non-initialized pointer does;
the default constructor now does the initialization to zero for you
Stale pointers: As stated before, stale pointers are evil:
MyClass* p(new MyClass);
MyClass* q = p;
delete p;
// p->DoSomething(); // We don’t do that, p is stale
p = 0; // we do this instead
q->DoSomething(); // Ouch, q is still stale!
Smart pointers can set their content to 0 once copied, e.g.
template <class T>
auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs) {
if (this != &rhs) {
delete ptr; ptr = rhs.ptr; rhs.ptr = 0;
}
return *this; }

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 22 / 27
S TALE P OINTERS ( CONT ’ D )

The simplistic strategy to “change ownership” may not be suitable; other


strategies can be implemented:
Deep copy the source into the target
Transfer ownership by letting p and q point to the same object but transfer
the responsibility for cleaning up from p to q
Reference counting: count the references to the object and delete it only
when the count reaches zero
Copy on write: use reference counting as long as the pointer is not modified,
and just before it gets modified copy it and modify the copy
Each strategy has advantages and disadvantages and are suitable for
certain kind of applications

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 23 / 27
W HY U SE : E XCEPTION S AFETY

Our old, simple example. . .


void foo() {
MyClass* p(new MyClass);
p->DoSomething();
delete p;
}
. . . generates a memory leak (at best!)

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 24 / 27
W HY U SE : E XCEPTION S AFETY

Our old, simple example. . .


void foo() {
MyClass* p(new MyClass);
p->DoSomething();
delete p;
}
. . . generates a memory leak (at best!) whenever DoSomething throws an
exception
We could of course take care of it by hand (pretty awkward; imagine now
that you have some loops threw in for good measure):
void foo() {
MyClass* p(new MyClass);
try { p = new MyClass; p->DoSomething(); delete p; }
catch (...) {delete p; throw; }
}
With smart pointers we just let p clean up by itself.

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 24 / 27
W HY U SE : G ARBAGE C OLLECTION , E FFICIENCY

C++ typically lacks garbage collection


But this can be implemented using smart pointers
Simplest form of garbage collection: reference counting
Other, more sophisticated strategies can be implemented in the same spirit
Generally, garbage collection = reference counting + memory compaction

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 25 / 27
W HY U SE : G ARBAGE C OLLECTION , E FFICIENCY

C++ typically lacks garbage collection


But this can be implemented using smart pointers
Simplest form of garbage collection: reference counting
Other, more sophisticated strategies can be implemented in the same spirit
Generally, garbage collection = reference counting + memory compaction
If the object pointed at does not change, there is no need to copy it; ’nuff
said
Copying takes both time and space
Copy on write is our friend here
C++ strings are typically implemented in this manner
string s("Hello");
string t = s; // t and s point to the same
// buffer of characters
t += " there!"; // a new buffer is allocated for t before
// appending, so that s is unchanged

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 25 / 27
W HY U SE : STL C ONTAINERS

STL containers (such as vector) store objects by value


So you cannot store objects of a derived type:
class Base { /*...*/ };
class Derived : public Base { /*...*/ };

Base b; Derived d;
vector<Base> v;

v.push_back(b); // OK
v.push_back(d); // no cake

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 26 / 27
W HY U SE : STL C ONTAINERS

STL containers (such as vector) store objects by value


So you cannot store objects of a derived type:
class Base { /*...*/ };
class Derived : public Base { /*...*/ };

Base b; Derived d;
vector<Base> v;

v.push_back(b); // OK
v.push_back(d); // no cake
You go around this by using pointers:
vector<Base*> v;

v.push_back(new Base); // OK
v.push_back(new Derived); // OK
// obligatory cleanup, disappears when using smart pointers
for (vector<Base*>::iterator i = v.begin(); i != v.end(); ++i)
delete *i;

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 26 / 27
H OW TO C HOOSE YOUR S MART P OINTER

The simplest smart pointer auto ptr is probable suited for local variables
A copied pointer is usually useful for class members
Think copy constructor and you think deep copy
Due to their nature STL containers require garbage collected pointers
(e.g., reference counting)
Whenever you have big objects you are probably better off using copy on
write pointers

CS 403: Pointers, References, and Memory Management (S. D. Bruda) Fall 2021 27 / 27

You might also like