Clojure Template Tail Recursion Hello Factorial n n

  • Slides: 16
Download presentation
Clojure Template Tail Recursion

Clojure Template Tail Recursion

Hello, Factorial! n n The factorial function is everybody’s introduction to recursion (defn factorial-1

Hello, Factorial! n n The factorial function is everybody’s introduction to recursion (defn factorial-1 [n] (if (zero? n) 1 (* n (factorial-1 (dec n))))) (factorial-1 10) ; => 3628800 The problem with this function is that every recurrence increases the stack size n Not a problem if the number of recurrences is small 2

Tail recursion, or tail call recursion n A function is tail recursive if, for

Tail recursion, or tail call recursion n A function is tail recursive if, for every recursive call in the function, the recursive call is the last thing done in the function n In this situation, the compiler can replace the recursion with a simple loop, which does not add frames to the stack The programmer still does not have (or need!) loops In factorial-1, (* n (factorial-1 (dec n))) the multiplication keeps it from being tail recursive n To make the factorial function tail recursive, we need to somehow bring the multiplication into the parameter list 3

Adding an accumulator n n (defn factorial-two-args [acc n] (if (zero? n) acc (recur

Adding an accumulator n n (defn factorial-two-args [acc n] (if (zero? n) acc (recur (* acc n) (dec n)))) One way to think of this is as “pumping” information from the input parameter to the accumulator 4

Using factorial-two-args n n n (factorial-helper 1 10) ; => 3628800 (factorial-helper 0 10)

Using factorial-two-args n n n (factorial-helper 1 10) ; => 3628800 (factorial-helper 0 10) ; => 0 (factorial-helper 10 1) ; => 10 5

Use of a faҫade n n We can use a façade along with the

Use of a faҫade n n We can use a façade along with the “real” function (now called “factorial-helper” (defn factorial-2 [n] (factorial-helper 1 n)) (defn factorial-helper [acc n] (if (zero? n) acc (recur (* acc n) (dec n)))) n This adds an unnecessary function to those available to the user 6

When tail recursion isn’t n n The function (defn factorial-? [acc n] (if (zero?

When tail recursion isn’t n n The function (defn factorial-? [acc n] (if (zero? n) acc (factorial-? (* acc n) (dec n)))) is tail recursive, but that doesn’t do you any good unless you tell the compiler to replace the recursion with a loop Use recur instead of factorial-? in the tail call 7

Polymorphic parameters n n Clojure functions can be defined with more than one parameter

Polymorphic parameters n n Clojure functions can be defined with more than one parameter list (defn factorial-3 ([n] (factorial-3 1 n)) ([acc n] (if (zero? n) acc (recur (* acc n) (dec n)) ) 8

Defining a local helper function n (defn factorial-4 [number] (let [factorial-helper (fn [acc n]

Defining a local helper function n (defn factorial-4 [number] (let [factorial-helper (fn [acc n] (if (zero? n) acc (recur (* acc n) (dec n))))] (factorial-helper 1 number))) 9

let and loop n n n In the previous example, we used (let [factorial-helper

let and loop n n n In the previous example, we used (let [factorial-helper (fn [acc n] …]…) to define a helper function with local scope The general form is (let [name 1 value 1, …, name. N value. N] code) and we use recur in the code to tell the compiler to turn this into a loop To simplify this, Clojure provides a loop construct: (loop [name 1 value 1, …, name. N value. N] code) n n n This is not a loop; it’s a request to the compiler to turn tail recursion into a loop! The name/value pairs are initial values of parameters The call to recur in the body supplies new values for the parameters 10

let and loop comparison n n (defn factorial-4 [number] (let [factorial-helper (fn [acc n]

let and loop comparison n n (defn factorial-4 [number] (let [factorial-helper (fn [acc n] (if (zero? n) acc (recur (* acc n) (dec n))))] (factorial-helper 1 number))) (defn factorial-5 [number] (loop [acc 1, n number] (if (zero? n) acc (recur (* acc n) (dec n))))) 11

shallow-reverse n n (defn shallow-reverse-1 ([lst] (shallow-reverse () lst)) ([acc lst] (if (empty? lst)

shallow-reverse n n (defn shallow-reverse-1 ([lst] (shallow-reverse () lst)) ([acc lst] (if (empty? lst) acc (recur (cons (first lst) acc) (rest lst)) ) (defn shallow-reverse-2 [lst] (loop [acc (), lst-2 lst] (if (empty? lst-2) acc (recur (cons (first lst-2) acc) (rest lst-2)) ) 12

find-first-index n n Problem: Find the index of the first thing in a sequence

find-first-index n n Problem: Find the index of the first thing in a sequence that satisfies a given predicate (defn find-first-index [pred a-seq] (loop [acc 0, b-seq a-seq] (cond (empty? b-seq) nil (pred (first b-seq)) acc : else (recur (inc acc) (rest b-seq)) ) 13

Find the average of a sequence n n (defn avg [a-seq] (loop [sum 0,

Find the average of a sequence n n (defn avg [a-seq] (loop [sum 0, n 0, b-seq a-seq] (if (empty? b-seq) (/ sum n) (recur (+ sum (first b-seq)) (inc n) (rest b-seq)) ) Notice that the loop takes 3 arguments 14

Summary: Tail recursion n Tail recursion saves stack space, but the code is somewhat

Summary: Tail recursion n Tail recursion saves stack space, but the code is somewhat harder to read When the recursion is guaranteed to be “not too deep, ” you can ignore tail recursion Tail recursion isn’t too hard, as long as you remember the “bucket” analogy …and don’t fortet to use recur! 15

The End 16

The End 16