Skip to content

Commit a0368e3

Browse files
committed
Refine contracts chapter and update config files
Improves clarity and consistency in 'chapter-2-contracts.md' by revising explanations, updating code examples, and correcting terminology. Adds custom dictionary words to VSCode settings and enables the Katex preprocessor in book.toml for better math rendering.
1 parent e0809a6 commit a0368e3

File tree

3 files changed

+55
-44
lines changed

3 files changed

+55
-44
lines changed

.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
@@ -715,7 +717,7 @@ Now, not every contract is as simple as the ones we've shown so far,
715717
but simplicity is a goal. In fact, if you can't write a terse,
716718
simple, but _complete_ contract for a component, there's a good chance
717719
it's badly designed. A classic example is the C library `realloc`
718-
function, which does at least three different things—allocate, deallocate, and resize
720+
function, which does at least three different things—allocate, deallocate, and resize
719721
dynamic memory—all of which
720722
need to be described. A better design would have separated these
721723
functions. So simple contracts are both easy to digest and easy to
@@ -804,10 +806,10 @@ What are the preconditions for removing an element? Obviously, there
804806
needs to be an element to remove.
805807

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

813815
A client of this method is considered to have a bug unless
@@ -820,25 +822,25 @@ bug. The bug could be in the documentation of course, *which is a
820822
part of the method*.
821823

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

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

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

844846
Now, if the postcondition seems a bit glaringly redundant with the
@@ -865,10 +867,10 @@ invariant is also trivially implied. And that is also very commonly
865867
omitted.
866868

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

874876
In fact, the precondition is implied by the summary too. You
@@ -886,8 +888,8 @@ you are going to remove the last element, so the original declaration
886888
should be sufficient:
887889

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

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

934+
<!-- 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. -->
935+
932936
_Some_ precondition on the predicate is needed just to make the result
933937
a meaningful sort with respect to the predicate. For example, a
934938
totally unconstrained predicate could return random boolean values,
935939
and there's no reasonable sense in which the function could be said to
936940
leave the elements sorted with respect to that. Therefore the
937-
predicate at least has to be stable. To leave elements meaningfully
941+
predicate at least has to be deterministic. To leave elements meaningfully
938942
sorted, the predicate has to be *transitive*: if it is `true` for
939943
elements (*i*, *j*), it must also be true for elements (*i*, *j*+1).
940944
A strict weak ordering has both of these properties, among others.
@@ -981,19 +985,19 @@ essential information—but because the statement of effects is tricky,
981985
this is a case where an example might really help.
982986

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

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

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

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

0 commit comments

Comments
 (0)