H. Conrad Cunningham
6 February 2019
Copyright (C) 2017, 2018, 2019, H. Conrad Cunningham
Acknowledgements: I originally created these slides in Fall 2017 to accompany what is now Chapter 9, Recursion Styles, Correctness, and Efficiency, in the 2018 version of the Haskell-based textbook Exploring Languages with Interpreters and Functional Programming. This Scala version adapts the Haskell-based work for the revised Scala-based notes.
Browser Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of February 2019 is a recent version of Firefox from Mozilla.
Examine how to consider termination, preconditions, and postconditions in function design
Introduce different styles of recursive programs
linear and nonlinear – backward and forward
tail recursion — logarithmic
Explore their effects on time and space efficiency
Every recursive call must move the computation closer to some base case (and it must not be possible to miss the base case).
Recursive function with at most one recursive application occurring in any leg of definition
Precondition? Postcondition?
Termination?
Time complexity? (e.g. count multiplications, recursive calls)
Space complexity? (e.g. count max active stack frames)
Recursive function in which the evaluation of some leg requires more than one recursive application
Precondition? Postcondition?
Termination?
Time complexity? Space complexity?
fib (n)
combinatorially explosive in time
Algorithms matter! Linear better than superlinear
Linear recursion can be optimized to loop, perhaps using separate stack
Nonlinear recursion more difficult to optimize
Recursive application embedded within another expression
Evaluation work to do after recursive call returns
Compiler can optimize backward linear to loop (using stack)
Backward recursion often easy to write, reason about
Can we convert to more efficient function?
Recursive application not embedded within another expression
Evaluation work done in argument list (before call if eager)
Add auxiliary function factIter
whose second argument
accumulates result incrementally
Precondition of factIter(n,r)
?
Postcondition of factIter(n,r)
?
Termination of factIter(n,r)
?
Time complexity of factIter(n,r)
?
for optimized?
Space complexity of factIter(n,r)
?
for optimized?
Recursive application both forward and linear recursive — factIter
tail recursive
Compiler can optimize as loop without separate stack, reuses runtime stack frame, returns directly to caller
factIter
parameter r
accumulating parameter
accumulator standard method for converting backward to tail
factIter
more general than
factorial
— equivalent if
accumulator initially 1
def fib2(n: Int): Int = {
def fibIter(n: Int, p: Int, q: Int): Int = n match {
case 0 => p
case m => fibIter(m-1,q,p+q)
}
if (n >= 0)
fibIter(n,0,1)
else
sys.error(s"Fibonacci undefined for $n")
}
Precondition of fibIter(n,p,q)
?
Postcondition?
Termination of fibIter(n,p,q)
?
Time complexity of fibIter(n,p,q)
?
fib(n-1) + fib(n-2)
by cheap p + q
Space complexity of fibIter(n,p,q)
?
When optimized?
Observation: for n >= 0
, b^n =
Precondition? Postcondition?
Termination?
Time complexity? Space complexity?
def expt2(b: Double, n: Int): Double = {
def exptIter(n: Int, p: Double): Double =
n match {
case 0 => p
case m => exptIter(m-1,b*p)
}
if (n >= 0)
exptIter(n,1)
else
sys.error(s"Cannot raise to negative power $n")
}
Precondition of exptIter
?
Postcondition?
Termination of exptIter
?
Time complexity? Space complexity?
Observation: for n >= 0
, b^n =
b^n = b^(n/2)^2 if n is even
b^n = b * b^(n-1) if n is odd
Incorporate in expt3
def expt3(b: Double, n: Int): Double = {
def exptAux(n: Int): Double = n match {
case 0 => 1
case m if (m % 2 == 0) => // i.e. even
val exp = exptAux(m/2)
exp * exp // backward recursion
case m => // i.e. odd
b * exptAux(m-1) // backward recursion
}
if (n >= 0)
exptAux(n)
else
sys.error(s"Cannot raise to negative power $n")
}
def exptAux(n: Int): Double = n match {
case 0 => 1
case m if (m % 2 == 0) => // i.e. even
val exp = exptAux(m/2)
exp * exp // backward recursion
case m => // i.e. odd
b * exptAux(m-1) // backward recursion
}
Precondition of exptAux(b,n)
?
Postcondition?
Termination of exptAux(n)
?
Time complexity of exptAux(n)
?
Space complexity?
Styles of recursion (linear and nonlinear, backward and forward, tail, logarithmic)
Correctness (precondition, postcondition, and termination)
Efficiency estimation (time and space complexity)
Transformations to improve efficiency (auxiliary function, accumulator)