Skript Funktionale Programmierung
Skript Funktionale Programmierung
+
Verification
Winter 2021/22
Helmut Seidl
TUM
1
0 General
• Correctness of programs
• Functional programming with OCaml
2
1 Correctness of Programs
Problem
How can it be guaranteed that a program behaves as it should behave?
3
Approaches
Tool: assertions
4
Approaches
Tool: assertions
5
Example
public class GCD {
public static void main (String[] args) {
int x, y, a, b;
a = read(); x = a;
b = read(); y = b;
while (x != y)
if (x > y) x = x - y;
else y = y - x;
assert(x == y);
write(x);
} // End of definition of main();
} // End of definition of class GCD;
6
Comments
7
Caveat
The run-time check should evaluate a property of the program state
when reaching a particular program point.
Hint
In order to check properties of complicated data-structures, it is
recommended to realize distinct inspector classes whose objects allow to
inspect the data-structure without interference !?
8
Caveat
The run-time check should evaluate a property of the program state
when reaching a particular program point.
Hint
In order to check properties of complicated data-structures, it is
recommended to realize distinct inspector classes whose objects allow to
inspect the data-structure without interference !
9
Problem
==⇒
10
1.1 Program Verification
11
Simplification
For the moment, we consider MiniJava only:
12
Simplification
For the moment, we consider MiniJava only:
Idea
• We annotate each program point with an assertion !
• At every program point, we argue that the assertion is valid ...
logic
13
Simplification
For the moment, we consider MiniJava only:
Idea
• We annotate each program point with a formula !
• At every program point, we prove that the assertion is valid
==⇒ logic
14
Background: Logic
Assertion: “All humans are mortal”,
“Socrates is a human”, “Socrates is mortal”
Deduction:
Tautology:
15
Background: Logic
Assertion: “All humans are mortal”,
“Socrates is a human”, “Socrates is mortal”
∀ x. human( x) ⇒ mortal( x)
human(Socrates), mortal(Socrates)
Deduction:
Tautology:
16
Background: Logic
Assertion: “All humans are mortal”,
“Socrates is a human”, “Socrates is mortal”
∀ x. human( x) ⇒ mortal( x)
human(Socrates), mortal(Socrates)
Tautology:
17
Background: Logic
Assertion: “All humans are mortal”,
“Socrates is a human”, “Socrates is mortal”
∀ x. human( x) ⇒ mortal( x)
human(Socrates), mortal(Socrates)
Tautology: A ∨ ¬A
∀ x ∈ Z. x < 0 ∨ x = 0 ∨ x > 0
18
Background: Logic (cont.)
Laws: ¬¬ A ≡ A double negation
A∧A ≡ A idempotence
A∨A ≡ A
¬( A ∨ B) ≡ ¬ A ∧ ¬ B De Morgan
¬( A ∧ B) ≡ ¬ A ∨ ¬ B
A ∧ ( B ∨ C ) ≡ ( A ∧ B) ∨ ( A ∧ C ) distributivity
A ∨ ( B ∧ C ) ≡ ( A ∨ B) ∧ ( A ∨ C )
A ∨ ( B ∧ A) ≡ A absorption
A ∧ ( B ∨ A) ≡ A
19
Our Example
20
Start
x = a = read();
y = b = read();
no yes
x != y
no yes
write(x); x<y
x=x−y; y=y−x;
Stop
21
Discussion
Background
d | x holds iff x = d · z for some integer z.
For integers x, y, let gcd( x, y) = 0, if x = y = 0, and the greatest
number d which both divides x and y, otherwise.
Then the following laws hold:
22
gcd( x, 0) = | x|
gcd( x, x) = | x|
gcd( x, y) = gcd( x, y − x)
gcd( x, y) = gcd( x − y, y)
23
Idea for the Example
A ≡ gcd( a, b) = gcd( x, y)
B ≡ A∧x = y
24
Idea for the Example
A ≡ gcd( a, b) = gcd( x, y)
B ≡ A∧x = y
25
Our Example
Start
true
x = a = read();
a=x
y = b = read();
A
no yes
x != y
B A
no yes
write(x); x<y
A A
B x=x−y; y=y−x;
Stop
26
Question
How can we prove that the assertions are locally consistent?
Sub-problem 1: Assignments
27
General Principle
28
General Principle
29
General Principle
30
Example
assignment: x = x-y;
post-condition: x>0
weakest pre-condition: x−y > 0
stronger pre-condition: x−y > 2
even stronger pre-condition: x−y = 3
31
... in the GCD Program (1):
assignment: x = x-y;
post-condition: A
weakest pre-condition:
A[ x − y/ x] ≡ gcd( a, b) = gcd( x − y, y)
≡ gcd( a, b) = gcd( x, y)
≡ A
32
... in the GCD Program (2):
assignment: y = y-x;
post-condition: A
weakest pre-condition:
A[ y − x/ y] ≡ gcd( a, b) = gcd( x, y − x)
≡ gcd( a, b) = gcd( x, y)
≡ A
33
Wrap-up
∀ x. B B B[e/x]
x = read(); write(e); x = e;
B B B
WP[[;]]( B) ≡ B
WP[[x = e;]]( B) ≡ B[e/ x]
WP[[x = read();]]( B) ≡ ∀ x. B
WP[[write(e);]]( B) ≡ B
34
Discussion
35
Orientation
Start
true
x = a = read();
a=x
y = b = read();
A
no yes
x != y
B A
no yes
write(x); x<y
A A
B x=x−y; y=y−x;
Stop
36
For the statements: b = read(); y = b; we calculate:
37
For the statements: b = read(); y = b; we calculate:
38
Orientation
Start
true
x = a = read();
a=x
y = b = read();
A
no yes
x != y
B A
no yes
write(x); x<y
A A
B x=x−y; y=y−x;
Stop
39
For the statements: a = read(); x = a; we calculate:
40
Sub-problem 2: Conditionals
A
no yes
b
B0 B1
It should hold:
• A ∧ ¬b ⇒ B0 and
• A ∧ b ⇒ B1 .
41
This is the case, if A implies the weakest pre-condition of the
conditional branching:
WP[[b]] ( B0 , B1 ) ≡ ((¬b) ⇒ B0 ) ∧ (b ⇒ B1 )
42
This is the case, if A implies the weakest pre-condition of the
conditional branching:
WP[[b]] ( B0 , B1 ) ≡ ((¬b) ⇒ B0 ) ∧ (b ⇒ B1 )
WP[[b]] ( B0 , B1 ) ≡ (b ∨ B0 ) ∧ (¬b ∨ B1 )
≡ (¬b ∧ B0 ) ∨ (b ∧ B1 ) ∨ ( B0 ∧ B1 )
≡ (¬b ∧ B0 ) ∨ (b ∧ B1 )
43
Example
44
Example
45
... for the GCD Example
b ≡ y>x
¬b ∧ A ≡ x ≥ y ∧ gcd( a, b) = gcd( x, y)
b∧A ≡ y > x ∧ gcd( a, b) = gcd( x, y)
46
... for the GCD Example
b ≡ y>x
¬b ∧ A ≡ x ≥ y ∧ gcd( a, b) = gcd( x, y)
b∧A ≡ y > x ∧ gcd( a, b) = gcd( x, y)
gcd( a, b) = gcd( x, y)
47
Orientation
Start
true
x = a = read();
a=x
y = b = read();
A
no yes
x != y
B A
no yes
write(x); x<y
A A
B x=x−y; y=y−x;
Stop
48
The argument for the assertion before the loop is analogous:
b ≡ y 6= x
¬b ∧ B ≡ B
b∧A ≡ A ∧ x 6= y
49
Summary of the Approach
A ⇒ WP[[s]]( B)
A ⇒ WP[[b]] ( B0 , B1 )
50
1.2 Correctness
Questions
51
Recap (1)
• In MiniJava, the program state σ consists of a variable
assignment, i.e., a mapping of program variables to integers (their
values), e.g.,
σ = { x 7→ 5, y 7→ −42}
52
Recap (1)
• In MiniJava, the program state σ consists of a variable
assignment, i.e., a mapping of program variables to integers (their
values), e.g.,
σ = { x 7→ 5, y 7→ −42}
A[σ ( x)/ x] x∈ A
We write: σ |= A.
53
Example
σ = { x 7→ 5, y 7→ 2}
A ≡ ( x > y)
A[5/ x, 2/ y] ≡ (5 > 2)
≡ true
54
Example
σ = { x 7→ 5, y 7→ 2}
A ≡ ( x > y)
A[5/ x, 2/ y] ≡ (5 > 2)
≡ true
σ = { x 7→ 5, y 7→ 12}
A ≡ ( x > y)
A[5/ x, 12/ y] ≡ (5 > 12)
≡ false
55
Trivial Properties
σ |= true for every σ
σ |= false for no σ
σ |= A1 and σ |= A2 is equivalent to
σ |= A1 ∧ A2
σ |= A1 or σ |= A2 is equivalent to
σ |= A1 ∨ A2
56
Recap (2)
57
Recap (2)
58
Example
Start
0
x = a = read();
y = b = read();
no yes
5 x != y 2
no yes
write(x); 4 x<y 3
6 x=x−y; y=y−x;
Stop
59
Assume that we start in point 3 with { x 7→ 6, y 7→ 12}.
60
Assume that we start in point 3 with { x 7→ 6, y 7→ 12}.
σ ⊕ { x 7→ d} = { z 7→ σ z | z 6≡ x} ∪ { x 7→ d}
61
Assume that we start in point 3 with { x 7→ 6, y 7→ 12}.
σ ⊕ { x 7→ d} = { z 7→ σ z | z 6≡ x} ∪ { x 7→ d}
{ x 7→ 6, y 7→ 12} ⊕ { y 7→ 6} = { x 7→ 6, y 7→ 6}
62
Theorem
63
Theorem
64
Remarks
65
Remarks
66
Proof
Assumption: σ0 |= A.
Proof obligation: σm |= B.
Idea
Induction on the length m of the execution trace.
Base m = 0:
The endpoint of the execution equals the startpoint.
==⇒
==⇒
67
Proof
Assumption: σ0 |= A.
Proof obligation: σm |= B.
Idea
Induction on the length m of the execution trace.
Base m = 0:
The endpoint of the execution equals the startpoint.
==⇒ σ0 = σm and A ≡ B
==⇒ the claim holds.
68
Important Notion: Evaluation of Expressions
Program State
σ = { x 7→ 5, y 7→ −1, z 7→ 21}
Arithmetic Expression
t ≡ 2∗z+y
Evaluation
69
Proposition
For (arithmethic) expressions t, e,
70
Proposition
For (arithmethic) expressions t, e,
E.g.„ consider t ≡ x + y, e ≡ 2 ∗ z
for σ = { x 7→ 5, y 7→ −1, z 7→ 21}.
71
Proposition
For (arithmethic) expressions t, e,
E.g.„ consider t ≡ x + y, e ≡ 2 ∗ z
for σ = { x 7→ 5, y 7→ −1, z 7→ 21}.
72
Proposition
73
Proposition
Proof
σ ⊕ { x 7→ [[e]] σ } |= t1 < t2
iff [[t1 ]] (σ ⊕ { x 7→ [[e]] σ }) < [[t2 ]] (σ ⊕ { x 7→ [[e]] σ })
iff [[t1 [e/x]]] σ < [[t2 [e/x]]] σ
iff σ |= t1 [e/x] < t2 [e/x] ✷
74
Proposition
for every formula A,
σ ⊕ { x 7→ [[e]] σ } |= A iff σ |= A[e/x]
75
Proposition
for every formula A,
σ ⊕ { x 7→ [[e]] σ } |= A iff σ |= A[e/x]
Proof
Induction on the structure of formula A ✷
76
Induction Proof of Correctness (cont.)
Step m > 0:
Induction Hypothesis: The statement holds already for m − 1.
Let B′ denote the assertion at point um−1 .
==⇒ σm−1 |= B′
Case 1. sm ≡ ;
Then • σm−1 = σm
• WP[[;]] ( B) ≡ B
==⇒ B′ ⇒ B
==⇒ σm−1 = σm |= B ✷
77
Case 2. sm ≡ write(e);
Then • σm−1 = σm
• WP[[;write(e)]] ( B) ≡ B
==⇒ B′ ⇒ B
==⇒ σm−1 = σm |= B ✷
78
Case 2. sm ≡ write(e);
Then • σm−1 = σm
• WP[[;write(e)]] ( B) ≡ B
==⇒ B′ ⇒ B
==⇒ σm−1 = σm |= B ✷
Case 3. sm ≡ x = read();
79
Case 4. sm ≡ x = e; Then we have:
80
Case 4. sm ≡ x = e; Then we have:
81
Induction Proof of Correctness (cont.)
Step m > 0:
Inductive Hypothesis: The statement holds already for m − 1.
Let B′ denote the assertion at point um−1 .
==⇒ σm−1 |= B′
82
Induction Proof of Correctness (cont.)
Step m > 0:
Inductive Hypothesis: The statement holds already for m − 1.
Let B′ denote the assertion at point um−1 .
==⇒ σm−1 |= B′
83
Case 1. σm |= b
84
Case 1. σm |= b
Case 2. σm |= ¬b
85
Conclusion
86
1.3 Optimization
Observation
87
Example
B3
x=x+2;
x = x+2; B2
z = z+x; z=z+x;
i = i+1; B1
i=i+1;
B
88
Example (cont.)
Assume B ≡ z = i2 ∧ x = 2i − 1
Then we calculate:
B1 ≡ WP[[i = i+1;]]( B) ≡ z = (i + 1 )2 ∧ x = 2 (i + 1 ) − 1
≡ z = (i + 1)2 ∧ x = 2i + 1
89
Example (cont.)
Assume B ≡ z = i2 ∧ x = 2i − 1
Then we calculate:
B1 ≡ WP[[i = i+1;]]( B) ≡ z = (i + 1 )2 ∧ x = 2 (i + 1 ) − 1
≡ z = (i + 1)2 ∧ x = 2i + 1
B2 ≡ WP[[z = z+x;]]( B1 ) ≡ z + x = (i + 1)2 ∧ x = 2i + 1
≡ z = i2 ∧ x = 2i + 1
90
Example (cont.)
Assume B ≡ z = i2 ∧ x = 2i − 1
Then we calculate:
B1 ≡ WP[[i = i+1;]]( B) ≡ z = (i + 1 )2 ∧ x = 2 (i + 1 ) − 1
≡ z = (i + 1)2 ∧ x = 2i + 1
B2 ≡ WP[[z = z+x;]]( B1 ) ≡ z + x = (i + 1)2 ∧ x = 2i + 1
≡ z = i2 ∧ x = 2i + 1
B3 ≡ WP[[x = x+2;]]( B2 ) ≡ z = i2 ∧ x + 2 = 2i + 1
≡ z = i2 ∧ x = 2i − 1
≡ B
91
Idea
92
Example
int a, i, x, z;
a = read();
i = 0;
x = -1;
z = 0;
while (i != a) {
x = x+2;
z = z+x;
i = i+1;
}
assert(z==a*a);
write(z);
93
Example
Start
a = read();
i = 0;
x = −1;
z = 0;
B
no yes
i != a
z = a2 B
write(z); x=x+2;
z=z+x;
Stop i=i+1;
94
We verify:
WP[[i != a]]( z = a2 , B)
≡ (i = a ∧ z = a2 ) ∨ (i 6 = a ∧ B )
≡ (i = a ∧ z = a2 ) ∨ (i 6= a ∧ z = i2 ∧ x = 2i − 1)
⇐ (i = a ∧ z = i2 ∧ x = 2i − 1) ∨ (i 6= a ∧ z = i2 ∧ x = 2i − 1)
≡ z = i2 ∧ x = 2i − 1 ≡ B
95
Orientation
Start
a = read();
i = 0;
x = −1;
z = 0;
B
no yes
i != a
z = a2 B
write(z); x=x+2;
z=z+x;
Stop i=i+1;
96
We verify:
WP[[z = 0;]]( B) ≡ 0 = i2 ∧ x = 2i − 1
≡ i = 0 ∧ x = −1
WP[[x = -1;]](i = 0 ∧ x = −1) ≡ i=0
WP[[i = 0;]](i = 0) ≡ true
WP[[a = read();]](true) ≡ true
97
1.4 Termination
Problem
98
Examples
99
Examples
100
Example int i, j, t;
t = 0;
i = read();
while (i>0) {
j = read();
while (j>0) { t = t+1; j = j-1; }
i = i-1;
}
write(t);
101
Example int i, j, t;
t = 0;
i = read();
while (i>0) {
j = read();
while (j>0) { t = t+1; j = j-1; }
i = i-1;
}
write(t);
102
Programs with for-loops only of the form:
for (i=n; i>0; i--) {...}
// i is not modified in the body
... always terminate !
103
Programs with for-loops only of the form:
for (i=n; i>0; i--) {...}
// i is not modified in the body
... always terminate !
Question
104
Idea
• Make sure that each loop is executed only finitely often ...
• For each loop, identify an indicator value r, that has two
properties
105
Example: Safe GCD Program
int a, b, x, y;
a = read(); b = read();
if (a < 0) x = -a; else x = a;
if (b < 0) y = -b; else y = b;
if (x == 0) write(y);
else if (y == 0) write(x);
else {
while (x != y)
if (y > x) y = y-x;
else x = x-y;
write(x);
}
106
We choose: r = x+y
Transformation
int a, b, x, y, r;
a = read(); b = read();
if (a < 0) x = -a; else x = a;
if (b < 0) y = -b; else y = b;
if (x == 0) write(y);
else if (y == 0) write(x);
else { r = x+y;
while (x != y) {
if (y > x) y = y-x;
else x = x-y;
r = x+y; }
write(x);
}
107
Start no yes
x == 0
no yes
a = read(); y == 0 write(y);
write(x);
b = read();
r=x+y;
no yes
a<0
no yes
x = a; x = −a; 3 x != y 1 r>0
no yes
x<y
write(x);
no yes
b<0 x=x−y; y=y−x;
2
y = b; y = −b; r > x+y
r=x+y;
Stop
108
At program points 1, 2 and 3, we assert:
Then we have:
109
We verify:
110
We verify:
111
We verify:
112
We verify:
113
Orientation
Start no yes
x == 0
no yes
a = read(); y == 0 write(y);
write(x);
b = read();
r=x+y;
no yes
a<0
no yes
x = a; x = −a; 3 x != y 1 r>0
no yes
x<y
write(x);
no yes
b<0 x=x−y; y=y−x;
2
y = b; y = −b; r > x+y
r=x+y;
Stop
114
Further propagation of C through the control-flow graph completes
the locally consistent annotation with assertions.
115
Further propagation of C through the control-flow graph completes
the locally consistent annotation with assertions.
We conclude:
116
General Method
117
1.5 Modular Verification and Procedures
118
Idea
{ A} p { B}
119
Idea
{ A} p { B}
A : pre-condition
B : post-condition
120
Examples
121
Examples
122
Examples
123
Examples
124
Modular verification can be used to prove the correctness of programs
using functions/methods.
Simplification
We only consider
125
Example
126
Comment
{ a ≥ b} mm(); { a = x}
127
Approach
{ A} f(); { B}
{ A} ss { B}
holds.
• Whereever a procedure call occurs in the program, we rely on the
triple from H ...
128
... in the Example
We verify:
mm() a≥b
no yes
a>b
x = b; x = a;
y = a; y = b;
Stop a=x
129
... in the Example
We verify:
mm() a≥b
no yes
a=b a>b true
x = b; x = a;
y = a; y = b;
Stop a=x
130
Discussion
• The approach also works in case the procedure has a return value:
that can be simulated by means of a global variable return which
receives the respective function results.
• It is not obvious, though, how pre- and post-conditions of procedure
calls can be chosen if a procedured is called in multiple places ...
• Even more complicated is the situation when a procedure is
recursive: the it has possibly unboundedly many distinct calls !?
131
Example
132
Comment
133
Problem
In the Example
{ A} f(); { B} where
A ≡ x = l ∧ x > 1 ∧ m0 = m1 = 1
B ≡ l > 1 ∧ m1 ≤ 2l ∧ m0 ≤ 2l −1
134
General Approach
{ A} ss { B}
holds.
135
... in the Example
f() A
x = x−1;
x = l − 1 ∧ x > 0 ∧ m0 = m1 = 1
no yes
x>1
D
f();
C
t = m1;
m1=m1+m0;
m0 = t;
Stop B
136
• We start with an assertion for the end point:
B ≡ l > 1 ∧ m1 ≤ 2l ∧ m0 ≤ 2l −1
137
Question
How can the global hypothesis be used to deal with a specific procedure
call ???
Idea
• The assertion { A} f(); { B} represents a value table for
f().
• This value table can be logically represented by the implication:
∀ l. ( A[h/ x] ⇒ B)
138
Examples
∀ l. (h > 1 ∧ h = l ∧ h0 = h1 = 1) ⇒
l > 1 ∧ m1 ≤ 2l ∧ m0 ≤ 2l −1
≡ ( h > 1 ∧ h0 = h1 = 1 ) ⇒ m1 ≤ 2 h ∧ m0 ≤ 2 h−1
139
Another pair ( A1 , B1 ) of assertions forms a valid triple
{ A1 } f(); { B1 } , if we are able to prove that
∀ l. A[h/ x] ⇒ B A1 [ h/ x]
B1
140
Another pair ( A1 , B1 ) of assertions forms a valid triple
{ A1 } f(); { B1 } , if we are able to prove that
∀ l. A[h/ x] ⇒ B A1 [ h/ x]
B1
Example: double()
A ≡ x=l B ≡ x = 2l
A1 ≡ x≥3 B1 ≡ x≥6
141
Another pair ( A1 , B1 ) of assertions forms a valid triple
{ A1 } f(); { B1 } , if we are able to prove that
∀ l. A[h/ x] ⇒ B A1 [ h/ x]
B1
Example: double()
A ≡ x=l B ≡ x = 2l
A1 ≡ x≥3 B1 ≡ x≥6
We verify:
x = 2h h≥3
x≥6
142
Remarks
{ x = l } double(); { x = 2l }
{ x = l − 1} double(); { x = 2(l − 1)}
143
Remarks
{ x = l } double(); { x = 2l }
{ x = l − 1} double(); { x = 2(l − 1)}
{ x = l } double(); { x = 2l }
{ x = l ∧ l > 0} double(); { x = 2l ∧ l > 0}
144
Remarks (cont.)
{ x = l } double(); { x = 2l }
{ x > 0 ∧ x = l } double(); { x = 2l }
{ x = l } double(); { x = 2l }
{ x = l } double(); { x = 2l ∨ x = −1}
145
Application to Fibonacci
Our goal is to prove: { D } f(); {C }
A ≡ x > 1 ∧ l = x ∧ m0 = m1 = 1
A[(l − 1)/l ] ≡ x > 1 ∧ l − 1 = x ∧ m0 = m1 = 1
≡ D
146
Application to Fibonacci
Our goal is to prove: { D } f(); {C }
A ≡ x > 1 ∧ l = x ∧ m0 = m1 = 1
A[(l − 1)/l ] ≡ x > 1 ∧ l − 1 = x ∧ m0 = m1 = 1
≡ D
B ≡ l > 1 ∧ m1 ≤ 2l ∧ m0 ≤ 2l −1
B[(l − 1)/l ] ≡ l − 1 > 1 ∧ m1 ≤ 2l −1 ∧ m0 ≤ 2l −2
≡ C
147
Orientation
f() A
x = x−1;
x = l − 1 ∧ x > 0 ∧ m0 = m1 = 1
no yes
x>1
D
f();
C
t = m1;
m1=m1+m0;
m0 = t;
Stop B
148
For the conditional, we verify:
WP[[x>1]] ( B, D ) ≡ ( x ≤ 1 ∧ l > 1 ∧ m1 ≤ 2l ∧ m0 ≤ 2l −1 ) ∨
( x > 1 ∧ x = l − 1 ∧ m1 = m0 = 1)
⇐ x > 0 ∧ x = l − 1 ∧ m0 = m1 = 1
149
1.6 Procedures with Local Variables
Example
{int y= 17; double(); write(y);}
150
• The values of local variables are automatically preserved, if the
global hypothesis has the following properties:
151
• The values of local variables are automatically preserved, if the
global hypothesis has the following properties:
{ A} f(); { B}
{ A ∧ C } f(); { B ∧ C }
152
Summary
153
Functional Programming
154
John McCarthy, Stanford
155
Robin Milner, Edinburgh
156
Xavier Leroy, INRIA, Paris
157
2 Basics
• Interpreter Environment
• Expressions
• Definitions of Values
• More Complex Datatypes
• Lists
• Definitions (cont.)
• User-defined Datatypes
158
2.1 The Interpreter Environment
seidl@linux:~> ocaml
OCaml version 4.13.1
...
#
# #use "Hello.ml";;
159
2.2 Expressions
# 3+4;;
- : int = 7
# 3+
4;;
- : int = 7
#
→ At #, the interpreter is waiting for input.
→ The ;; causes evaluation of the given input.
→ The result is computed and returned together with its type.
160
Pre-defined Constants and Operators
161
Type Comparison operators
int = <> < <= >= >
float = <> < <= >= >
bool = <> < <= >= >
string = <> < <= >= >
char = <> < <= >= >
162
Type Comparison operators
int = <> < <= >= >
float = <> < <= >= >
bool = <> < <= >= >
string = <> < <= >= >
char = <> < <= >= >
# -3.0/.4.0;;
- : float = -0.75
# "So"^" "^"it"^" "^"goes";;
- : string = "So it goes"
# 1>2 || not (2.0<1.0);;
- : bool = true
163
2.3 Definitions of Values
164
Another definition of seven does not assign a new value to seven, but
creates a new variable with the name seven.
165
2.4 More Complex Datatypes
• Pairs
# (3,4);;
- : int * int = (3, 4)
# (1=2,"hello");;
- : bool * string = (false, "hello")
• Tuples
# (2,3,4,5);;
- : int * int * int * int = (2, 3, 4, 5)
# ("hello",true,3.14159);;
-: string * bool * float = ("hello", true, 3.14159)
166
Simultaneous Definition of Variables
167
Simultaneous Definition of Variables
168
Records: Example
169
Remark
170
Remark
171
... with pattern matching
# let {given=x;sur=y;age=z} = paul;;
val x : string = "Paul"
val y : string = "Meier"
val z : int = 24
172
Case Distinction: match and if
match n
with 0 -> "null"
| 1 -> "one"
| _ -> "uncountable!"
match e
with true -> e1
| false -> e2
if e then e1 else e2
173
Watch out for redundant and incomplete matches!
# let n = 7;;
val n : int = 7
# match n with 0 -> "zero";;
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
1
Exception: Match_failure ("", 5, -13).
# match n
with 0 -> "zero"
| 0 -> "one"
| _ -> "uncountable!";;
Warning: this match case is unused.
- : string = "uncountable!"
174
2.5 Lists
# let mt = [];;
val mt : ’a list = []
# let l1 = 1::mt;;
val l1 : int list = [1]
# let l = [1;2;3];;
val l : int list = [1; 2; 3]
# let l = 1::2::3::[];;
val l : int list = [1; 2; 3]
175
Caveat
# 1.0::1::[];;
This expression has type int but is here used with type float
176
Caveat
# 1.0::1::[];;
This expression has type int but is here used with type float
177
Pattern Matching on Lists
# match l
with [] -> -1
| x::xs -> x;;
-: int = 1
178
2.6 Definition of Functions
179
→ Alternatively, we may introduce a variable whose value is a
function.
180
Caveat
181
Caveat
A function is a value:
# double;;
- : int -> int = <fun>
182
Recursive Functions
183
If functions call themselves indirectly via other other functions, they are
called mutually recursive.
184
Definition by Case Distinction
185
Definition by Case Distinction
186
Case distinction for several arguments
187
Case distinction for several arguments
188
Local Definitions
189
2.7 User-defined Datatypes
190
Disadvantages
191
Example: Playing cards
192
Advantages
→ The representation is intuitive.
→ Typing errors are recognized:
# (Culbs,Nine);;
Unbound constructor Culbs
→ The internal representation is efficient.
Remark
→ By type, a new type is defined.
→ The alternatives are called constructors and are separated by |.
→ Every constructor starts with a capital letter and is uniquely
assigned to a type.
193
Enumeration Types (cont.)
194
val is_trump : color * value -> bool = <fun>
By that, e.g.,
# is_trump (Gras,Jack);;
- : bool = true
# is_trump (Clubs,Nine);;
- : bool = false
195
# let string_of_color = function
Diamonds -> "Diamonds"
| Hearts -> "Hearts"
| Gras -> "Gras"
| Clubs -> "Clubs";;
val string_of_color : color -> string = <fun>
Remark
196
Now, Ocaml can (almost) play cards:
197
...
# let take card2 card1 =
if takes card2 card1 then card2 else card1;;
198
Sum Types
199
...
let get x = match x with
| Some y -> y
let value x a = match x with
| Some y -> y
| None -> a
let map f x = match x with
| Some y -> Some (f y)
| None -> None
let join a = match a with
| Some a’ -> a’
| None -> None
200
Option is a module, which collects useful functions and values for
option.
A constructor defined inside type t = Con of <type> | ...
has functionality Con : <type> -> t — must, however, always
occur applied ...
# Some;;
The constructor Some expects 1 argument(s),
but is here applied to 0 argument(s)
# None;;
- : ’a option = None
# Some 10;
- : int option = Some 10
# let a = Some "Hello!";;
val a : string option = Some "Hello!"
201
The type option is polymorphic – which means that it can be
constructed for any type ’a, in particular int or string.
202
Datatypes can be recursive:
203
Datatypes can be recursive:
204
Recursive datatypes lead to recursive functions:
205
Another Example
# down 3;;
- : int sequence = Next (3, Next (2, Next (1, End)));;
# down (-1);;
Stack overflow during evaluation (looping recursion?).
206
3 A closer Look at Functions
• Tail Calls
• Higher-order Functions
→ Currying
→ Partial Application
• Polymorphic Functions
• Polymorphic Datatypes
• Anonymous Functions
207
3.1 Tail Calls
A tail call in the body e of a function is a call whose value provides the
value of e ...
let f x = x+5
let g y = let z = 7
in if y>5 then f (-y)
else z + f y
The first call is tail, the second is not.
==⇒ From a tail call, we need not return to the calling function.
==⇒ The stack space of the calling function can immediately be
recycled !!!
208
A recursive function f is called tail recursive, if all calls to f are last.
Examples
let rec fac1 = function
(1,acc) -> acc
| (n,acc) -> fac1 (n-1,n*acc);;
209
Discussion
210
Reversing a List – Version 1
let rec rev list = match list
with [] -> []
| x::xs -> app (rev xs) [x]
211
Reversing a List – Version 1
let rec rev list = match list
with [] -> []
| x::xs -> app (rev xs) [x]
212
Reversing a List – Version 2
let rev list = let rec r a l =
match l
with [] -> a
| x::xs -> r (x::a) xs
in r [] list
213
Reversing a List – Version 2
let rev list = let rec r a l =
match l
with [] -> a
| x::xs -> r (x::a) xs
in r [] list
214
3.2 Higher Order Functions
At first sight, f and g differ only in the syntax. But they also differ in
their types:
# f;;
- : int * int -> int = <fun>
# g;;
- : int -> int -> int = <fun>
215
• Function f has a single argument, namely, the pair (a,b). The
return value is given by a+b+1.
• Function g has the one argument a of type int. The result of
application to a is a function that, when applied to the other
argument b, returns the result a+b+1 :
# f (3,5);;
- : int = 9
# let g1 = g 3;;
val g1 : int -> int = <fun>
# g1 5;;
- : int = 9
216
Haskell B. Curry, 1900–1982
217
In honor of its inventor Haskell B. Curry, this principle is called Currying.
218
...
# let plus (x,y) = x+y;;
val plus : int * int -> int = <fun>
# apply plus;;
- : int -> int -> int = <fun>
# let plus2 = apply plus 2;;
val plus2 : int -> int = <fun>
# let plus3 = apply plus 3;;
val plus3 : int -> int = <fun>
# plus2 (plus3 4);;
- : int = 9
219
3.3 Some List Functions
220
let rec find_opt f = function
[] -> None
| x::xs -> if f x then Some x
else find_opt f xs
Remarks
221
3.4 Polymorphic Functions
The Ocaml system infers the following types for the given functionals:
222
→ By partial application, some of the type variables may be
instantiated:
# string_of_int;;
val : int -> string = <fun>
# map string_of_int;;
- : int list -> string list = <fun>
# fold_left (+);;
val it : int -> int list -> int = <fun>
223
→ If a functional is applied to a function that is itself polymorphic,
the result may again be polymorphic:
224
Some of the Simplest Polymorphic Functions
let compose f g x = f (g x)
let twice f x = f (f x)
let rec iter f g x = if g x then x else iter f g (f x);;
val compose : (’a -> ’b) -> (’c -> ’a) -> ’c -> ’b = <fun>
val twice : (’a -> ’a) -> ’a -> ’a = <fun>
val iter : (’a -> ’a) -> (’a -> bool) -> ’a -> ’a = <fun>
226
# Leaf 1;;
- : int tree = Leaf 1
# Node (Leaf (’a’,true), Leaf (’b’,false));;
- : (char * bool) tree = Node (Leaf (’a’, true),
Leaf (’b’, false))
227
let rec size = function
| Leaf _ -> 1
| Node(t,t’) -> size t + size t’
228
...
val size : ’a tree -> int = <fun>
val flatten : ’a tree -> ’a list = <fun>
val flatten1 : ’a tree -> ’a list = <fun>
# size t;;
- : int = 3
# flatten t;;
val : int list = [1;5;3]
# flatten1 t;;
val : int list = [1;5;3]
229
3.6 Application: Queues
Wanted
230
First Idea
231
First Idea
232
First Idea
233
Discussion
234
Second Idea
• The second list represents the tail of the list and therefore in
reverse ordering ...
235
Second Idea (cont.)
236
Second Idea (cont.)
237
Discussion
238
3.7 Anonymous Functions
As we have seen, functions are data. Data, e.g., [1;2;3] can be used
without naming them. This is also possible for functions:
# fun x y z -> x+y+z;;
- : int -> int -> int -> int = <fun>
• fun initiates an abstraction.
This notion originates in the λ-calculus.
• -> has the effect of = in function definitions.
• Recursive functions cannot be defined in this way, as the recurrent
occurrences in their bodies require names for reference.
239
Alonzo Church, 1903–1995
240
• Pattern matching can be used by applying match ... with
for the corresponding argument.
• In case of a single argument, function can be considered ...
241
Anonymous functions are convenient if they are used just once in a
program. Often, they occur as arguments to functionals:
242
4 A Larger Application:
Balanced Trees
2 3 5 7 11 13 17
243
Properties
244
Wanted:
245
First Idea
246
First Idea
3 13
2 5 11 17
247
Discussion
248
Discussion
249
Second Idea
250
An AVL Tree
251
An AVL Tree
252
Not an AVL Tree
253
G.M. Adelson-Velskij, 1922 E.M. Landis, Moskau, 1921-1997
254
We prove:
fib(k) ≥ Ak−1
√
5+1
nodes where A= 2 // golden cut
255
We calculate:
(1) Each AVL tree of depth k > 0 has at least
fib(k) ≥ Ak−1
√
5+1
nodes where A= 2 // golden cut
(2) Every AVL tree with n > 0 internal nodes has depth at most
1
· log(n) + 1
log( A)
256
We calculate:
(1) Each AVL tree of depth k > 0 has at least
fib(k) ≥ Ak−1
√
5+1
nodes where A= 2 // golden cut
(2) Every AVL tree with n > 0 internal nodes has depth at most
1
· log(n) + 1
log( A)
258
k=1: N (1) = 1 = fib(1) = A0
k>2: Assume that the assertion holds for k−1 and k−2
...
==⇒ N (k) = N (k − 1) + N (k − 2) + 1
≥ fib(k − 1) + fib(k − 2)
= fib(k)
259
k=1: N (1) = 1 = fib(1) = A0
k>2: Assume that the assertion holds for k−1 and k−2
...
==⇒ N (k) = N (k − 1) + N (k − 2) + 1
≥ fib(k − 1) + fib(k − 2)
= fib(k)
fib(k) = fib(k − 1) + fib(k − 2)
≥ Ak−2 + Ak−3
= Ak−3 · ( A + 1 )
= Ak−3 · A2
= Ak−1
260
Second Idea (cont.)
261
Representation
262
Representation
3 2
2 1 1
263
Third Idea
264
Representation
3 2
2 1 1
265
Representation
N P
P E E
266
Insertion
• If the tree is a leaf, i.e., empty, an internal node is created with two
new leaves.
• If the tree in non-empty, the new value is compared with the value
at the root.
267
let rec insert x avl = match avl
with Null -> (Eq (Null,x,Null), true)
| Eq (left,y,right) -> if x < y then
let (left,inc) = insert x left
in if inc then (Neg (left,y,right), true)
else (Eq (left,y,right), false)
else let (right,inc) = insert x right
in if inc then (Pos (left,y,right), true)
else (Eq (left,y,right), false)
...
268
let rec insert x avl = match avl
with Null -> (Eq (Null,x,Null), true)
| Eq (left,y,right) -> if x < y then
let (left,inc) = insert x left
in if inc then (Neg (left,y,right), true)
else (Eq (left,y,right), false)
else let (right,inc) = insert x right
in if inc then (Pos (left,y,right), true)
else (Eq (left,y,right), false)
...
• Besides the new AVL tree, the function insert also returns
the information whether the depth of the result has increased.
• If the depth is not increased, the marking of the root need not be
changed.
269
| Neg (left,y,right) -> if x < y then
let (left,inc) = insert x left
in if inc then let (avl,_) = rotateRight (left,y,right)
in (avl,false)
else (Neg (left,y,right), false)
else let (right,inc) = insert x right
in if inc then (Eq (left,y,right), false)
else (Neg (left,y,right), false)
| Pos (left,y,right) -> if x < y then
let (left,inc) = insert x left
in if inc then (Eq (left,y,right), false)
else (Pos (left,y,right), false)
else let (right,inc) = insert x right
in if inc then let (avl,_) = rotateLeft (left,y,right)
in (avl,false)
else (Pos (left,y,right), false);;
270
Comments
• Insertion into the less deep subtree never increases the total depth.
The depths of the two subtrees, though, may become equal.
• Insertion into the deeper subtree may increase the difference in
depth to 2.
then the node at the root must be rotated in order to decrease the
difference ...
271
rotateRight
N P
E N
272
rotateRight
N E
N E
273
rotateRight
N E
P E E
274
rotateRight
N E
P E P
275
rotateRight
N E
P N E
276
let rotateRight (left, y, right) = match left
with Eq (l1,y1,r1) -> (Pos (l1, y1, Neg (r1,y,right)), false)
| Neg (l1,y1,r1) -> (Eq (l1, y1, Eq (r1,y,right)), true)
| Pos (l1, y1, Eq (l2,y2,r2)) ->
(Eq (Eq (l1,y1,l2), y2, Eq (r2,y,right)), true)
| Pos (l1, y1, Neg (l2,y2,r2)) ->
(Eq (Eq (l1,y1,l2), y2, Pos (r2,y,right)), true)
| Pos (l1, y1, Pos (l2,y2,r2)) ->
(Eq (Neg (l1,y1,l2), y2, Eq (r2,y,right)), true)
• The extra bit now indicates whether the depth of the tree after
rotation has decreased ...
• This is not the case only when the deeper subtree is of the form
Eq (...) — which does never occur here.
277
rotateLeft
P N
E P
278
rotateLeft
P E
P E
279
rotateLeft
P E
N E E
280
rotateLeft
P E
N E P
281
rotateLeft
P E
N N E
282
let rotateLeft (left, y, right) = match right
with Eq (l1,y1,r1) -> (Neg (Pos (left,y,l1), y1, r1), false)
| Pos (l1,y1,r1) -> (Eq (Eq (left,y,l1), y1, r1), true)
| Neg (Eq (l1,y1,r1), y2 ,r2) ->
(Eq (Eq (left,y,l1),y1, Eq (r1,y2,r2)), true)
| Neg (Neg (l1,y1,r1), y2 ,r2) ->
(Eq (Eq (left,y,l1),y1, Pos (r1,y2,r2)), true)
| Neg (Pos (l1,y1,r1), y2 ,r2) ->
(Eq (Neg (left,y,l1),y1, Eq (r1,y2,r2)), true)
283
Discussion
284
Extraction of the Minimum
285
let rec extract_min avl = match avl
with Null -> (None, Null, false)
| Eq (Null,y,right) -> (Some y, right, true)
| Eq (left,y,right) -> let (first,left,dec) = extract_min left
in if dec then (first, Pos (left,y,right), false)
else (first, Eq (left,y,right), false)
| Neg (left,y,right) -> let (first,left,dec) = extract_min left
in if dec then (first, Eq (left,y,right), true)
else (first, Neg (left,y,right), false)
| Pos (Null,y,right) -> (Some y, right, true)
| Pos (left,y,right) -> let (first,left,dec) = extract_min left
in if dec then let (avl,b) = rotateLeft (left,y,right)
in (first,avl,b)
else (first, Pos (left,y,right), false)
286
Discussion
287
5 Practical Features of Ocaml
• Exceptions
• Input and Output as Side-effects
• Sequences
288
5.1 Exceptions
In case of a runtime error, e.g., division by zero, the Ocaml system
generates an exception:
# 1 / 0;;
Exception: Division_by_zero.
# List.tl (List.tl [1]);;
Exception: Failure "tl".
# Char.chr 300;;
Exception: Invalid_argument "Char.chr".
289
Another reason for an exception is an incomplete match:
290
Pre-defined Constructors for Exceptions
Division_by_zero division by 0
Invalid_argument of string wrong usage
Failure of string general error
Match_failure of string * int * int incomplete match
Not_found not found
Out_of_memory memory exhausted
End_of_file end of file
Exit for the user ...
An exception is a first class citizen, i.e., a value from a datatype exn ...
291
# Division_by_zero;;
- : exn = Division_by_zero
# Failure "complete nonsense!";;
- : exn = Failure "complete nonsense!"
# exception Hell;;
exception Hell
# Hell;;
- : exn = Hell
292
# Division_by_zero;;
- : exn = Division_by_zero
# Failure "complete nonsense!";;
- : exn = Failure "complete nonsense!"
293
Handling of Exceptions
# divide (10,3);;
- : int option = Some 3
# divide (10,0);;
- : int option = None
294
let rec member x l = try if x = List.hd l then true
else member x (List.tl l)
with Failure _ -> false
# member 2 [1;2;3];;
- : bool = true
# member 4 [1;2;3];;
- : bool = false
try <exp>
with <pat1> -> <exp1> | ... | <patN> -> <expN>
# 1 + (2/0);;
Exception: Division_by_zero.
# 1 + raise Division_by_zero;;
Exception: Division_by_zero.
296
Exception handling may occur at any sub-expression, arbitrarily nested:
# g (6,1);;
- : string = "Error: Division by zero"
# g (6,3);;
- : string = "9"
297
5.2 Textual Input and Output
• Reading from the input and writing to the output violates the
paradigm of purely functional programming !
• These operations are therefore realized by means of side-effects,
i.e., by means of functions whose return value is irrelevant (e.g.,
unit).
• During execution, though, the required operation is executed
==⇒ now, the ordering of the evaluation matters !!!
298
• Naturally, Ocaml allows to write to standard output:
# print_string "Hello World!\n";;
Hello World!
- : unit = ()
• Analogously, there is a function: read_line : unit -> string
...
# read_line ();;
Hello World!
- : string = "Hello World!"
299
In order to read from file, the file must be opened for reading ...
# close_in infile;;
- : unit = ()
300
Further Useful Values
stdin : in_channel
input_char : in_channel -> char
in_channel_length : in_channel -> int
301
Output to files is analogous ...
The words written seperately, may only occur inside the file, once the file
has been closed ...
# close_out outfile;;
- : unit = ()
302
5.3 Sequences
# print_string "Hello";
print_string " ";
print_string "world!\n";;
Hello world!
- : unit = ()
303
Often, several strings must be output !
Given a list of strings, the list functional List.iter can be used:
val iter : (’a -> unit) -> ’a list -> unit = <fun>
304
6 The Module System of OCAML
→ Modules
→ Signatures
→ Information Hiding
→ Functors
→ Separate Compilation
305
6.1 Modules
module Pairs =
struct
type ’a pair = ’a * ’a
let pair (a,b) = (a,b)
let first (a,b) = a
let second (a,b) = b
end
306
On this input, the compiler answers with the type of the module, its
signature:
module Pairs :
sig
type ’a pair = ’a * ’a
val pair : ’a * ’b -> ’a * ’b
val first : ’a * ’b -> ’a
val second : ’a * ’b -> ’b
end
307
Access onto Components of a Module
Thus, several functions can be defined all with the same name:
# module Triples = struct
type ’a triple = Triple of ’a * ’a * ’a
let first (Triple (a,_,_)) = a
let second (Triple (_,b,_)) = b
let third (Triple (_,_,c)) = c
end;;
...
308
...
module Triples :
sig
type ’a triple = Triple of ’a * ’a * ’a
val first : ’a triple -> ’a
val second : ’a triple -> ’a
val third : ’a triple -> ’a
end
# Triples.first;;
- : ’a Triples.triple -> ’a = <fun>
309
... or several implementations of the same function:
# module Pairs2 =
struct
type ’a pair = bool -> ’a
let pair (a,b) = fun x -> if x then a else b
let first ab = ab true
let second ab = ab false
end;;
310
Opening Modules
# open Pairs2;;
# pair;;
- : ’a * ’a -> bool -> ’a = <fun>
# pair (4,3) true;;
- : int = 4
311
# module A = struct let x = 1 end;;
module A : sig val x : int end
# module B = struct
open A
let y = 2
end;;
module B : sig val y : int end
# module C = struct
include A
include B
end;;
module C : sig val x : int val y : int end
312
Nested Modules
313
...
let first q = Pairs.first (Pairs.first q)
let second q = Pairs.second (Pairs.first q)
let third q = Pairs.first (Pairs.second q)
let fourth q = Pairs.second (Pairs.second q)
end
# Quads.quad (1,2,3,4);;
- : (int * int) * (int * int) = ((1,2),(3,4))
# Quads.Pairs.first;;
- : ’a * ’b -> ’a = <fun>
314
6.2 Module Types or Signatures
... an Example
315
module Sort = struct
let single list = map (fun x->[x]) list
let rec merge l1 l2 = match (l1,l2)
with ([],_) -> l2
| (_,[]) -> l1
| (x::xs,y::ys) -> if x<y then x :: merge xs l2
else y :: merge l1 ys
let rec merge_lists = function
[] -> [] | [l] -> [l]
| l1::l2::ll -> merge l1 l2 :: merge_lists ll
let sort list = let list = single list
in let rec doit = function
[] -> [] | [l] -> l
| l -> doit (merge_lists l)
in doit list
end
316
The implementation allows to access the auxiliary functions single,
merge and merge_lists from the outside:
# Sort.single [1;2;3];;
- : int list list = [[1]; [2]; [3]]
317
The functions single and merge_lists are no longer exported:
318
Signatures and Types
The types mentioned in the signature must be Instances of the types for
the exported definitions.
In that way, these types are spezialized:
module type A1 = sig
val f : ’a -> ’b -> ’b
end
module type A2 = sig
val f : int -> char -> int
end
module A = struct
let f x y = x
end
319
# module A1 : A1 = A;;
Signature mismatch:
Modules do not match: sig val f : ’a -> ’b -> ’a end
is not included in A1
Values do not match:
val f : ’a -> ’b -> ’a
is not included in
val f : ’a -> ’b -> ’b
# module A2 : A2 = A;;
module A2 : A2
# A2.f;;
- : int -> char -> int = <fun>
320
6.3 Information Hiding
Example
module ListQueue = struct
type ’a queue = ’a list
let empty_queue () = []
let is_empty = function
[] -> true | _ -> false
let enqueue xs y = xs @ [y]
let dequeue (x::xs) = (x,xs)
end
321
A signature allows to hide the implementation of a queue:
322
# module Queue : Queue = ListQueue;;
module Queue : Queue
# open Queue;;
# is_empty [];;
This expression has type ’a list but is here used with type
’b queue = ’b Queue.queue
==⇒
323
If the datatype should be exported together with all constructors, its
definition is repeated in the signature:
324
6.4 Functors
325
First, we specify the functor’s argument and result by means of
signatures:
326
...
module Fold : GenFold = functor (X:Decons) ->
struct
let rec fold_left f b t = match X.decons t
with None -> b
| Some (x,t) -> fold_left f (f b x) t
let rec fold_right f t b = match X.decons t
with None -> b
| Some (x,t) -> f x (fold_right f t b)
let size t = fold_left (fun a x -> a+1) 0 t
let list_of t = fold_right (fun x xs -> x::xs) t []
let iter f t = fold_left (fun () x -> f x) () t
end;;
Now, we can apply the functor to the module to obtain a new module ...
327
module MyQueue = struct open Queue
type ’a t = ’a queue
let decons = function
Queue([],xs) -> (match rev xs
with [] -> None
| x::xs -> Some (x, Queue(xs,[])))
| Queue(x::xs,t) -> Some (x, Queue(xs,t))
end
328
module FoldAVL = Fold (MyAVL)
module FoldQueue = Fold (MyQueue)
Caveat
329
6.5 Separate Compilation
330
• If there is already a file Test.mli this is interpreted as the
signature for Test. Then we call
> ocamlc Test.mli Test.ml
• Given a module A and a module B, then these should be
compiled by
> ocamlc B.mli B.ml A.mli A.ml
• If a re-compilation of B should be omitted, ocamlc may
receive a pre-compiled file
> ocamlc B.cmo A.mli A.ml
• For practical management of required re-compilation after
modification of files, Linux offers the tool make. The script of
required actions then is stored in a Makefile.
• ... alternatively, dune can be used.
331
7 Formal Verification for Ocaml
Question
How can we make sure that an Ocaml program behaves as it should ???
We require:
• a formal semantics
• means to prove assertions about programs ...
332
7.1 MiniOcaml
We consider ...
• only base types int, bool as well as tuples and lists
• recursive function definitions only at top level
• modifiable datatypes
• input and output
• local recursive functions
333
This fragment of Ocaml is called MiniOcaml.
Expressions in MiniOcaml can be described by the grammar
334
This fragment of Ocaml is called MiniOcaml.
Expressions in MiniOcaml can be described by the grammar
Short-cut
fun x1 -> ...fun xk -> e ≡ fun x1 . . . xk -> e
335
Caveat
336
A program then consists of a sequence of mutally recursive global
definitions of variables f 1 , . . . , f m :
let rec f1 = E1
and f2 = E2
...
and fm = Em
337
7.2 A Semantics for MiniOcaml
Question
Which value is returned for the expression E ??
338
A MiniOcaml Program ...
let rec comp = fun f g x -> f (g x)
and map = fun f list -> match list
with [] -> []
| x::xs -> f x :: map f xs
339
A MiniOcaml Program ...
let rec comp = fun f g x -> f (g x)
and map = fun f list -> match list
with [] -> []
| x::xs -> f x :: map f xs
340
Idea
341
Tuples
e1 ⇒ v1 ... ek ⇒ vk
(TU)
(e1 , . . . , ek ) ⇒ (v1 , . . . , vk )
Lists
e1 ⇒ v1 e2 ⇒ v2
(LI)
e1 :: e2 ⇒ v1 :: v2
Global definitions
f =e e ⇒ v
(GD)
f ⇒ v
342
Local definitions
e1 ⇒ v1 e0 [v1 / x] ⇒ v0
(LD)
let x = e1 in e0 ⇒ v0
Function calls
343
By repeated application of the rule for function calls, a rule for functions
with multiple arguments can be derived:
344
Pattern Matching
e 0 ⇒ v ′ ≡ pi [ v 1 / x 1 , . . . , v k / x k ] ei [ v 1 / x 1 , . . . , v k / x k ] ⇒ v
(PM)
match e0 with p1 -> e1 | ... | pm -> em ⇒ v
Built-in operators
e1 ⇒ v1 e2 ⇒ v2 v1 op v2 ⇒ v
(OP)
e1 op e2 ⇒ v
345
The built-in equality operator
v=v ⇒ true
v1 = v2 ⇒ false
Example 1
17 ⇒ 17 4 ⇒ 4 17+4 ⇒ 21
17+4 ⇒ 21 21 ⇒ 21 21=21 ⇒ true
17 + 4 = 21 ⇒ true
346
The built-in equality operator
v=v ⇒ true
v1 = v2 ⇒ false
17+4 ⇒ 21
17+4 ⇒ 21 21=21 ⇒ true
17 + 4 = 21 ⇒ true
347
Example 2
348
Example 3
349
Proof
350
Discussion
351
Example Claim
app l1 l2 terminates for all list values l1 , l2 .
Proof
Induction on the length n of the list l1 .
352
n>0: I.e., l1 = h::t.
In particular, we assume that the claim already holds for all shorter lists.
Then we have:
app t l2 ⇒ l
for some l. We deduce
app t l2 ⇒ l
app = fun x y -> . . . h :: app t l2 ⇒ h :: l
app ⇒ fun x y -> . . . match h::t with · · · ⇒ h :: l
app (h::t) l2 ⇒ h :: l
353
Discussion (cont.)
354
Caveat
C :: = const | (C1 , . . . , Ck ) | [] | C1 :: C2
355
Caveat
C :: = const | (C1 , . . . , Ck ) | [] | C1 :: C2
356
Discussion
• Apparently, the functions to the right and left of the equality sign
cannot be compared by Ocaml for equality.
==⇒
357
Extension of Equality
Non-termination
e1 , e2 both not terminating
e1 = e2
Termination
e1 ⇒ v1 e2 ⇒ v2 v1 = v2
e1 = e2
358
Structured values
v1 = v′1 . . . vk = v′k
(v1 , . . . , vk ) = (v′1 , . . . , v′k )
v1 = v′1 v2 = v′2
v1 :: v2 = v′1 :: v′2
Functions
e1 [v/ x1 ] = e2 [v/ x2 ] for all v
fun x1 -> e1 = fun x2 -> e2
359
We have:
e ⇒ v
e = v
e1 = e2 e1 terminates
e1 = e2 ⇒ true
e1 = e2 ⇒ true
e1 = e2 ei terminates
360
Substitution Lemma
e1 = e2
e[e1 / x] = e[e2 / x]
e1 = e2 e[e1 / x] terminate
e[e1 / x] = e[e2 / x] ⇒ true
361
Discussion
362
We provide us with a repertoir of rewrite rules for reducing the equality
of expressions to the equality of, possibly simpler expressions ...
e1 terminates
let x = e1 in e = e[e1 / x]
363
We provide us with a repertoir of rewrite rules for reducing the equality
of expressions to the equality of, possibly simpler expressions ...
e1 terminates
let x = e1 in e = e[e1 / x]
364
Proof of the let rule
Since e1 terminates, there is a value v1 with
e1 ⇒ v1
e[v1 / x] = e[e1 / x]
e[v1 / x] ⇒ v
365
Then
e[e1 / x] = e[v1 / x] = v
Because of the big-step semantics, however, we have:
366
By repeated application of the rule for function calls, an extra rule for
functions with multiple arguments can be deduced:
367
Rule for pattern matching
e0 = []
match e0 with [] -> e1 | ... | pm -> em = e1
368
Rule for pattern matching
e0 = []
match e0 with [] -> e1 | ... | pm -> em = e1
369
7.3 Proofs for MiniOcaml Programs
Example 1
let rec app = fun x -> fun y -> match x
with [] -> y
| h::t -> h :: app t y
370
Idea: Induction on the length n of x
We deduce:
app x [] = app [] []
= match [] with [] -> [] | h::t -> h :: app t []
= []
= x
371
n>0 Then: x = h::t where t has length n − 1.
We deduce:
372
Analogously we proceed for assertion (2) ...
n=0 Then: x = []
We deduce:
373
n>0 Then x = h::t where t has length n − 1.
We deduce:
app x y ⇒ v
375
Example 2
Claim
376
More generally,
app (rev x) y = rev1 x y for all lists x, y.
377
n>0 Then x = h::t where t has length n − 1.
378
Discussion
• Again, we have implicitly assumed that all calls of app, rev and
rev1 terminate.
• Termination of these can be proven by induction on the length of
their first arguments.
• The claim:
rev x = rev1 x []
follows from:
app (rev x) y = rev1 x y
379
Example 3
let rec sorted = fun x -> match x
with h1::h2::t -> (match h1 <= h2
with true -> sorted (h2::t)
| false -> false)
| _ -> true
380
Claim
n=0 Then: x = [] = y
We deduce:
sorted (merge x y) = sorted (merge [] [])
= sorted []
= true
381
n>0
Case 1: x = [].
We deduce:
sorted (merge x y) = sorted (merge [] y)
= sorted y
= true
Case 2: y = [] analogous.
382
Case 3: x = x1::xs ∧ y = y1::ys ∧ x1 ≤ y1.
We deduce:
sorted (merge x y) = sorted (merge (x1::xs) (y1::ys))
= sorted (x1 :: merge xs y)
= ...
Case 3.1: xs = []
We deduce:
... = sorted (x1 :: merge [] y)
= sorted (x1 :: y)
= sorted y
= true
383
Case 3.2: xs = x2::xs’ ∧ x2 ≤ y1.
We deduce:
... = sorted (x1 :: merge (x2::xs’) y)
= sorted (x1 :: x2 :: merge xs’ y)
= sorted (x2 :: merge xs’ y)
= sorted (merge xs y)
= true by induction hypothesis
384
Case 3.3: xs = x2::xs’ ∧ x2 > y1.
We deduce:
... = sorted (x1 :: merge (x2::xs’) (y1::ys))
= sorted (x1 :: y1 :: merge xs ys)
= sorted (y1 :: merge xs ys)
= sorted (merge xs y)
= true by induction hypothesis
385
Case 4: x = x1::xs ∧ y = y1::ys ∧ x1 > y1.
We deduce:
sorted (merge x y) = sorted (merge (x1::xs) (y1::ys))
= sorted (y1 :: merge x ys)
= ...
Case 4.1: ys = []
We deduce:
... = sorted (y1 :: merge x [])
= sorted (y1 :: x)
= sorted x
= true
386
Case 4.2: ys = y2::ys’ ∧ x1 > y2.
We deduce:
... = sorted (y1 :: merge x (y2::ys’))
= sorted (y1 :: y2 :: merge x ys’)
= sorted (y2 :: merge x ys’)
= sorted (merge x ys)
= true by induction hypothesis
387
Case 4.3: ys = y2::ys’ ∧ x1 ≤ y2.
We deduce:
... = sorted (y1 :: merge (x1::xs) (y2::ys’))
= sorted (y1 :: x1 :: merge xs ys)
= sorted (x1 :: merge xs ys)
= sorted (merge x ys)
= true by induction hypothesis
388
Discussion
• Again, we have assumed for the proof that all calls of the functions
sorted and merge terminate.
• As an additional techniques, we required a thorough case
distinction over the various possibilities for arguments in calls.
• The case distinction made the proof longish and cumbersome.
// The case n=0 is in fact superfluous.
// since it is covered by the cases 1 and 2
389
8 Parallel Programming
390
When your program requires multiple threads, use
ocamlc -I +threads unix.cma threads.cma <my files>
ocamlopt -I +threads unix.cmxa threads.cmxa <my files>
When you want to play with it within utop, use the following sequence
of commands:
#thread;;
#directory "+threads";;
#load "unix.cma";;
#load "threads.cma";;
391
Example
392
Comments
393
Further useful Functions
• The function join: t -> unit blocks the current thread until
the evaluation of the given thread has terminated.
• The function kill: t -> unit stops a given thread (not
implemented);
• The function delay: float -> unit delays the current
thread by a time period in seconds;
• The function exit: unit -> unit terminates the current
thread.
394
... running the compiled code yields:
> ./a.out
> abcdefghijk
> abcdefghijk
> 0
>
395
8.1 Channels
type ’a channel
new_channel : unit -> ’a channel
type ’a event
always : ’a -> ’a event
sync : ’a event -> ’a
send : ’a channel -> ’a -> unit event
receive : ’a channel -> ’a event
396
• Each call new_channel() creates another channel.
• Arbitrary data may be sent across a channel !!!
• always wraps a value into an event.
• Sending and receiving generates events ...
• Synchronization on evente returns their values.
398
In the example, main spawns a thread. Then it sends it a string and
waits for the answer. Accordingly, the new thread waits for the transfer
of a string value over the channel. As soon as the string is received, an
answer is sent on the same channel.
Caveat
If the ordering of send and receive is not carefully designed, threads
easily get blocked ...
> ./a.out
main is running ...
Greetings!
He got it!
>
399
Example: A global memory cell
The implementation must take care that the get and put calls are
sequentialized.
400
This task is delegated to a server thread that reacts to get and put:
The channel transports requests to the memory cell, which either provide
the new value or the back channel ...
401
let get cell = let reply = new_channel ()
in sync (send cell (Get reply));
sync (receive reply)
The function get sends a new back channel on the channel cell. If the
latter is received, it waits for the return value.
The function put sends a Put element which contains the new value for
the memory cell.
402
Of interest now is the implementation of the cell itself:
403
Creation of the cell with initial value x spawns a server thread that
evaluates the call serve x.
Caveat
404
let main = let x = new_cell 1
in print_int (get x); print_string "\n";
put x 2;
print_int (get x); print_string "\n"
> ./a.out
1
2
>
Instead of get and put, also more complex query or update operations
could be executed by the cell server ...
405
Example: Locks
Often, only one at a time out of several active threads should be allowed
access to a given resource. In order to realize such a mutual exclusion,
locks can be applied:
406
Execution of the operation acquire returns an element of type
ack which is used to return the lock:
For simplicity, ack is chosen itself as the channel by which the lock
is returned.
407
The unlock channel is created by acquire itself
408
Core of the implementation are the two mutually recursive functions
acq_server and rel_server.
409
in let th (l1,l2) = let a1 = acquire l1
in let _ = delay 1.0
in let a2 = acquire l2
in release a2; release a1;
print_int (id (self ()));
print_string " finished\n"
in let t1 = create th (l1,l2)
in let t2 = create th (l2,l1)
in join t1
The result is
> ./a.out
410
Example: Semaphores
411
Idea
412
let new_sema n = let sema = new_channel ()
in let rec serve (n,q) =
match sync (receive sema)
with None -> (match dequeue q
with (None,q) -> serve (n+1,q)
| (Some ack,q) -> sync (send ack ());
serve (n,q))
| Some ack -> if n>0 then (sync (send ack ());
serve (n-1,q))
else serve (n, enqueue ack q)
in let _ = create serve (n,new_queue())
in sema
end
Apparently, the queue does not maintain the waiting threads, but only
their back channels.
413
8.2 Selective Communication
is meant to read integers from two channels and send their sum to the
third.
414
First Attempt
let forever f init =
let rec loop x = loop (f x)
in let _ = create loop init
in ()
let add1 (in1, in2, out) = forever (fun () ->
sync (send out (sync (receive in1) +
sync (receive in2))
)) ()
Disadvantage
If a value arrives at the second input channel first, the thread
nonetheless must wait.
415
Second Attempt
let add (in1, in2, out) = forever (fun () ->
let (a,b) = select [
wrap (receive in1) (fun a -> (a, sync (receive in2)));
wrap (receive in2) (fun b -> (sync (receive in1), b))
]
in sync (send out (a+b))
) ()
416
Idea
417
The list thus consists of (int*int) events.
The functions
choose : ’a event list -> ’a event
select : ’a event list -> ’a
non-deterministically choose an event from the event list.
select synchronizes with the selected event, i.e., performs the
corresponding communication task and returns the event:
let select = comp sync choose
Typically, that event is occurs that finds its communication partner first.
418
Further Examples
The function
copy : ’a channel * ’a channel * ’a channel -> unit
is meant to copy a read element into two channels:
419
let copy (in, out1, out2) = forever (fun () ->
let x = sync (receive in)
in select [
wrap (send out1 x)
(fun () -> sync (send out2 x));
wrap (send out2 x)
(fun () -> sync (send out1 x))
]
) ()
Apparently, the event list may also consist of send events — or contain
both kinds.
type ’a cell = ’a channel * ’a channel
...
420
...
let get (get_chan,_) = sync (receive get_chan)
let put (_,put_chan) x = sync (send put_chan x)
let new_cell x = let get_chan = new_channel ()
in let put_chan = new_channel ()
in let rec serve x = select [
wrap (send get_chan x) (fun () -> serve x);
wrap (receive put_chan) serve
]
in let _ = create serve x
in (get_chan, put_chan)
421
In general, there could be a tree of events:
sync
f3 f4
send c3 x send c4 y
f1 f2
receive c1 receive c2
422
→ The leaves are basic events.
→ A wrapper function may be applied to any given event.
→ Several events of the same type may be combined into a choice.
→ Synchronization on such an event tree activates a single leaf
event. The result is obtained by successively applying the wrapper
functions from the path to the root.
423
Example: A Swap Channel
424
As soon as a thread successfully completed to send (i.e., the other
thread successfully synchronized on a receive event), the second value
must be transmitted in opposite direction.
Together with the first value, we therefore transmit a channel for the
second value:
module Swap =
struct open Thread open Event
type ’a swap = (’a * ’a channel) channel
let new_swap () = new_channel ()
...
425
...
let swap ch x = let c = new_channel ()
in choose [
wrap (receive ch) (fun (y,c) ->
sync (send c x); y);
wrap (send ch (x,c)) (fun () ->
sync (receive c))
]
426
Timeouts
427
module Timer = stuct open Thread open Event
let set_timer t = let ack = new_channel ()
in let serve () = delay t;
sync (receive ack)
in let _ = create serve ()
in send ack ()
428
8.3 Threads and Exceptions
429
... yields
> /.a.out
Thread 1 killed on uncaught exception Division_by_zero
main terminated regularly ...
The thread was killed, the Ocaml program terminated nonetheless.
430
Then we have
> ./a.out
Fatal error: exception Division_by_zero
Caveat
Exceptions can only be caught in the body of the wrapper function itself,
not behind the sync !
431
8.4 Buffered Communication
432
Then we implement:
module Mailbox =
struct open Thread open Queue open Event
type ’a mbox = ’a channel * ’a channel
let send (in_chan,_) x = sync (send in_chan x)
let receive (_,out_chan) = receive out_chan
let new_mailbox () = let in_chan = new_channel ()
and out_chan = new_channel ()
...
433
...
in let rec serve q = if (is_empty q) then
serve (enqueue (
sync (Event.receive in_chan)) q)
else select [
wrap (Event.receive in_chan)
(fun y -> serve (enqueue y q));
wrap (Event.send out_chan (first q))
(fun () -> let (_,q) = dequeue q
in serve q)
]
in let _ = create serve (new_queue ())
in (in_chan, out_chan)
end
... where first : ’a queue -> ’a returns the first element in the
queue without removing it.
434
8.5 Multicasts
435
The operation new_port generates a fresh port where a message
can be received.
The (non-blocking) operation multicast sends to all registered
ports.
436
The operation multicast sends the message on channel send_ch. The
Operation receive reads from the mailbox of the port.
The multicast channel’s server thread maintains the list of ports:
437
Note that the server thread must respond both to port requests over the
channel req and to send requests over send_ch.
Caveat
Our implementation supports addition, but not removal of obsolete ports.
For an example run, we use a test expression main:
438
...
let main = let mc = new_mchannel ()
in let thread i = let p = new_port mc
in while true do let x = sync (receive p)
in print_int i; print_string ": ";
print_string (x^"\n")
done
in let _ = create thread 1
in let _ = create thread 2
in let _ = create thread 3
in delay 1.0;
multicast mc "Hello!";
multicast mc "World!";
multicast mc "... the end.";
delay 10.0
end
end
439
We obtain
- ./a.out
3: Hello!
2: Hello!
1: Hello!
3: World!
2: World!
1: World!
3: ... the end.
2: ... the end.
1: ... the end.
440
Summary
441
Perspectives
442
9 Datalog: Computing with Relations
443
Discussion
Lecturer:
Name Telefon Email
Esparza 17204 esparza@in.tum.de
Nipkow 17302 nipkow@in.tum.de
Seidl 18155 seidl@in.tum.de
444
Module:
Title Room Time
Discrete Structures MI 1 Thu 12:15-13, Fri 10-11:45
Pearls of Informatics III MI 3 Thu 8:30-10
Funct. Programming and Verification MI 1 Tue 16-18
Optimization MI 2 Mon 12-14, Di 12-14
Student:
Matr.nr. Name Sem.
123456 Hans Dampf 03
007042 Fritz Schluri 11
543345 Anna Blume 03
131175 Effi Briest 05
445
Discussion (cont.)
• The rows correspond to the instances.
• The columns correspond to the attributes.
• Assumption: the first attribute identifies the instance
==⇒ primary key
Consequence: Relationships are tables as well ...
offers:
Name Title
Esparza Discrete Structures
Nipkow Pearls of Informatics III
Seidl Funct. Programming and Verification
Seidl Optimization
446
attends:
Matr.nr. Title
123456 Funct. Programming and Verification
123456 Optimization
123456 Discrete Structures
543345 Funct. Programming and Verification
543345 Discrete Structures
131175 Optimization
447
Possible Queries
==⇒ Datalog
448
Idea: Table ⇐=⇒ Relation
R ⊆ U1 × . . . × Un
where Ui is the set of all possible values for the ith component. In
our example, there are:
int, string, possibly enumeration types
// unary relations represent sets.
449
Predicates can be defined by enumeration of facts ...
450
Rules can be used to deduce further facts ...
451
The knowledge base consisting of facts and rules now can be queried ...
• Datalog finds all values for Z so that the query can be deduced
from the given facts by means of the rules.
• In our examples these are:
Z = "Hans Dampf"
Z = "Anna Blume"
Z = "Effi Briest"
452
Further Queries
453
Further Queries
Caveat
A query may contain none, one or several variables.
454
An Example Proof
The rule
has_attendant (X,Y) :- offers (X,Z), attends (M,Z),
student (M,Y,_).
holds for all X, M, Y, Z.
455
An Example Proof
The rule
has_attendant (X,Y) :- offers (X,Z), attends (M,Z),
student (M,Y,_).
holds for all X, M, Y, Z. By means of the substitution
"Seidl"/X "Funct. Programming ..."/Z
543345/M "Anna Blume"/Y
we prove
offers ("Seidl", "Funct. Programming ...")
attends (543345, "Funct. Programming ...")
student (543345, "Anna Blume", 3)
has_attendant ("Seidl", "Anna Blume")
456
Example 2: A Weblog
Eintrag
Weblog
edits contains + ID
Group + Title + Contents
+ Date
Person
has member + Account owns
+ Name
− Password
trusts
457
Task: Specification of access rights
458
Specification in Datalog
459
Remark
460
9.1 Answering a Query
Problem
equals (X,X).
461
Theorem
Assume that W is a finite set of facts and rules with the following
properties:
462
Theorem
Assume that W is a finite set of facts and rules with the following
properties:
Proof Sketch
463
Calculation of All Provable Facts
R(0) = ∅ R (i + 1 ) = F ( R (i ) )
F ( M) = {h[ a/ X ] | ∃ h :- l1 , . . . , lk . ∈ W :
l1 [ a/ X ], . . . , lk [ a/ X ] ∈ M }
464
We have: R(i) = F i (∅) ⊆ F i+1 (∅) = R(i+1)
465
We have: R(i) = F i (∅) ⊆ F i+1 (∅) = R(i+1)
466
We have: R(i) = F i (∅) ⊆ F i+1 (∅) = R(i+1)
Example
edge (a,b).
edge (a,c).
edge (b,d).
edge (d,a).
t (X,Y) :- edge (X,Y).
t (X,Y) :- edge (X,Z), t (Z,Y).
467
Relation edge :
a b c d
a
b
c
d
468
t (0) a b c d t (1) a b c d
a a
b b
c c
d d
469
t (2) a b c d t (3) a b c d
a a
b b
c c
d d
470
Discussion
471
Discussion
472
9.2 Operations on Relations
473
1. Union
474
... in Datalog:
r( X1 , . . . , Xk ) :- s1 ( X1 , . . . , Xk ).
r( X1 , . . . , Xk ) :- s2 ( X1 , . . . , Xk ).
Example
475
2. Intersection
476
... in Datalog:
r( X1 , . . . , Xk ) :- s1 ( X1 , . . . , Xk ),
s2 ( X1 , . . . , Xk ).
Example
477
3. Relative Complement
478
... in Datalog:
r( X1 , . . . , Xk ) :- s1 ( X1 , . . . , Xk ), not(s2 ( X1 , . . . , Xk )).
Example
479
Caveat
The query
p("Hello!").
?- not (p(X)).
results in infinitely many answers.
480
Caveat (cont.):
Negation is only meaningful when s does not recursively depend on
r ...
481
4. Cartesian Product
S1 × S2 = {( a1 , . . . , ak , b1 , . . . , bm ) | ( a1 , . . . , ak ) ∈ S1 ,
(b1 , . . . , bm ) ∈ S2 }
... in Datalog:
r( X1 , . . . , Xk , Y1 , . . . , Ym ) :- s1 ( X1 , . . . , Xk ), s2 (Y1 , . . . , Ym ).
482
a b c d
a a a
b b b
c c c
d d d
483
Example
lecturer_student (X,Y) :- lecturer (X,_,_),
student (_,Y,_).
Comments
484
5. Projection
... in Datalog:
r( Xi1 , . . . , Xik ) :- s( X1 , . . . , Xm ).
485
a b c d
a a
b b
1 c c
d d
486
a b c d a b c d
a a
b b
1,1 c c
d d
487
6. Join
S1 ✶ S2 = {( a1 , . . . , ak , b1 , . . . , bm ) | ( a1 , . . . , ak+1 ) ∈ S1 ,
(b1 , . . . , bm ) ∈ S2 ,
ak+1 = b1 }
... in Datalog:
r( X1 , . . . , Xk , Y1 , . . . , Ym ) :- s1 ( X1 , . . . , Xk , Y1 ), s2 (Y1 , . . . , Ym ).
488
Discussion
Joins can be defined by means of the other operations ...
S1 ✶ S2 = π1,...,k,k+2,...,k+1+m (
S1 × S2 ∩
U k × π1,1 (U ) × U m−1 )
489
Background
490
Background
− is rather cryptic
− does not support recursive definitions.
491
Example
492
Perspective
493