Chapter 8 Outline¶
Here’s the general structure of this outline:
. . .
* <section number> <section title>
* p<paragraph number>. <One sentence summary of _what_ the
main subject is. Not an explanation of the subject,
unless I feel like explaining it.>
* f<figure number>. <One sentence summary.>
. . .
8.1 Recursion
p1. Characteristics of recursive function definitions.
p2. An example of recursion as a pattern that occurs naturally in language.
p3. I’m not sure what the intention of this paragraph is. Why is the author bringing up LC, Turing Completeness, and the Y-combinator here? How is it relevant to writing recursive functions in Haskell?
p4. Recursion is the only method of expressing repetition in Haskell, so you need to understand it in order to read others code.
8.2 Factorial!
p1. We will demonstrate a recursive function called factorial.
p2. Let’s evaluate 4!:
f1. Shows the evaluation steps for 4!.
p3. Now let’s look at a factorial definition in Haskell, for only one input, the number 4.
f2. Shows a definition of a literal value equivalent to
factorial 4
.
p4. The code above only covers one possible result for
factorial
. We want to express the general idea offactorial
.p5. A base case is non-recursive, and is what stops self-application. An example follows.
f3. Shows factorial definition generalized for any input value, but missing a base case, named
brokenFact1
.
p6. Let’s apply this to 4 and see what happens:
f4. Shows the evaluation steps of
brokenFact1
applied to 4.
p7. The base case stops recursive calls. Here’s what it looks like for
factorial
:f5. Shows a complete definition of
factorial
, as well as the evaluation steps ofbrokenFact1 4
.
- p8. “The base case for
factorial
, defined asfactorial 0 = 1
provides a stopping point, so the reduction changes:”
- f6. Shows the evaluation steps of the fully defined
factorial
function, which has a base case.
- f6. Shows the evaluation steps of the fully defined
- p8. “The base case for
p9. Making our base case an identity value for the recursive operation means that applying it doesn’t change the value.
8.2.1 Another way to look at recursion
p1. Composition chains function applications together.
p2. Composition has a definite number of repetitions, but recursive calls are indefinite.
p3. Function composition has the following type:
f1. Shows the type signature of the
(.)
operator.
p4. And when we use it like this:
f2. Shows an example of using
(.)
to compose functions that get the first five odd numbers after three.
p5.
p6. “Recursion is self-referential composition.” Or, really, composition resembles the function call stack that is accumulated during recursive calls.
p7. “Now look again at how th ecompose function
(.)
is written:”f3. Shows type signature and term-level definition of the
(.)
operator.
p8. Explanation of what function composition is – a pipeline of function applications.
f4. Shows a term-level definition of
(.)
, again.
p9. “…instead of a fixed number of applications, recursive functions rely on inputs to determine when to stop applying function to successive results.”
p10. “Let’s look at aomse code to see the similarity in patterns:”
f5. Shows a incrementing function and an expression named
three
with a value of3
, made by composing the increment function three times.
p11. “We don’t presently have a means of changing how many times we want it to apply ince without writing a new function.”
p12. Generalizing inc requires a new function.
f6. Shows the definition of
incTimes
.
p13. Explains how
incTimes
lets you control the number of repetitions with thetimes
parameter.f7. Shows
incTimes
applied to different values in GHCi.
p14. How the risk of infinite recursion is minimized in
incTimes
.p15. “We can absract the recursion out of
incTimes
, too:”f8. Shows
incTimes
defined in terms ofapplyTimes
, which adds a parameter for the function to be applied, instead of hard-coding the increment function in.
f16. “When we do, we can make the composition more obvious in
applyTimes
:”f9. Shows
applyTimes
rewritten to use the(.)
operator, instead of parenthesis, so that reduction steps don’t create nested parenthesis.
p17. “We’re recursively composing our function
f
withapplyTies (n-1) f
however many subtractions it takes to getn
to0
!”
8.2.2 Intermission: Exercise
p1. “Write the evaluation of the following. It might be a little less noisy if you do so with the form that didn’t use
(.)
.f1. Shows
applyTimes 5 (+1) 5
.
8.3 Bottom
p1. Bottom represents computations that don’t result in a value, like expressions that result in an error or infinite loops. Bottom corresponds to false in logic.
f1. Shows what happens when you evaluate an infinitely recursive expression in GHCi.
p2. Explains the GHCi output.
p3. “Next let’s define a function that will return an exception:”
f2. Shows a function that explicitly throws an error for the value
True
.
p4. “And let’s try that out in GHCi:”
f3.
p5. Explanation of GHCi output.
p6. “Another example of a bottom would be a partial function. Let’s consider a rewrite of the previous function:” Example of an infinite loop.
f4. Example of a partial function, the only input it’s defined for is
False
.
p7. This new function will give us a different exception.
f5. Shows the GHCi output of our new partial function definition. “Non-exhaustive patterns in function f.”
p8. Haskell has made the fallback case for undefined inputs an error. The previous function was really:
f6. Shows
f
with an explicitly defined fallback case that throws an error.
p9. Partial vs total. How do we make our
f
into a total function?f7. Shows a simplified definition of the
Maybe
datatype.
p10. Explanation of
Maybe
. “Here’s how we’d use it withf
:”f8. Shows
f
adapted to return a result of typeMaybe Int
.
p11. We’ll get a type error if we try to load the code.
f9. Shows
f
, usingMaybe
, but missing aJust
for one of the equations.f10. Show what happens when you attempt to load f9 into GHCi.
No instance for (Num (Maybe Int))
.
p12. We can get a better error message by making the result of
0
for our base case a concreteInt
type.f11. Basically f9 with
f False = 0 :: Int
.
p13. “And then get a better type error in the bargain:”
f12. Shows loading f11 into GHCi.
Couldn't match expected type ‘Maybe Int’ with actual type ‘Int’
.
p14. “We’ll explain
Maybe
in more detail later.”
8.4 Fibonacci numbers
p1. In order to demonstrate how to create recursive functions, we’re going to walk through how to write a function that calculates the \(n\)th element of the Fibonacci sequence.
8.4.1 Consider the types
p2. First consider what the input and output should be, and then encode that in a type signature. The preconditions for valid input are hints about what type you should use.
f1. Shows the type signature of
fibonacci
.
8.4.2 Consider the base case
p3. When can you solve the problem directly, without recursing? In this case,
fibonacci
should only operate on positive numbers, so if we get an argument value of 0, we’ll return a 0 to stop the recursion. (It would probably make more sense to use a different type.)p4. Fibonacci requires two base cases, since the sequence by definition starts with \((0,1,…)\).
f2. Shows equations representing the two base cases of the
fibonacci
function and the function type signature in Haskell.
8.4.3 Consider the arguments
I don’t understand this section.
p5. Each argument is a number that represents an index for the element of the Fibonacci sequence we want to retrieve.
p6. In order to come up with the new element we must retrieve the two preceding elements.
f3. Same as the last figure, but also shows a stub for the recursive case that contains the arguments without any function calls.
8.4.4 Consider the recursion
p7. How will the function call itself? What needs to happen next to produce a Fibonacci number?
f4. Shows a stub of the
fibonacci
function definition with the two base cases, but without a complete recursive case.
p8. “If you pass the value 6 to that function, what will happen?”
f5. Shows GHCi output of
fibonacci 6
.
p9. We want to add the elements, not the index numbers of those elements. So we’ll call
fibonacci
to retrieve them.f6. Shows the complete definition of
fibonacci
, with a working recursive case.
p10. “Now, if we apply this function to the value 6, we will get a different result:”
f7. Shows the GHCi output of
fibonacci 6
using the new definition.
p11. Why do we get this result? Because
fibonacci
evaluates its arguments recursively.f8. Show each recursive function call that occurs when evaluating
fibonacci 6
.
p12. “0 and 1 are defined as being equal to 0 and 1. So at this point, our recursion stops, and the function starts adding up the result:”
f9. Shows the process of adding together the reduced value of all the recursive function calls.
p13. Thinking about the evaluation process ahead of time can be intimidating. But you don’t have to do everything at once.
8.5 Integral division from scratch
p1. Multiplication can be defined in terms of repeated addition. Likewise, division can be defined in terms of repeated subtraction.
p2. We will show how to define a function that performs multiplication in terms of addition using recursion, step by step.
(Instead of explaining how to come up with a solution, this explains an existing solution step by step. Annoying!)
f1. Shows the type signature for
dividedBy
.
p3. “Instead of having all the types labeled Integer we can instead do:”
f2. Shows type aliases
Numerator
,Denominator
, andQuotient
in the type signature fordividedBy
.
p5.
type
introduces a type alias.p6. We aren’t going to use those type synonyms after all. We also haven’t written out a recursive implementation of
dividedBy
yet.p7. The base case is when our result is lower than the divisor.
f3. Shows a psuedocode example of \(20/4\) in terms of repeated subtraction steps. In comments, a stopping condition (result < divisor), and a count of the number of subtraction steps are mentioned.
p8. “Otherwise, we’ll have a remainder. Let’s look at a case where it doesn’t divide evenly:”
f4. Shows \(24/5\) in the same style as above.
p9. We can generalize the calculations in the figures above as a function. Also, now that the possibility of a remainder has been pointed out, we want to reflect it in the type signature by returning a tuple of
(count, remainder)
.f5. Shows a definition of
dividedBy
.
p10. We changed the type signature to use
Integral a =>
and also to return a tuple(a, a)
.p11. Explanation of
go
function idiom. Go functions are inner functions. This one keeps track of an extra argument, the count.p12. Explains the two branches of the go function.
p13. The result is our base case.
p14. “Here’s an example of how dividedBy expands but with the code inlined:”
f6.
dividedBy 10 2
p15. First we’ll show it in psuedocode, but keep track of how many times we’ll subtract.
f7.
p16.
p17. “Now, we’ll expand the code:”
f8. Shows a fragment of
dividedBy
’s code during evaluation..
p18. “The otherwise above is literally the value True, so if the first branch fails, the otherwise branch always succeeds:”
f9. Continues the evaluation of
dividedBy
’s recursive branch until the base case it hit.
f19. Explanation of final output.
8.6 Chapter exercises
8.6.1 Review of types
1
a
b
c
d
2
a
b
c
d
3
a
b
c
d
4
a
b
c
d
8.6.2 Reviewing currying
p1. Desk-check the evaluation steps of the following expressions.
f1. Shows the definitions of a few functions that concatenate strings and rearrange arguments.
1
2
3
4
5
6
8.6.3 Recursion
1
2
3
8.6.4 Fixing
dividedBy
p1.
dividedby
is a undefined for numbers 0 or less.p2. Using
div
we can see how negative numbers should be handled:f1. Shows GHCi output of div against different arguments, both positive and negative.
p3. The next issue is how to handle zero. Let’s use a datatype to represent the possibility of a result or a division by zero.
f2. Shows the definition of
DividedResult
.
8.6.5
McCarthy91
functionp1. We’ll describe a function in English, math notation, and also show some test cases. Your task is to write it in Haskell.
p2. “The McCarthy 91 function yields x - 10 when x > 100 and 91 otherwise. The function is recursive:”
f1. Shows the McCarthy91 function in math notation.
f2. Show an name binding in haskell for the identifier
mc91
toundefined
.
p3. Map distributes a function over every element of a list.
f3. Shows the GHCi output of
map mc91 [95..110]
8.6.6 Numbers into words
f1.
p1.
p2.
p3.
p4.
f2.
p5. “Also consider:”
f3.
p6. “Here is what your REPL output should look like when it’s working:”
f4. Shows the GHCi output of
wordNumber 123456
.
8.7 Definitions
8.7.1 Recursion
p1.
p2. “This function is not recursive:”
f1.
p3. “This one is recursive:”
f2.