Another Accumulator

Sometimes is needed, not just optional.

;; some-name-twice?: FamTree --> boolean
;;
(define (some-name-twice? a-ft   ... )
  (cond [(unknown? a-ft) ...]
        [(child?   a-ft) 
            ..(child-name a-ft)..
            ..(some-name-twice? (child-pa a-ft)).. 
            ..(some-name-twice? (child-ma a-ft))..]))
Why does the tempalte fail miserably? How to do this with an accumulator -- what information do we need from previous calls?
(define (sn2-a? a-ft   ... )
  (cond [(unknown? a-ft) ...]
        [(child?   a-ft) 
            ..(child-name a-ft)..
            ..(child-pa a-ft).. 
            ..(child-ma a-ft)..]))





Answer:
;; sn2? FamTree --> boolean
;; Does some name occur twice in the family tree?
;;
(define (sn2? a-ft)
  (local [;; sn2-accum : FamTree, list-of-name --> list-of-name or true.
          ;; Return true if some name occurs twice anywhere in a-ft, or in names-so-far;
          ;; else return the (updated) list of names seen so far.
          ;;
          (define (sn2-accum a-ft names-so-far)
            (cond [(unknown? a-ft) names-so-far]
                  [(child?   a-ft) 
                   (if (contains? (child-name a-ft) names-so-far)
                       true
                       (local [(define seen-w-mom 
                                 ; True, or a list of names-seen-so-far, including on mom's side.
                                 (sn2-accum (child-ma a-ft)
                                            (cons (child-name a-ft) names-so-far)))]
                         (if (boolean? seen-w-mom)
                             true
                             (sn2-accum (child-pa a-ft) seen-w-mom))))]))]

    ;; Initially, names-so-far is empty.
    (boolean? (sn2-accum a-ft empty))))




(define (sn2?-v2 a-ft)
  (local [;;sn2?-accum: FamTree, list-of-symbol, list-of-famTree --> boolean
          ;; Return wehther or not some name occurs twice in any of: a-ft, names-so-far,
          ;; or any of the famtrees in todo.
          ;;
          (define (sn2?-accum a-ft names-so-far todo)
            (cond [(and (unknown? a-ft) (empty? todo))
                   false]
                  [(and (unknown? a-ft) (cons? todo))
                   (sn2?-accum (first todo) names-so-far (rest todo))]
                  [(child?   a-ft) ;Regardless of todo being empty/cons:
                   (if (contains? (child-name a-ft) names-so-far)
                       true
                       (sn2?-accum (child-ma a-ft)
                                   (cons (child-name a-ft) names-so-far)
                                   (cons (child-pa a-ft) todo)))]))]

    ;; Just call the helper, with the right initial values:
    ;; No names seen so far, and no pending trees to process.
    (sn2?-accum a-ft empty empty)))
What is the accumulator/invariant? in every single call, something stays the same throughout every call. Think about it:
  1. max-helper: the largest of so-far, {nums} is always the same.
  2. !-a: the product of so-far and n! is always the same.

Finally, we have a name for the very nice recursion where you don't build up pending computations: "tail-recursive". Question: can all functions be re-written in a tail-recursive manner?

higher-order functions

See textbook: higher-order functions

Class Examples

;; filter>: num, list-of-nums --> list-of-nums
;; Return all elements of nums which are > k.
;;
(define (filter> k nums)
  (cond [(empty? nums) empty]
        [(cons?  nums) (if (> (first nums) k)
                           (cons (first nums) (filter> k (rest nums)))
                           (filter> k (rest nums)))]))
;; filter<: num, list-of-nums --> list-of-nums
;; Return all elements of nums which are < k.
;;
(define (filter< k nums)
  (cond [(empty? nums) empty]
        [(cons?  nums) (if (< (first nums) k)
                           (cons (first nums) (filter< k (rest nums)))
                           (filter< k (rest nums)))]))
;; filter=: num, list-of-nums --> list-of-nums
;; Return all elements of nums which are = k.
;;
(define (filter= k nums)
  (cond [(empty? nums) empty]
        [(cons?  nums) (if (= (first nums) k)
                           (cons (first nums) (filter= k (rest nums)))
                           (filter= k (rest nums)))]))
Making all three of these wasn't too bad with cut/paste, but what if i went and wanted to change them all (say) to accumulator style? (Or, like i just did when writing these notes -- realized i'd swapped the order of the arguments.) What is in common, between all three functions? Can we factor the common part out?

Abstraction: Suppose we had a function filter>5, another filter>7, and another filter>19. We'd get tired of writing these, and say "hey, all that changes is that individual number -- i'll replace it with the generic k, which i'll pass in as an argument!". Well, let's do the same abstraction: all that changes here is the comparison function; let's replace it with the generic "compare-two":

;; filter-vs: comparer, num, list-of-nums --> list-of-nums
;; Return all nums "num" for which (comparer num k) returns true.
;;
(define (filter-vs comparer k nums)
  (cond [(empty? nums) empty]
        [(cons?  nums) (if (comparer (first nums) k)
                           (cons (first nums) (filter-vs comparer k (rest nums)))
                           (filter-vs comparer k (rest nums)))]))
Now, we have the general case. If we wanted to have the specific functions as before, it's easy:
(define (filter> k nums)
  (filter-vs > k nums))

(define (filter< k nums)
  (filter-vs < k nums))

(define (filter= k nums)
  (filter-vs = k nums))
Note how this also works with (say) a list of symbols, and we could filter with symbol=?. Or if we had a list of files, we could filter with is-a-larger-file-than.

What is the contract for filter-vs?
(Math-aside: Compare ":" with "element-of", and "-->" as a way to make new sets from old ones: the set of functions from A to B.)

Naked functions. (making a function w/o a name). Recall we had two flavor of define: one for simple placeholders (like (define pi 22/7): just associated a name with the resulting value), and a define for functions (like (define (add2 n) (+ n 2)): create a function, and then associate the name add2 with that newly-crated function.) Actually, lambda is the way functions are made in scheme.

(define (add2 n)
   (+ n 2))
Is actually just shorthand -- "syntactic sugar" -- for
(define add2
  (lambda (n)
     (+ n 2)))
The interesting thing is, you can use functions w/o naming them. For instance, let's Remove all elements of the list divisible by 5. Clearly we want to use filter-vs, but
;; Remove all elements of the list divisible by 5:
;;
(filter-vs (lambda (x y) (zero? (remainder x y))) 5 (list 4 5 11 0 10 -5 6))

So filter-vs takes in a binary function comparer and some sort of reference k; can we roll k inside the comparer? Sure! Write filter-in.

Note: "collect" might be a better name than "filter", since we collect the good ones, not filter them out. Alas, filter is already the name of the scheme built-in. Perhaps we could just use our own names, filter-in and filter-out:

(define filter-in filter)

(define (filter-out bad? elts)
  (local [(define (good? elt) (not (bad? elt)))]
    (filter-in good? elts)))
What are the contracts?

Currying