********************** Chapter 8: Recursion ********************** .. image:: figures/nickieatmirror.jpg :align: center :alt: A picture of a girl looking into a mirror, opposite another mirror, forming an infinite cooridor of reflections. This is a visual example of recursion. The nested instances of the reflection are the recursive case. The fade to dark green is the base case. "Recursion (rĭ-kûr’-zhən) noun. If you still don’t get it, see recursion." **A recursive structure is one that contains a smaller instance of itself.** A process or structure is recursive when it contains progressively smaller nested instances of itself. "An object is said to be recursive if it partially consists or is defined in terms of itself." ~ Niklaus Wirth. Algorithms + Data Structures = Programs. ISBN-13: 978-0130224187. "An entity or concept is said to be recursive when simpler or smaller self-similar instances form part of its constituents." ~ Manuel Rubio-Sánchez. Introduction to Recursive Programming. ISBN-13: 978-1-4987-3528-5. "The recursive problem solving process can be described loosely as follows: * If the given instance of the problem can be solved directly, do so. * Otherwise, reduce it to one or more smaller instances of the same problem." ~ Jeff Erickson. `Algorithms `_. ISBN-13: 978-1792644832. "The power of recursion evidently lies in the possibility of defining an infinite set of objects by a finite statement. In the same manner, an infinite number of computations can be described by a finite recursive program, even if this program contains no explicit repetitions." ~ Niklaus Wirth. `Algorithms + Data Structures = Programs `_. ISBN-13: 978-0130224187. "Recursion is the root of computation since it trades description for time." ~ Alan Perlis. `Epigrams on Programming `_. 8.1 Recursion ------------- .. topic:: Unifing control-flow and data-flow Function calls are the only mechanism for control flow in Haskell. They're also the only mechanism for changing values. This effectively means that the control flow and data flow of a program are the same; both control and data go from function call to function call. Rather than having a separate graph for the control flow and data flow of a program, there is one graph that represents both. This makes execution of a Haskell program less like updating registers of a machine that advances a program counter, and more like evaluating a system of equations in algebra. .. topic:: Recursive function definitions without a name Paragraph three asks this question: How do we define a recursive anonymous function? Doesn't the function need a name so that it can call itself? One workaround is to provide a duplicate of the function as an input argument value, which is then bound to a parameter name so that it can be called within the function definition, like so:: ·∾ import Data.Function (fix) ·∾ -- Fix duplicates the function and feeds ·∾ -- it to itself as the first argument. ·∾ fix (\f n -> if n == 0 then 1 else n * f (n-1)) 3 6 ·∾ import Unsafe.Coerce (unsafeCoerce) ·∾ -- Fix, above, corresponds to the Y-combinator ·∾ -- in lambda calculus, but the definition is ·∾ -- pretty different. It looks like ·∾ -- fix f = let x = f x in x. The lambda calculus ·∾ -- version looks like (λy.(λx.y(xx))(λx.y(xx))). ·∾ -- Using unsafeCoerce we can write a definition ·∾ -- of the Y-combinator that corresponds closely ·∾ -- to that found in untyped lambda calculus. ·∾ uc = unsafeCoerce ·∾ :{ ⋮ (\y -> (\x -> y (uc x x)) (\x -> y (uc x x))) ⋮ (\f n -> if n == 0 then 1 else n * f (n-1)) ⋮ 6 ⋮ :} 6 ·∾ :{ ⋮ (\f -> (\x -> f (uc x x)) (\x -> f (uc x x))) ⋮ (\f n -> if n == 0 then 1 else n * f (n-1)) ⋮ 20 ⋮ :} 2432902008176640000 In this chapter, we will: * explore what recursion is and how recursive functions evaluate; * go step-by-step through the process of writing recursive functions; * have fun with bottom. 8.2 Factorial! -------------- Let's examine a simple factorial function:: factorial 0 = 1 factorial n = n * factorial (n - 1) .. "Base case" is first mentioned in 8.2, paragraph 5, **"Let's look at some broken code to .. introduce the concept of a base case:"**. .. .. paragraph 7, sentence a **"The way we can stop a recursive expression is by having a base case .. that stops the self-application to further arguments."**, sentence c **"Here's what that looks .. like for factorial:"**. Recursive functions are comprised of two categories of input cases: base cases and recursive cases. A recursive function can contain any number of base cases, but must have at least one recursive case. .. What is a "case" in mathematics? I've seen a few phrases that use the term: special case, edge .. case, base case, recursive case. .. .. ddg.gg "!mw case". 1a: a set of circumstances or conditions. 2: condition. 4: what actually .. exists or happens. "He thought he had failed, but that wasn't the case." .. .. Case. Noun (1). Case is used to direct attention to a real or assumed occurence or situation that .. is to be considered, studied, or dealt with. "a case of mistaken identity" .. .. ddg.gg "!etymology case". .. https://www.etymonline.com/word/case .. case, from latin casus "a chance, occasion, opportunity; accident, mishap". Literally "a falling" .. from cas-, past participle stem of cadere "to fall, sink, settle down, decline perish". from PIE .. root \*kad- "to fall". The notion is of "that which falls" as "that which happens" (compare .. befall). .. .. Maybe it's called the "base case" because it forms the *basis* of any inductive reasoning steps .. describe in the recursive case. .. "Every proof by induction consists of two parts, the basis and the induction step." ~ Theory of Computation, Sipser, page 23, ch 0: Introduction, 0.4 Types of proof, proof by induction. .. Base cases are instances of problems that can be solved without carrying out recursive calls. Base cases are where a functions output value can be obtained without requiring further recursive calls. In the ``factorial`` function declaration above, the base case is written as the equation ``factorial 0 = 1``. Technically, the input value ``0`` is the base case, and ``1`` is the value that corresponds to it. In the case of ``0``, we return ``1``. If you think of recursive calls as inductive reasoning steps, the base case forms *the basis* for those inferences. Recursive cases are where self-referencing function calls occur. A recursive call applies the function definition to different input values. Each call of the function splits the input into smaller values of that function. Theses values are then combined to arrive at the final answer. In our function declaration, the recursive case is the equation ``factorial n = n * factorial (n - 1)``. In order for function evaluation to eventually stop, each recursive call of this case should move progressively closer towards one of the base cases. If not, the function will call itself forever, a phenomena know as infinite recursion. Here is an example of how ``factorial 4`` evaluates:: factorial 0 = 1 factorial n = n * factorial (n - 1) -- The ==> symbol indicates a new step in the reduction process. Each call of -- ⧼factorial arg⧽ is replaced with its definition, where ⧼n⧽ has been replaced -- by the current argument. factorial 4 ==> 4 * factorial (4 - 1) ==> 4 * (4 - 1) * factorial ((4 - 1) - 1) ==> 4 * (4 - 1) * ((4 - 1) - 1) * factorial (((4 - 1) - 1) - 1) ==> 4 * (4 - 1) * ((4 - 1) - 1) * (((4 - 1) - 1) - 1) * factorial ((((4 - 1) - 1) - 1) - 1) -- ^^^^^^^^^^^^^^^^^^^^^^^^^ -- The base case is triggered here. -- v ==> 4 * (4 - 1) * ((4 - 1) - 1) * (((4 - 1) - 1) - 1) * 1 ==> 4 * 3 * 2 * 1 * 1 ==> 4 * 3 * 2 * 1 ==> 4 * 3 * 2 ==> 4 * 6 ==> 24 If we didn't supply the base case ``0 -> 1``, then the recursive call would never stop, subtracting forever. 8.2.1 Another way to look at recursion ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. 2b **"The number of times the function may be applied depends on the arguments to the function, .. and the applications can be infinite if a stopping point is not clearly defined"**. Here's another example, to show how building up the call stack resembles composing multiple instances of the same function (this is a combination of figures 8 and 9):: applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b applyTimes 0 f b = b applyTimes n f b = f (applyTimes (n-1) f b) incTimes' :: (Eq a, Num a) => a -> a -> a incTimes' times n = applyTimes times (+1) n applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b applyTimes 0 f b = b applyTimes n f b = f . applyTimes (n-1) f $ b -- ^ -- Building up the series of self-referential function compositions... .. include:: exercises/8.2.2_-_intermission_exercise.rst 8.3 Bottom ---------- ``⊥``, or bottom, denotes computations that don't successfully result in a value. The two main varieties of bottom are computations that failed with an error or those that failed to terminate (for example, and infinite loop). In logic ``⊥`` corresponds to ``False``. Non-termination ^^^^^^^^^^^^^^^ Here's a non-terminating expression. When we run it, GHCi gets stuck in an infinite loop:: ·∾ let x = x in x ^CInterrupted. With a different version of GHC, the expression ``let x = x in x`` may have resulted in an exception, instead of heating up my laptop until I pressed Control-c. Partial functions ^^^^^^^^^^^^^^^^^ Another source of bottom are partial functions. For example:: ·∾ :{ ⋮ f :: Bool -> Int ⋮ f False = 0 ⋮ :} ·∾ ·∾ f False 0 ·∾ f True *** Exception: :3:1-11: Non-exhaustive patterns in function f In order to defend against this, we can define a catch-all case. Or, if it doesn't make sense to do so, we can explicitly return nothing, using the ``Maybe`` data type, like this:: ·∾ :{ ⋮ f :: Bool -> Maybe Int ⋮ f False = Just 0 ⋮ f _ = Nothing ⋮ :} ·∾ ·∾ f True Nothing ·∾ f False Just 0 Undefined values ^^^^^^^^^^^^^^^^ What happens if we try to evaluate ``undefined`` in the repl? :: ·∾ undefined *** Exception: Prelude.undefined CallStack (from HasCallStack): error, called at libraries/base/GHC/Err.hs:80:14 in base:GHC.Err undefined, called at :22:1 in interactive:Ghci9 Intentionally thrown errors ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Another source of bottom values are intentionally thrown errors. The function ``error`` prints a string and returns ``undefined``. :: ·∾ error "Should this be in a monad?" *** Exception: Should this be in a monad? CallStack (from HasCallStack): error, called at :27:1 in interactive:Ghci9 8.4 Fibonacci numbers --------------------- 8.4.1 Consider the types ^^^^^^^^^^^^^^^^^^^^^^^^ .. topic:: Why didn't the authors use ``Natural``? The Fibonacci function we're defining only works on positive numbers, so it makes more sense to use a type that can only represent positive integers, like ``Natural`` or ``Word``. Why didn't the authors do that? Probably to avoid discussing the mechanics of how negative numeric literals are resolved to values by GHC. When GHC evaluates an expression like ``(-10)``, it does not immediately turn it into a number with the value :math:`-10`, but instead desugurs it into ``(negate 10)``. At this point, ``negate`` tries to turn a value ``10`` to ``(-10)`` of type ``Natural``, but it can't, so it throws an exception if done at runtime. If we try compiling a file with this expression, instead, we'll get a warning and it won't compile. I was expecting a type error, instead. I'm still confused, and I'll have to read up on this. But hey, I learned something new! https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/exts/lexical_negation.html#extension-LexicalNegation .. topic:: Can I write a better type signature for Fibonacci? Here's an article about writing a proof for Fibonacci in a LiquidHaskell type annotation. https://ucsd-progsys.github.io/liquidhaskell-blog/2016/09/18/refinement-reflection.lhs/ 8.5 Integral division from scratch ---------------------------------- Here's an example for integral division. The inner ``go`` function keeps a count that the outer ``dividedBy`` function doesn't care about: .. include:: figures/DividedBy.hs :code: 8.6 Chapter Exercises --------------------- .. include:: exercises/8.6.1_-_review_of_types.rst .. include:: exercises/8.6.2_-_reviewing_currying.rst .. include:: exercises/8.6.3_-_recursion.rst .. include:: exercises/8.6.4_-_fixing_dividedby.rst .. include:: exercises/8.6.5_-_mccarthy_91_function.rst .. further reading: https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html .. Categories of recursive procedures .. ---------------------------------- .. linear: when a methods call itself only once each repetition. .. tail: like linear recursion, but the recursive call is the last operation carried out in the recursive case. .. multiple: when a method calls itself several times at once in some recursive case. .. mutual: When a set of methods call each other cyclically. Sometimes called indirect recursion. .. nested: when an argument of a recursive function is defined through another recursive call. Consider the McCarthy 91 function.