Skip to content

Commit ac444d6

Browse files
authored
Merge pull request #37 from stlab/sean-parent/contracts-cleanup-2
Refine contracts chapter and update config files
2 parents 0d00da7 + 029d3f8 commit ac444d6

3 files changed

Lines changed: 55 additions & 44 deletions

File tree

.vscode/settings.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,11 @@
66
}
77
],
88
"rewrap.autoWrap.enabled": true,
9-
"rewrap.wrappingColumn": 80
9+
"rewrap.wrappingColumn": 80,
10+
"cSpell.words": [
11+
"footgun",
12+
"Gitter",
13+
"irreflexivity",
14+
"preorder"
15+
]
1016
}

better-code/book.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ enable = true
1919

2020
[output.html.print]
2121
enable = true
22+
23+
[preprocessor.katex]
24+

better-code/src/chapter-2-contracts.md

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ Hoare used this notation, called a “Hoare triple,”
142142
which is an assertion that if **precondition** *P* is met, operation
143143
*S* establishes **postcondition** *Q*.
144144

145+
<!-- This had been using math for pre and post conditions, but I find mixing math and code makes it look like we are talking about different `x` and $x$ variables and equality vs. assignment gets confusing. I think if the operation is expressed in code, the conditions should be expressed in code. -->
146+
145147
For example:
146148

147149
- if we start with `x == 2` (precondition), after `x += 1`, `x == 3` (postcondition):
@@ -156,7 +158,7 @@ For example:
156158
What makes preconditions and postconditions useful for formal proofs
157159
is this *sequencing rule*:
158160

159-
> {P}S{Q} {Q}T{R} {P}S;T{R}
161+
> {P}S{Q} ^ {Q}T{R} => {P}S;T{R}
160162
161163
Given two valid Hoare triples, if the postconditions of the first are
162164
the preconditions of the second, we can form a new valid triple describing
@@ -327,7 +329,7 @@ When we talk about an instance being “in a good state,” we
327329
mean that its type's invariants are satisfied.
328330

329331
For example, this type's public interface is like an
330-
array of pairs, but it stores elements of those pairs separate
332+
array of pairs, but it stores elements of those pairs in separate
331333
arrays.[^array-pairs]
332334

333335
[^array-pairs]: You might want to use a type like this one to store
@@ -716,7 +718,7 @@ Now, not every contract is as simple as the ones we've shown so far,
716718
but simplicity is a goal. In fact, if you can't write a terse,
717719
simple, but _complete_ contract for a component, there's a good chance
718720
it's badly designed. A classic example is the C library `realloc`
719-
function, which does at least three different things—allocate, deallocate, and resize
721+
function, which does at least three different things—allocate, deallocate, and resize
720722
dynamic memory—all of which
721723
need to be described. A better design would have separated these
722724
functions. So simple contracts are both easy to digest and easy to
@@ -805,10 +807,10 @@ What are the preconditions for removing an element? Obviously, there
805807
needs to be an element to remove.
806808

807809
```swift
808-
/// Removes and returns the last element.
809-
///
810-
/// - Precondition: `self` is non-empty.
811-
public mutating func popLast() -> T { ... }
810+
/// Removes and returns the last element.
811+
///
812+
/// - Precondition: `self` is non-empty.
813+
public mutating func popLast() -> T { ... }
812814
```
813815

814816
A client of this method is considered to have a bug unless
@@ -821,25 +823,25 @@ has a bug. The bug could be in the documentation of course, *which is
821823
a part of the method*.
822824

823825
```swift
824-
/// Removes and returns the last element.
825-
///
826-
/// - Precondition: `self` is non-empty.
827-
/// - Postcondition: The length is one less than before
828-
/// the call. Returns the original last element.
829-
public mutating func popLast() -> T { ... }
826+
/// Removes and returns the last element.
827+
///
828+
/// - Precondition: `self` is non-empty.
829+
/// - Postcondition: The length is one less than before
830+
/// the call. Returns the original last element.
831+
public mutating func popLast() -> T { ... }
830832
```
831833

832834
The invariant of this function is the rest of the elements, which are
833835
unchanged:
834836

835837
```swift
836-
/// Removes and returns the last element.
837-
///
838-
/// - Precondition: `self` is non-empty.
839-
/// - Postcondition: The length is one less than before
840-
/// the call. Returns the original last element.
841-
/// - Invariant: the values of the remaining elements.
842-
public mutating func popLast() -> T { ... }
838+
/// Removes and returns the last element.
839+
///
840+
/// - Precondition: `self` is non-empty.
841+
/// - Postcondition: The length is one less than before
842+
/// the call. Returns the original last element.
843+
/// - Invariant: the values of the remaining elements.
844+
public mutating func popLast() -> T { ... }
843845
```
844846

845847
Now, if the postcondition seems a bit glaringly redundant with the
@@ -866,10 +868,10 @@ invariant is also trivially implied. And that is also very commonly
866868
omitted.
867869

