primrec

Students will often have difficulty with understanding and writing the primrec function for Exercise 3.3, and/or with using it to define (+) and (*).

What we're doing here is writing a function for defining other functions by induction on the structure of a particular algebraic type, NaturalNumber. It is useful here to think of the values of algebraic types as being terms built using the constructors of that type, together with other values as required by the type signatures of each constructor. This is only an approximation, of course. A values might be a graph rather than a term, and it might be undefined, but it's a good place to start.

Conceptually, a function that is defined by structural induction maps the constructors used to build its argument to functions, and then evaluates the result. Consider, again, the notion of summing the values of an integer list:

sum [1,2,3,4,5] ==> sum (1:(2:(3:(4:(5:[]))))) ==> (1+(2+(3+(4+(5+0 ))))) ==> 15

The function sum essentially replaces (:) by (+) and [] by 0. The foldr function enables us to exactly capture this observation, and thereby define sum succinctly and insightfully:

sum = foldr (+) 0

There are a couple of things to note here. The first is that it is mathematically convenient to think of a constant as a zero-ary function, e.g., 0 in the definition of sum. The second is that constructors have a kind of dual identity as functions, and we'll see an example of that soon.

The point to Exercise 3.3 is to define a function primrec that is analogous to foldr. Note that there are a couple of ways to think about primrec, both valid, both equivalent. Most students will think of primrec as a function that takes three arguments: a function to replace the S constructor, a constant (i.e., zero-ary function) to replace Zero, and a natural number to substitute the first two arguments into. This gives rise to a definition like this:

primrec succf zerof Zero = zerof primrec succf zerof (S n) = succf (primrec succf zerof n)

A higher order way to think about this, though, is that primrec is a binary function, which takes just the constructor-replacing functions as arguments, and that it produces a function as a result. This kind of thinking leads naturally to the following definition:

primrec succf zerof = f where f Zero = zerof f (S n) = succf (f n)

I have a slight preference here for the second definition, which focusses on the function we're building, rather than on the results of evaluating that function at a particular point.

Let's turn now to the definitions of (+) and (*). Note that these are instance methods of the Num class, and therefore all of these definitions will occur within an implicit instance Num NaturalNumber implementation block. I think it's useful to start with a small example:

7 + 3 ==> 7 + S ( S (S Zero)) ==> S (7 + S (S Zero)) ==> S ( S (7 + S Zero)) ==> S ( S (S (7 + Zero)) ==> S ( S (S 7))

What this example illustrates is that adding x to y amounts to replacing the Zero in y's definition with x. This observation gives rise to a direct definition of (+):

x + y = primrec S x y

Note here that the use of S on the right-hand side exploits a constructor's dual role as a function. Note also that the definition above hints at the possibility of an η-reduction, but it only hints because the left-hand side is written in infix rather than prefix form. We can fix this:

(+) x y = primrec S x y

η-reduces to

(+) x = primrec S x

which uncovers a "bonus" η-reduction:

(+) = primrec S

That's terse.

The same basic approach gives us a definition of (*). Let's start with an example, as before:

7 * 3 ==> 7 * S (S (S Zero)) ==> 7 + (7 * S (S Zero)) ==> 7 + (7 + (7 * S Zero)) ==> 7 + (7 + (7 + (7 * Zero))) ==> 7 + (7 + (7 + Zero)

If we line up the structures of S (S (S Zero)) and 7 + (7 + (7 + Zero)), we can see that what we're doing is replacing S by (7+), and Zero by itself. This observation gives rise to the following definition:

x * y = primrec (x+) Zero y

As before, we're almost set up for an η-reduction:

(*) x y = primrec (x+) Zero y

becomes

(*) x = primrec (x+) Zero

We could stop at this point, but let's not. The right-hand side of this definition contains only a single x, so maybe we can massage it into a form suitable for η-reduction. The first step is to move the (x+) to the end of the expression, using flip:

(*) x = flip primrec Zero (x+)

Next, we rewrite the section (x+) in application form as (+) x:

(*) x = flip primrec Zero ((+) x)

Note that we had to add a set of parentheses to maintain the right associations. Next, we recognize this as a composition:

(*) x = flip primrec Zero ((+) x)

hence

(*) x = (flip primrec Zero . (+)) x

at which point we can η-reduce:

(*) = flip primrec Zero . (+)

We can call it quits here, or if we like, we can substitute out the definition of (+) in favor of a direct definition of (*) in terms of primrec, flip, (.) and the NaturalNumber constructors:

(*) = flip primrec Zero . primrec S