Tail Recursion and Accumulators Recursion Should now be - - PowerPoint PPT Presentation
Tail Recursion and Accumulators Recursion Should now be - - PowerPoint PPT Presentation
Tail Recursion and Accumulators Recursion Should now be comfortable with recursion: No harder than using a loop (Maybe?) OAen much easier than
Recursion ¡
Should ¡now ¡be ¡comfortable ¡with ¡recursion: ¡
- No ¡harder ¡than ¡using ¡a ¡loop ¡(Maybe?) ¡
¡
- OAen ¡much ¡easier ¡than ¡a ¡loop ¡ ¡
– When ¡processing ¡a ¡tree ¡(e.g., ¡evaluate ¡an ¡arithmeFc ¡ expression) ¡ – Avoids ¡mutaFon ¡even ¡for ¡local ¡variables ¡
- Now: ¡ ¡
– How ¡to ¡reason ¡about ¡efficiency ¡of ¡recursion ¡ – The ¡importance ¡of ¡tail ¡recursion ¡ – Using ¡an ¡accumulator ¡to ¡achieve ¡tail ¡recursion ¡ – [No ¡new ¡language ¡features ¡here] ¡
Call-‑stacks ¡
While ¡a ¡program ¡runs, ¡there ¡is ¡a ¡call ¡stack ¡of ¡funcFon ¡ calls ¡that ¡have ¡started ¡but ¡not ¡yet ¡returned ¡
– Calling ¡a ¡funcFon ¡f ¡pushes ¡an ¡instance ¡of ¡f on ¡the ¡stack ¡ – When ¡a ¡call ¡to ¡f to ¡finishes, ¡it ¡is ¡popped ¡from ¡the ¡stack ¡
These ¡stack ¡frames ¡store ¡informaFon ¡such ¡as ¡
- the ¡values ¡of ¡arguments ¡and ¡local ¡variables ¡
- informaFon ¡about ¡“what ¡is ¡leA ¡to ¡do” ¡in ¡the ¡funcFon ¡
(further ¡computaFons ¡to ¡do ¡with ¡results ¡from ¡other ¡ funcFon ¡calls) ¡ Due ¡to ¡recursion, ¡mulFple ¡stack-‑frames ¡may ¡be ¡calls ¡to ¡ the ¡same ¡funcFon ¡
Example ¡
(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))
(fact ct 3 3) = => ( (* 3 3 _) _) (fact ct 3 3) (fact ct 2 2) (fact 3) => (* 3 _) (fact ct 0 0) (fact ct 1 1) (fact 2) => (* 2 _) (fact 1) => (* 1 _) (fact 2) => (* 2 _) (fact 3) => (* 3 _) (fact ct 3 3) = => ( (* 3 3 _) _) (fact ct 3 3) = => ( (* 3 3 2 2) (fact ct 2 2) = => ( (* 2 2 1 1) (fact 3) => (* 3 _) (fact ct 0 0) = => 1 1 (fact ct 1 1) = => ( (* 1 1 1 1) (fact 2) => (* 2 _) (fact 1) => (* 1 _) (fact 2) => (* 2 _) (fact 3) => (* 3 _)
What's ¡being ¡computed ¡
(fact 3) => (* 3 (fact 2)) => (* 3 (* 2 (fact 1))) => (* 3 (* 2 (* 1 (fact 0)))) => (* 3 (* 2 (* 1 1))) => (* 3 (* 2 1)) => (* 3 2) => 6
Example ¡Revised ¡
(define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1))
SFll ¡recursive, ¡more ¡complicated, ¡but ¡the ¡result ¡of ¡ recursive ¡calls ¡is ¡the ¡result ¡for ¡the ¡caller ¡(no ¡ remaining ¡mulFplicaFon) ¡
Example ¡Revised ¡
(fact ct2 3 3) = => _ _ (fact ct2 3 3) (f2-h 3 3 1 1) (fact2 3) => _ (f2-h 1 1 6 6) (f2-h 2 2 3 3) (f2-h 3 1) => _ (f2-h 2 3) => _ (f2-h 3 1) => _ (fact2 3) => _ (fact2 3) => _ (fact2 3) => _ (f2-h 3 1) => _ (fact2 3) => _ (f2-h 1 1 6 6) = => _ _ (f2-h 2 3) => _ (f2-h 3 1) => _ (f2-h 2 3) => _ (f2-h 3 1) => _ (fact2 3) => _
(define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1))
(f2-h 0 0 6 6) (f2-h 3 1) => _ (f2-h 2 3) => _ (f2-h 1 1 6 6) = => _ _ (f2-h 0 0 6 6) = => 6 6 (f2-h 1 1 6 6) = => 6 6 (f2-h 2 3) => 6
What's ¡being ¡computed ¡
(fact2 3) => (fact2-helper 3 1) => (fact2-helper 2 3) => (fact2-helper 1 6) => (fact2-helper 0 6) => 6
An ¡opFmizaFon ¡
It ¡is ¡unnecessary ¡to ¡keep ¡around ¡a ¡stack-‑frame ¡just ¡so ¡it ¡can ¡ get ¡a ¡callee’s ¡result ¡and ¡return ¡it ¡without ¡any ¡further ¡ evaluaFon ¡ ¡ Racket ¡recognizes ¡these ¡tail ¡calls ¡in ¡the ¡compiler ¡and ¡treats ¡ them ¡differently: ¡
– Pop ¡the ¡caller ¡before ¡the ¡call, ¡allowing ¡callee ¡to ¡reuse ¡the ¡same ¡ stack ¡space ¡ – (Along ¡with ¡other ¡opFmizaFons,) ¡as ¡efficient ¡as ¡a ¡loop ¡
(Reasonable ¡to ¡assume ¡all ¡funcFonal-‑language ¡ implementaFons ¡do ¡tail-‑call ¡opFmizaFon) ¡
¡includes ¡Racket, ¡Scheme, ¡LISP, ¡ML, ¡Haskell, ¡OCaml… ¡
What ¡really ¡happens ¡
(fact 3) (f2-h 3 1) (f2-h 2 3) (f2-h 1 6) (f2-h 0 6)
(define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1))
Moral ¡
- Where ¡reasonably ¡elegant, ¡feasible, ¡and ¡important, ¡
rewriFng ¡funcFons ¡to ¡be ¡tail-‑recursive ¡can ¡be ¡much ¡more ¡ efficient ¡
– Tail-‑recursive: ¡recursive ¡calls ¡are ¡tail-‑calls ¡ ¡
- meaning ¡all ¡recursive ¡calls ¡must ¡be ¡the ¡last ¡thing ¡the ¡calling ¡funcFon ¡
does ¡
- no ¡addiFonal ¡computaFon ¡with ¡the ¡result ¡of ¡the ¡callee ¡
- There ¡is ¡also ¡a ¡methodology ¡to ¡guide ¡this ¡transformaFon: ¡
– Create ¡a ¡helper ¡funcFon ¡that ¡takes ¡an ¡accumulator ¡ – Old ¡base ¡case's ¡return ¡value ¡becomes ¡iniFal ¡accumulator ¡value ¡ – Final ¡accumulator ¡value ¡becomes ¡new ¡base ¡case ¡return ¡value ¡
(define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1)) (define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))
Final ¡accumulator ¡value ¡ becomes ¡new ¡base ¡case ¡ return ¡value. ¡ Old ¡base ¡case's ¡return ¡ value ¡becomes ¡iniFal ¡ accumulator ¡value. ¡
Another ¡example ¡
(define (sum1 lst) (if (null? lst) 0 (+ (car lst) (sum1 (cdr lst))))) (define (sum2 lst) (define (sum2-helper lst acc) (if (null? lst) acc (sum2-helper (cdr lst) (+ (car lst) acc)))) (sum2-helper lst 0))
And ¡another ¡
(define (rev1 lst) (if (null? lst) '() (append (rev1 (cdr lst)) (list (car lst))))) (define (rev2 lst) (define (rev2-helper lst acc) (if (null? lst) acc (rev2-helper (cdr lst) (cons (car lst) acc)))) (rev2-helper lst '()))
Actually ¡much ¡be_er ¡
- For ¡fact ¡and ¡sum, ¡tail-‑recursion ¡is ¡faster ¡but ¡both ¡ways ¡
linear ¡Fme ¡
- The ¡non-‑tail ¡recursive ¡rev ¡is ¡quadraFc ¡because ¡each ¡
recursive ¡call ¡uses ¡append, ¡which ¡must ¡traverse ¡the ¡first ¡list ¡
– And ¡1 ¡+ ¡2 ¡+ ¡… ¡+ ¡(length-‑1) ¡is ¡almost ¡length ¡* ¡length ¡/ ¡2 ¡ ¡ – Moral: ¡beware ¡append, ¡especially ¡if ¡1st ¡argument ¡is ¡result ¡of ¡a ¡ recursive ¡call ¡
- cons ¡is ¡constant-‑Fme ¡(and ¡fast), ¡so ¡the ¡accumulator ¡version ¡
rocks ¡
(define (rev1 lst) ; Bad version (non T-R) (if (null? lst) '() (append (rev1 (cdr lst)) (list (car lst)))))
Tail-‑recursion ¡== ¡while ¡loop ¡with ¡local ¡ variable ¡
(define (fact2 n) (define (fact2-helper n acc) (if (= n 0) acc (fact2-helper (- n 1) (* acc n)))) (fact2-helper n 1)) def fact2(n): acc = 1 while n != 0: acc = acc * n n = n – 1 return acc
Tail-‑recursion ¡== ¡while ¡loop ¡with ¡local ¡ variable ¡
(define (sum2 lst) (define (sum2-helper lst acc) (if (null? lst) acc (sum2-helper (cdr lst) (+ (car lst) acc)))) (sum2-helper lst 0)) def sum2(lst): acc = 0 while lst != []: acc = lst[0] + acc lst = lst[1:] return acc
Tail-‑recursion ¡== ¡while ¡loop ¡with ¡local ¡ variable ¡
(define (rev2 lst) (define (rev2-helper lst acc) (if (null? lst) acc (rev2-helper (cdr lst) (cons (car lst) acc)))) (rev2-helper lst '())) def rev2(lst): acc = [] while lst != []: acc = [lst[0]] + acc lst = lst[1:] return acc
Always ¡tail-‑recursive? ¡
There ¡are ¡certainly ¡cases ¡where ¡recursive ¡funcFons ¡ cannot ¡be ¡evaluated ¡in ¡a ¡constant ¡amount ¡of ¡space ¡ ¡ Example: ¡funcFons ¡that ¡process ¡trees ¡
– Lists ¡can ¡be ¡used ¡to ¡ ¡ represent ¡trees: ¡'((1 2) ((3 4) 5))
¡ In ¡these ¡cases, ¡the ¡natural ¡recursive ¡approach ¡is ¡the ¡ way ¡to ¡go ¡
– You ¡could ¡get ¡one ¡recursive ¡call ¡to ¡be ¡a ¡tail ¡call, ¡but ¡ rarely ¡worth ¡the ¡complicaFon ¡ ¡
¡ 1 ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡2 ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡5 ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡3 ¡ ¡ ¡ ¡ ¡4 ¡
Precise ¡definiFon ¡
If ¡the ¡result ¡of ¡(f x) is ¡the ¡“return ¡value” ¡for ¡the ¡enclosing ¡funcFon ¡body, ¡ then ¡(f x) ¡is ¡a ¡tail ¡call ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡i.e., ¡don't ¡have ¡to ¡do ¡any ¡more ¡processing ¡of ¡(f ¡x) ¡to ¡end ¡funcFon ¡ ¡ Can ¡define ¡this ¡noFon ¡more ¡precisely… ¡
- A ¡tail ¡call ¡is ¡a ¡funcFon ¡call ¡in ¡tail ¡posi6on ¡
- The ¡single ¡expression ¡(ignoring ¡nested ¡defines) ¡of ¡the ¡body ¡of ¡a ¡funcFon ¡
is ¡in ¡tail ¡posiFon. ¡
- If ¡(if test e1 e2) ¡is ¡in ¡tail ¡posiFon, ¡then ¡e1 ¡and ¡e2 ¡are ¡in ¡tail ¡
posiFon ¡(but ¡test ¡is ¡not). ¡ ¡(Similar ¡for ¡cond-‑expressions) ¡
- If ¡a ¡let-‑expression ¡is ¡in ¡tail ¡posiFon, ¡then ¡the ¡single ¡expression ¡of ¡the ¡
body ¡of ¡the ¡let ¡is ¡in ¡tail ¡posiFon ¡(but ¡no ¡variable ¡bindings ¡are) ¡
- Arguments ¡to ¡a ¡funcFon ¡call ¡are ¡not ¡in ¡tail ¡posiFon ¡
- … ¡
¡