Download presentation
1
Reading Mathematical Contracts
Annatala Wolf – 2231 Lecture 1
2
Contracts (specs) ≠ Code!
It’s easy to get contracts and code confused because we use a strict language for both of them. In contracts, we use mathematical notation. In code, we use Java syntax and grammar. Often, the two coincide. For example, -18 or false mean the same thing in both contracts and code. But sometimes, the two do not coincide! In contracts, { -1/2 } refers to the set { -½ }. In code, { -1/2 } is likely an array initialization statement, which produces this string of integer: < 0 >. Notice that even the value of the string’s entry is different (think about why).
3
Specifications vs. Code
Since our specs are written in a formal and precise manner, you might wonder how they differ from code. In fact, they are another form of code. The difference is that specifications describe what code does, not how it does it. Specifications can be more abstract than code. Consider the relational behavior of the removeAny() spec for Set. One can even write a specification for a problem that can’t be solved at all, like the halting problem.
4
Why formal (mathematical) contracts?
Formal contracts are the only thing we use in this course that you won’t see anytime soon in industry. Computer scientists are expected to read math, so the formal specifications we require are appropriate for the course. The good news is that, unlike English, math is totally unambiguous. The bad news? You have to learn how to read this shit.
5
Our Contracts (Review)
Preconditions are given in our annotations, and postconditions are given in our annotations. The client (user) of an operation is told only one thing: if they meet the precondition, the operation guarantees the postcondition will hold. If not, any behavior is permitted, including non-termination. The implementer should not check preconditions, because the contract does not need to be fulfilled. We use asserts to help the developer during design (they are removed from the final product).
6
Who does what? should assume must check must make true
In design by contract (DBC), remember: Failing to follow any one of these directives shows you don’t understand DBC, and will cost you points. Client Implementer precondition must check should assume postcondition must make true
7
Mathematical Types Every object or primitive type we use is modeled by an underlying mathematical type: Boolean: either true or false integer: a positive or negative whole number character: a single Unicode character, like or ‘\t’ set: an unordered collection of items (sets have no order and don’t maintain/understand duplicates) string: a totally-ordered collection of items (not Java String) tuple: a group of different things that can be distinguished (like the columns of a database)
8
Examples of Mathematical Models
String: string of character NaturalNumber: non-negative integer Queue<T>: string of T Stack<T>: string of T Sequence<T>: string of T Set<T>: finite set of T Map<K, V>: partial function of K V (you can think of this as a finite set of pairs (i.e. 2-tuples) (K,V), where each K value is associated with at most one V)
9
Use correct math notation!
The mathematical notation we use is very precise. If you don’t use the correct notation on exams or other assignments, you can lose significant points – so make sure you know how to do this.
10
Mathematical Notation
Sets use curly braces: {a, b, …}. The set with no items, { }, is called the empty set. Strings use angle brackets: <a, b, …>. The string with no items, < >, is called the empty string. A string or set with a single element, such as <x>, is not the same as the element x by itself. Tuples use parentheses: (a, b, …). A tuple may contain different types. Order identifies which item is which, but the order chosen is arbitrary. A 2-tuple is often called a pair, a 3-tuple a triple, etc.
11
More Symbols The * symbol means concatenation when it appears between two strings. It means multiply when it appears between two integers. There is never ambiguity, because we always specify types. The entries of a string are all the unique bits of the string, taken as a set. (Remove order and forget duplicates, in other words.) A permutation of a string means a string with all the same elements, in possibly a different order. A string of character (like <‘H’, ‘i’, ‘!’>) can be abbreviated using double quotes (like “Hi!”).
12
One Last Symbol The parallel lines |x| can mean three different things, depending on context: x is a set implies |x| is the size of the set x is a string implies |x| is the length of the string x is a numeric type implies |x| is the absolute value It may seem like “size of a set” and “length of a string” are the same thing, but mathematically the concepts are different. The idea is very similar for finite sets and strings, though: it just counts the number of items (though don’t forget: duplicates are ignored in sets, but differentiated in strings).
13
Understanding Quantifiers
TODO (will explain quantification in detail here, probably with two or three slides)
14
Reading Specifications
/** s2 * |s1| > 0 * |s2| = |s1| - 1 and * for all i, j: integer, a, b: string of integer * where (s1 = a * <i> * <j> * b) * (there exists c, d: string of integer * (|c| = |a| and s2 = c * <(i+j)/2> * d)) */ public static void smooth(Sequence<Integer> s1, Sequence<Integer> s2) {...}
15
Breaking The Specification Down
Sequence is modeled by string of T (in this case Integer), so s1 and s2 are type “string of integer” in this specification. |s2| = |s1| and for all i, j: integer, a, b: string of integer where (s1 = a * <i> * <j> * b) (there exists c, d: string of integer (|c| = |a| and (s2 = c * <(i+j)/2> * d)) Part 1 Part 2
16
The easy part @requires |s1| > 0 @ensures |s2| = |s1| - 1 ...
This tells us s2 will be one item shorter than s1. It makes sense because s1 can’t be empty to start. Clearly, if s1 has one item in it, s2 must become the empty string of integer. But for longer s1 values, we need more information. Since s2 is replaces-mode, its incoming value should not affect the result. The values that appear in s2 must come from s1 or from the contract itself.
17
Quantified Strings When we model strings of items, frequently we will want to talk about what happens throughout the entire string. In this case, we are talking about all possible ways that you can arrange s1 so that there is an <i> next to a <j>. In other words, “consider all consecutive pairs of integers i and j, in s1”. The strings a and b are merely placeholders. It doesn’t matter what they contain—they’re just there so we can say “let’s talk about each consecutive pair of integers in s1”. for all i, j: integer, a, b: string of integer where (s1 = a * <i> * <j> * b) ...
18
we need at least two integers in s1 for this case to even occur
“Vacuously True” If no integers and strings can fit the for all quantifier, then there are no cases for us to test. This makes the statement vacuously true. This could only happen when |s1| < 2. But we already know what happens when |s1| = 1, and |s1| can’t be 0, so this shouldn’t cause a problem. for all i, j: integer, a, b: string of integer where (s1 = a * <i> * <j> * b) ... we need at least two integers in s1 for this case to even occur
19
Quantifiers: There Exists
With “there exists”, we’re making some assertion about the state of an object. Here, we say what part of the value of s2 must be, based on s1. there exists c, d: string of integer (|c| = |a| and s2 = c * <(i+j)/2> * d) this part tells us what we should find in s2 this part tells us where it should be found in s2
20
s2 = c * <(i+j)/2> * d
What are the values? The values in s2 are just the smoothed (averaged) values of each pair of integers. This is why s2 is one shorter than s1. For example, if there are six integers in s1, there are only five consecutive pairs. s2 = c * <(i+j)/2> * d each integer in s2 is the average of two consecutive integers from s1
21
Where do the values go? By adding some information about those placeholder string variables, we can describe exactly how the two strings match up. Can you see how they line up? (Hint: Just ignore b and d!) s1 = a * <i> * <j> * b s2 = c * <(i+j)/2> * d |c| = |a|
22
The Meaning of smooth( )
This procedure averages the consecutive values of s1, and produces s2 with the averaged values in the same order in which the pairs appeared in s1. This spec is a good example because it’s just about the hardest thing you will be expected to read and interpret on an exam. Don’t let the quantifiers scare you. Most of the “string” variables we create with quantifiers are only there as placeholders, so we can talk about multiple cases at the same time.
23
Recursively-Defined Specifications
/** updates seq requires (|seq| mod 2) = 0 ensures seq = SWITCH(#seq) math_function SWITCH(string of integer s): string of integer if |s| = 0 then SWITCH(s) = empty string else if |s| = 2 then there exists a, b: integer where (s = <a> * <b> and SWITCH = <b> * <a>) else there exists a, b: integer; t: string of integer where (s = <a> * <b> * t and SWITCH(s) = <b> * <a> * SWITCH(t)) !*/ public static method weird(Sequence<Integer> seq) { ... } A math_function is just a piece of a specification. It is used to make specs easier to read. It is not code. You can’t make a “call” to SWITCH(s).
24
The Specification for Weird( )
requires (|seq| mod 2) = 0 The sequence must contain an even number of integer elements. ensures seq = SWITCH(#seq) Note that #seq means “the incoming value of seq’s object”. The weird() method will update the sequence object by doing whatever SWITCH describes.
25
Interpreting SWITCH(s)
SWITCH changes one string of integer into a different string of integer. math_function SWITCH(string of integer s): string of integer if |s| = 0 then SWITCH(s) = empty string else if |s| = 2 then there exists a, b: integer where (s = <a> * <b> and SWITCH(s) = <b> * <a>) else there exists a, b: integer; t: string of integer where (s = <a> * <b> * t and SWITCH(s) = <b> * <a> * SWITCH(t)) case 1: |s| = 0 case 2: |s| = 2 case 3: “other”
26
SWITCH(s) Case 1 If you SWITCH(< >), it returns < >. In other words, it does not change s when s is the empty string of integer. if |s| = 0 then SWITCH(s) = empty string
27
SWITCH(s) Case 2 If s has two elements, SWITCH(s) returns a string consisting of those same two integer elements, in reverse order. else if |s| = 2 then there exists a, b: integer where (s = <a> * <b> and SWITCH(s) = <b> * <a>)
28
Limiting Complexity SWITCH(s) doesn’t make any sense at all when the length of s is odd. But that’s okay, because we’re only using it to describe what happens when its length is even. In other words, we don’t need to bother reasoning about cases that won’t come up.
29
SWITCH(s) Case 3 If s has more than two elements, we call the first two a and b. We reverse these two, then recurse to append SWITCH(t), where t is the rest of the string. It’s safe, since |s| is even implies |s|-2 is even. else there exists a, b: integer; t: string of integer where (s = <a> * <b> * t and SWITCH(s) = <b> * <a> * SWITCH(t))
30
The Meaning of SWITCH(s)
Given a string of integer of even length, SWITCH takes every two integers and swaps them, like so: SWITCH(< 1, 2, 3, 4, 5, 6 >) = < 2, 1, 4, 3, 6, 5 > Note: just because a spec is recursive doesn’t mean you need to use recursion to write code that implements it. This method would be both simpler and more efficient if written iteratively.
31
Really Hard Contracts…
TODO (will add Project 3 contract reading stuff here)
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.