Skip to main content

CSC 151.01, Class 23: Naming local procedures

Overview

  • Preliminaries
    • Notes and news
    • Upcoming work
    • Extra credit
    • Questions
  • Review of reading
  • Lab
  • Debrief

Preliminaries

News / Etc.

  • New partners!
  • Happy pi day!
  • We will have one mentor session this week at 7pm on Thursday. The intent is more of an opportunity to talk broadly about CS than about the quiz. (A panel of mentors.)
  • RIP Stephen Hawking.
  • I hope to distribute an answer key to HW6 tonight.
  • You are awesome.

Upcoming work

Extra credit (Academic/Artistic)

  • Visit the two exhibits at the Faulconer Gallery.

Extra credit (Peer)

Extra credit (Recurring peer)

  • Listen to KDIC Wednesdays at 6pm - Witty banter with other personalities and/or co-host. Also Indian, Arabic, and Farsi music.
    (Up to two units of extra credit.)
  • Peer editing with SS. Talk to SS about the details. Make your English Lit more literate.

Extra credit (Misc)

  • Host one or more prospective students.

Other good things

Questions

How do I decide whether to use direct recursion or helper recursion?

Use helper recursion if (a) your analysis of the problem suggests that an extra parameter will be useful; (b) you find it particularly natural to think about recursion in terms of building up a solution as you go; or (c) the “draw a table” approach seems productive to you.

Use direct recursion if the solution to a “simpler” version of the problem leads naturally to a solution to the whole problem. “Trust the magic recursion fairy.”

When I’m trying to build lists that appear in the same order as the original, I am more likely to use direct recursion.

Can we do an example?

Sure. We did tally-odds and iota on the board. Here’s our incorrect version of iota

(define iota
  (lambda (n)
    (iota-helper '() 0 n)))
(define iota-helper
  (lambda (so-far i n)
    (if (>= i n)
        so-far
        (iota-helper (cons so-far i)
                     (+ 1 i)
                     n))))

That one didn’t work so well.

(define iota
  (lambda (n)
    (iota-helper '() 0 n)))
(define iota-helper
  (lambda (so-far i n)
    (if (>= i n)
        so-far
        (iota-helper (cons so-far i)
                     (+ 1 i)
                     n))))

We realized that cons takes a value and a list, not a list and a value.

(define iota
  (lambda (n)
    (iota-helper '() 0 n)))
(define iota-helper
  (lambda (so-far i n)
    (if (>= i n)
        so-far
        (iota-helper (cons i so-far)
                     (+ 1 i)
                     n))))

When we tried it, we found that we got the values in reverse order.

> (iota 5)
'(4 3 2 1 0)

So we added a call to reverse

(define iota
  (lambda (n)
    (reverse (iota-helper '() 0 n))))

We tried using append instead.

(define iota
  (lambda (n)
    (iota-helper '() 0 n)))
(define iota-helper
  (lambda (so-far i n)
    (if (>= i n)
        so-far
        (iota-helper (append so-far (list i))
                     (+ 1 i)
                     n))))

This version worked fine. But Sam said “I don’t like the use of append in recursive procedures.”

Sam also noted that this may be a case in which direct recursion is better.

I hear that you don’t like append. Why?

It’s similar to the (= (length lst) 1) issue. append turns out to be an expensive operation. Let’s look at how we might write it.

(define join-two-lists (lambda (lst1 lst2) ...))

I’m building a list, so I likely want to try direct recursion. I’ll need to figure out which list to simplify.

We recurse over the first list. If the recursive case works, we can then use cons to add the first thing back in.

When are we done? When the first list is empty.

(join-two-lists null some-list) gives back some-list

; Just like append, but we defined it ourselves
(define join-two-lists
  (lambda (lst1 lst2)
    (if (null? lst1)
        lst2
        (cons (car lst1)
              (join-two-lists (cdr lst1) lst2)))))

Why does this work?

   (join-two-lists '(3 1 4) '(1 5 9))
=> (cons 3 (join-two-lists '(1 4) '(1 5 9)))
=> (cons 3 (cons 1 (join-two-lists '(4) '(1 5 9))))
=> (cons 3 (cons 1 (cons 4 (join-two-lists '() '(1 5 9)))))
=> (cons 3 (cons 1 (cons 4 '(1 5 9))))
=> (cons 3 (cons 1 '(4 1 5 9)))
=> (cons 3 '(1 4 1 5 9))
=> '(3 1 4 1 5 9)

Now let’s go back to iota and assume we wrote it with append.

(append '() (list 0)) - Fast. One call to cons to build (list 0)

(append '(0) (list 1)) - One call to cons to build (list 1) One recursive call in append gives another cons. Two calls to cons.

(append '(0 1) (list 2)) - Three calls to cons

(append '(0 1 2) (list 3)) - four calls to cons

To build '(0 1 2 3) took ten call to cons.

To build '(0 1 2 3 4) took fifteen calls to cons.

To build '(0 1 2 3 4 5) took twenty-one calls to cons.

To build (iota 100) will take approximately 5050 calls to cons. In contrast, our model with reverse will take approximately 200 calls. That’s a big difference.

So I should avoid “expensive” operations in the middle of recursive procedures?

In general, yes. Sometimes it’s unavoidable.

Lab

Can we use all and any? Certainly.

Writeup: Exercise 2.

What’s going on with named let?

(let NAME ([PARAM1 VAL1]
           [PARAM2 VAL2]
           ...)
  BODY)

Named let is a different way to define/run procedures. It defines a procedure with the given name, parameters, and body. It also calls the procedure with the given values.

Debrief

Next class, we’ll talk a bit more about named let.

Next class, we’ll look at the different ways people approached problem 2, particularly the different places they put the kernel.

(define ff0
  (letrec ([kernel ...])
    (lambda (lst)
      (cond
        ...))))

(define ff0
  (lambda (lst)
    (letrec ([kernel ...])
      (cond 
        ...))))

(define ff0
  (lambda (lst)
    (cond
      ...
      [else
       (letrec ([kernel ...])
         ...)])))

Which do you prefer? Why?

Which is easiest to change to named let?

Q from student: Can’t I just use an internal define? Nope.