Definition 6.14. A (consistent) theory is called categorical or univalent if all of its models are isomorphic 6 6 This is the original definition by Oswald Veblen Modern logicians talk about κ-categorical theories: all models
7. Deriving a Generic Algorithm To generalize something means to think it.
Hegel, Philosophy of Right
In this chapter we’ll take the Egyptian multiplication algorithm from Chapter 2 and, by using the mathematical abstractions introduced in the previous chapter, generalize it to apply to a wide variety of problems beyond simple arithmetic.
7.1 Untangling Algorithm Requirements
Two steps are required to write a good piece of code. The first step is to get the algorithm right. The second step is to figure out which sorts of things (types) it works for. Now, you might be thinking that you already know the type—it’s int or float or whatever you started with. But that may not always be the case; things change. Next year someone may want the code to work with unsigned ints or doubles or something else entirely. We want to design our code so we can reuse it in these different situations.
Let’s take another look at the multiply-accumulate version of the Egyptian multiplication algorithm we developed in Chapter 2. Recall that we are multiplying n by a and accumulating the result in r; also recall that we have a precondition that neither n nor a can be zero:
Click here to view code image
int mult_acc4(int r, int n, int a) { while (true) { if (odd(n)) { r = r + a; if (n == 1) return r; } n = half(n); a = a + a; } }
This time we’ve written some of the code in a slanted typeface and some of it in a bold typeface. Notice that the slanted and bold parts are disjointed; there are no places where a “slanted variable” and a “bold variable” are combined or interact with each other in any way. This means that the requirements for being a slanted variable don’t have to be the same as the requirements for being a bold variable—or to put it in programming language terms, they can be different types.
So what are the requirements for each kind of variable? Until now, we’ve declared the variables as ints, but it seems as if the algorithm would work for many other similar types as well. Slanted variables r and a must be some type that supports adding—we might say that they must be a “plusable” type. The bold variable n must be able to be checked for oddness, compared with 1, and must support division by 2 (it must be “halvable”). Note that division by 2 is a much more restricted operation than division in general. For example, angles can be divided by 2 with ruler-and-compass construction, while dividing them by 3 is impossible in that framework. We’ve established that r and a are the same type, which we’ll write using the template typename A. Similarly, we said that n is a different type, which we’ll call N. So instead of insisting that all the arguments be of type int, we can now write the following more generic form of the program:
Click here to view code image
template <typename A, typename N> A multiply_accumulate(A r, N n, A a) { while (true) { if (odd(n)) { r = r + a; if (n == 1) return r; } n = half(n); a = a + a; } }
This makes the problem easier—we can figure out the requirements for A separately from the requirements on N. Let’s dig a bit deeper into these types, starting with the simpler one.
7.2 Requirements on A
What are the syntactic requirements on A? In other words, which operations can we do on things belonging to A? Just by looking at how variables of this type are used in the code, we can see that there are three
operations:
• They can be added (in C++, they must have operator+).
• They can be passed by value (in C++, they must have a copy constructor). • They can be assigned (in C++, they must have operator=).
We also need to specify the semantic requirements. That is, we need to say what these operations mean. Our main requirement is that + must be associative, which we express as follows:
A(T) ∀a, b, c T : a + (b + c) = (a + b) + c
(In English, we might read the part before the colon like this: “If type T is an A, then for any values a, b, and c in T, the following holds: ....”)
Even though + is associative in theory (and in math generally), things are not so simple on computers. There are real-world cases where associativity of addition does not hold. For example, consider these two lines of code:
w = (x + y) + z; w = x + (y + z)
Suppose x, y, and z are of type int, and z is negative. Then it is possible that for some very large values, x + y will overflow, while this would not have happened if we added y + z first. The problem arises because addition is not well defined for all possible values of the type int; we say that + is a partial function.
To address this problem, we clarify our requirements. We require that the axioms hold only inside the domain of definition—that is, the set of values for which the function is defined. 1
1 For a more rigorous treatment of this topic, see Section 2.1 of Elements of Programming by Stepanov and
McJones. * * *
In fact, there are a couple more syntactic requirements that we missed. They are implied by copy construction and assignment. For example, copy construction means to make a copy that is equal to the original. To do this, we need the ability to test things belonging to A for equality:
• They can be compared for equality (in C++, they must have operator==). • They can be compared for inequality (in C++, they must have operator!=).
Accompanying these syntactic requirements are semantic requirements for what we call equational reasoning; equality on a type T should satisfy our expectations:
• Inequality is the negation of equality. (a ≠ b) ¬(a = b)
• Equality is reflexive, symmetric, and transitive. a = a
a = b b = a a = b ∧ b = c a = c
for any function f on T, a = b f(a) = f(b)
The three axioms in the middle (reflexivity, symmetry, and transitivity) provide what we call equivalence, but equational reasoning requires something much stronger, so we add the substitutability requirement. We have a special name for types that behave in the “usual way”—regular types:
Definition 7.1. A regular type T is a type where the relationships between construction, assignment, and