868870
```swift
869-
/// Removes and returns the last element.
870-
///
871-
/// - Precondition: `self` is non-empty.
872-
public mutating func popLast() -> T { ... }
871+
/// Removes and returns the last element.
872+
///
873+
/// - Precondition: `self` is non-empty.
874+
public mutating func popLast() -> T { ... }
873875
```
874876

875877
In fact, the precondition is implied by the summary too. You
@@ -887,8 +889,8 @@ you are going to remove the last element, so the original declaration
887889
should be sufficient:
888890

889891
```swift
890-
/// Removes and returns the last element.
891-
public mutating func popLast() -> T { ... }
892+
/// Removes and returns the last element.
893+
public mutating func popLast() -> T { ... }
892894
```
893895

894896
In practice, once you are comfortable with this discipline, the
@@ -931,12 +933,14 @@ the elements arranged from least to greatest. The contract gives an
931933
explicit precondition that isn't implied by the summary: it requires
932934
that the predicate be a strict weak ordering.
933935

936+
<!-- SRP: this section bothers me. "among others" instead of fully spelling out the requirements, using (i, j + 1) which may not exist, and the n^2 comparisons without calling out the O(n^3) complexity or which properties could be practically checked. Also is "stable" the term we want to use? Regular and deterministic are also candidates. I've tried to rewrite this a couple of times, but it just gets too complex and the main point is lost. -->
937+
934938
_Some_ precondition on the predicate is needed just to make the result
935939
a meaningful sort with respect to the predicate. For example, a
936940
totally unconstrained predicate could return random boolean values,
937941
and there's no reasonable sense in which the function could be said to
938942
leave the elements sorted with respect to that. Therefore the
939-
predicate at least has to be stable. To leave elements meaningfully
943+
predicate at least has to be deterministic. To leave elements meaningfully
940944
sorted, the predicate has to be *transitive*: if it is `true` for
941945
elements (*i*, *j*), it must also be true for elements (*i*, *j*+1).
942946
A strict weak ordering has both of these properties, among others.
@@ -983,19 +987,19 @@ essential information—but because the statement of effects is tricky,
983987
this is a case where an example might really help.
984988

985989
```swift
986-
/// Sorts the elements so that `areInIncreasingOrder(self[i+1],
987-
/// self[i])` is false for each `i` in `0 ..< length - 2`.
988-
///
989-
/// var a = [7, 9, 2, 7]
990-
/// a.sort(areInIncreasingOrder: <)
991-
/// print(a) // prints [2, 7, 7, 9]
992-
///
993-
/// - Precondition: `areInIncreasingOrder` is [a strict weak
994-
/// ordering](https://simple.wikipedia.org/wiki/Strict_weak_ordering)
995-
/// over the elements of `self`.
996-
/// - Complexity: at most N log N comparisons, where N is the number
997-
/// of elements.
998-
mutating func sort(areInIncreasingOrder: (Element, Element)->Bool) { ... }
990+
/// Sorts the elements so that `areInIncreasingOrder(self[i+1],
991+
/// self[i])` is false for each `i` in `0 ..< length - 2`.
992+
///
993+
/// var a = [7, 9, 2, 7]
994+
/// a.sort(areInIncreasingOrder: <)
995+
/// print(a) // prints [2, 7, 7, 9]
996+
///
997+
/// - Precondition: `areInIncreasingOrder` is [a strict weak
998+
/// ordering](https://simple.wikipedia.org/wiki/Strict_weak_ordering)
999+
/// over the elements of `self`.
1000+
/// - Complexity: at most N log N comparisons, where N is the number
1001+
/// of elements.
1002+
mutating func sort<T>(areInIncreasingOrder: (T, T)->Bool) { ... }
9991003
```
10001004

10011005
#### Letting Simplicity Drive Design
@@ -1033,7 +1037,6 @@ Therefore, if we have a sorting implementation that works with any
10331037
strict weak order, we can easily convert it to work with any total
10341038
preorder by passing the predicate through `converseOfComplement`.
10351039

1036-
10371040
Note that the name of the predicate became simpler: it no longer tests
10381041
that its arguments represent an _increase_. Instead, it tells us
10391042
whether the order is correct. Because the summary is no longer
@@ -1108,7 +1111,7 @@ contract is an engineering decision you will have to make. To reduce
11081111
the risk you could add this assertion[^checks], which will stop the program if
11091112
the ordering is strict-weak:
11101113

1111-
```
1114+
```swift
11121115
precondition(
11131116
self.isEmpty || areInOrder(first!, first!),
11141117
"Total preorder required; did you pass a strict-weak ordering?")
@@ -1160,7 +1163,6 @@ For example,
11601163
> - Document the performance of every operation that doesn't execute in
11611164
> constant time and space.
11621165
1163-
11641166
It is reasonable to put information in the policies without which the
11651167
project's other documentation would be incomplete or confusing, but
11661168
you should be aware that it implies policies *must be read*. We

0 commit comments

Comments
 (0)