2.2 Hierarchical Data and the Closure Property

!! "it is better to have 100 functions operate on one data structure
than 10 functions operate on 10 data structures" – alan perlis

like that bruce lee quote about fearing the foe who practiced one kick
10,000 times, not the one who practiced 10,000 kicks once

Box and pointer diagram of (cons 1 2)

|    +-----+-----+
+--->|     |     |    +---+
     |  *  |  *------>| 2 |
     +--|--+-----+    +---+
        |
        v
      +---+
      | 1 |
      +---+

pairs provide a universal building block from which we can build all
sorts of data structures

closure property
the ability for pairing pairs
hierarchical structures
structures made up of parts that are
made up of parts that are made up of parts…

note: closure in this sense is not the same as the closure used to
describe procedures with free variables. the authors do not like the
use of the term to mean this other thing.

the sequence 1, 2, 3, 4 repr as a chain of pairs

    +---+---+   +---+---+   +---+---+   +---+---+
--->| * | *---->| * | *---->| * | *---->| * | X |
    +-|-+---+   +-|-+---+   +-|-+---+   +-|-+---+
      v           v           v           v
    +---+       +---+       +---+       +---+
    | 1 |       | 2 |       | 3 |       | 4 |
    +---+       +---+       +---+       +---+

2.2.1 Representing Sequences

one useful structure is the sequence, ie a list

(cons 1
      (cons 2
            (cons 3
                  (cons 4 nil))))

scheme provides a primitive for this action

(list 1 2 3 4)
(define one-thru-four (list 1 2 3 4))
(car one-thru-four)
(cdr one-thru-four)
(define one-thru-four (list 1 2 3 4))
(cons 10 one-thru-four)
(cadr one-thru-four)

list processing (lisping!) is often achieved by "cdring down" lists

(define (list-ref items n)
  (if (= n 0)
      (car items)
      (list-ref (cdr items) (- n 1))))
(define squares (list 1 4 9 16 25))
(list-ref squares 4)

we often cdr down the entire list. scheme offeres the primitve null?
to help

(define (length items)
  (if (null? items)
      0
      (+ 1 (length (cdr items)))))
(define odds (list 1 3 5 7 9))
(length odds)

if iteration is more to taste,

(define (length items)
  (define (length-iter a count)
    (if (null? a)
        count
        (length-iter (cdr a) (+ 1 count))))
  (length-iter items 0))
(length (list 8 6 7 5 3 0 9))

another common technique is to "cons up" a list of answers while
"cdring down" a list

(define (myappend list1 list2)
  (if (null? list1)
      list2
      (cons (car list1) (myappend (cdr list1) list2))))
(myappend (list 1 2 3 4) (list 0 9 8 7))

– MAPPING OVER LISTS –

a very useful operation is applying a transformation to each element
in a list

(define (scale-list items factor)
  (if (null? items)
      (list)
      (cons (* (car items) factor)
            (scale-list (cdr items) factor))))
(scale-list (list 1 2 3 4 5) 100)

we can abstract this pattern

(define (mymap proc items)
  (if (null? items)
      (list)
      (cons (proc (car items))
            (mymap proc (cdr items)))))
(mymap abs (list -10 2.5 -11.6 17))

scheme provides a map primitive

we can use map to redefine scale-list

(define (scale-list items factor)
  (map (lambda (x) (* x factor))
       items))

the original definition of scale-list drew attention to the
item-by-item processing. map establishes a higher level of
abstraction.

!! map abstracts the impl of procedures that operate on lists from the
details of how elements are extracted and combined

we are going to look at how this use of sequences provides a framework
for organizing programs ~~ cool

2.2.2 Hierarchical Structures

representing sequences as lists generalizes to representing sequences
made of sequences

for example,

((1 2) 3 4) ;; made by
(cons (list 1 2) (list 3 4))

is a list of three items, the first of which is a list

((1 2) 3 4) --> |.|.|---------->|.|.|-->|.|\|
                 |               |       |
                 v               v       v
                |.|.|-->|.|\|    3       4
                 |       |
                 v       v
                 1       2

!! sequences of sequences are really trees

to count the leaves in a tree:

(define x (cons (list 1 2) (list 3 4)))
(length x)              ;; 3
(count-leaves x)        ;; 4
(list x x) ;; (((1 2) 3 4) ((1 2) 3 4))
(length (list x x))     ;; 2
(count-leaves (list x x)) ;; 8

(define (count-leaves x)
  (cond ((null? x) 0)
        ((not (pair? x)) 1)
        (else (+ (count-leaves (car x))
                 (count-leaves (cdr x))))))
(count-leaves x)

MAPPING OVER TREES

(define (scale-tree tree factor)
  (cond ((null? tree) (list))
        ((not (pair? tree)) (* tree factor))
        (else (cons (scale-tree (car tree) factor)
                    (scale-tree (cdr tree) factor)))))
(scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10)
(define (scale-tree tree factor)
  (map (lambda (sub-tree)
         (if (pair? sub-tree)
             (scale-tree sub-tree factor)
             (* sub-tree factor)))
       tree))
(scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 2)

2.2.3 Sequences as Conventional Interfaces

we have seen the power of data abstraction, we will now look at a new
powerful design principle for working with data structures:
conventional interfaces

consider this procedure, analagous to count-leaves

(define (sum-odd-squares tree)
  (cond ((null? tree) 0)
        ((not (pair? tree))
         (if (odd? tree) (square tree) 0))
        (else (+ (sum-odd-squares (car tree))
                 (sum-odd-squares (cdr tree))))))

on the surface, it's rather different than this one for finding even
fibs

(define (even-fibs n)
  (define (next k)
    (if (> k n)
        (list)
        (let ((f (fib k)))
          (if (even? f)
              (cons f (next (+ k 1)))
              (next (+ x 1))))))
  (next 0))

an abstract description shows a common nature

program 1:

  • enumerates the leaves of a tree
  • filters, selecting odds
  • squares selected ones
  • accumulates the results with +, starting with 0

program 2:

  • enumerates integers from 0 to n
  • computes fib for each int
  • filters, selecting evens
  • accumulates with cons, starting with empty list

these follow a common pattern ging through the common stages (like a
signal flow)

  • enumerate
  • filter
  • map
  • accumulate

these programs could be more elegantly expressed by organizing them to
follow a signal-flow idea

SEQUENCE OPERATIONS

concentrate on the "signals" that flow from one stage of the program
to the next. representing these signals as lists allows us to perform
list operations on them (LISt Processing ?!)

to filter, accumulate, and enumerate

(define (filter predicate sequence)
  (cond ((null? sequence) (list))
        ((predicate (car sequence))
         (cons (car sequence)
               (filter predicate (cdr sequence))))
        (else (filter predicate (cdr sequence)))))

(define (accumulate op initial sequence)
  (if (null? sequence)
      initial
      (op (car sequence)
          (accumulate op initial (cdr sequence)))))

;; to enumerate even-fibs
(define (enumerate-interval low high)
  (if (> low high)
      (list)
      (cons low (enumerate-interval (+ low 1) high))))

;; to enumerate tree leaves
(define (enumerate-tree tree)
  (cond ((null? tree) (list))
        ((not (pair? tree)) (list tree))
        (else (append (enumerate-tree (car tree))
                      (enumerate-tree (cdr tree))))))

(filter odd? (list 1 2 3 4 5))
(accumulate + 0 (list 1 2 3 4 5))
(enumerate-interval 2 7)
(enumerate-tree (list 1 (list 2 (list 3 4)) 5))

with that, we can express those funcs as signal flows

(define (sum-odd-squares tree)
  (accumulate +
              0
              (map square
                   (filter odd?
                           (enumerate-tree tree)))))
(define (even-fibs n)
  (accumulate cons
              (list)
              (filter even?
                      (map fib
                           (enumerate-interval 0 n)))))

this style encorages MODULARITY … cool

we can use those units for any other task, like a list of the squares
of the first so many fib nums

(define (fib n)
    (cond ((= n 0) 0)
          ((= n 1) 1)
          (else (+
                 (fib (- n 1))
                 (fib (- n 2))))))
(define (square x) (* x x))
(define (list-fib-squares n)
  (accumulate cons
              (list)
              (map square
                   (map fib
                        (enumerate-interval 0 n)))))
(list-fib-squares 10)

we could rearrange them to compute odd ints in a sequence

(define (product-of-squares-of-odd-elements sequence)
  (accumulate *
              1
              (map square
                   (filter odd? sequence))))
(product-of-squares-of-odd-elements (list 1 2 3 4 5))

here's another example to get the highest programmer salary

(define (salary-of-highest-paid-programmer records)
  (accumulate max
              0
              (map salary
                   (filter programmer? records))))

this is just a hint of the great power conferred by sequence ops

we will use em to enable infinite sequences in 3.5

NESTED MAPPINGS

the sequence paradigm can be used in the fashion of nested loops
for this problem of pairs of numbers that add to primes

(accumulate append
            (list)
            (map (lambda (i)
                   (map (lambda (j) (list i j))
                        (enumerate-interval 1 (- i 1))))
                 (enumerate-interval 1 n)))

;; this is very common, we can express it like so
(define (flatmap proc seq)
  (accumulate append (list) (map proc seq)))

(define (prime-sum? pair)
  (prime? (+ (car pair) (cadr pair))))
(define (make-pair-sum pair)
  (list (car pair) (cadr pair) (+ (car pair) (cadr pair))))

(define (prime-sum-pairs n)
  (map make-pair-sum
       (filter prime-sum?
               (flatmap
                (lambda (i)
                  (map (lambda (j) (list i j))
                       (enumerate-interval 1 (- i 1))))
                (enumerate-interval 1 n)))))

to generate unique permutations of a set, {1 2 3} {2 1 3} {3 1 2} etc

(define (permutations s)
  (if (null? s)
      (list (list))
      (flatmap (lambda (x)
                 (map (lambda (p) (cons x p))
                      (permutations (remove x s))))
               s)))
(define (remove item sequence)
  (filter (lambda (x) (not (= x item)))
          sequence))

2.2.4 Example: A Picture Language

we are going to explore data abstraction, closure, and higher-order
procedures with the picture language

the language combines procedures as data

the picture language has only one primitive: painter

we can combine imagues by constructing new painters from given
painters

eg flip-vert takes a painter and produces a painter that paints
upside-down

we can combine these pictures with all the power of plain ol' scheme

im not really interested in this section: i see what they are getting
at and the lecture was dope, but i feel like im sitting on my thumbs
with no way to actually use painters

i do like the connection to geb

LEVELS OF LANGUAGE FOR ROBUST DESIGN

stratified design
a complex system should be structured as a
sequence of levels that are described using a sequence of
languages

each part is constructed using what it considers to be primitive

the level above takes those constructs as its primitives

the level below uses hidden concepts that are its own idea of
primitive

!! means of abstraction appropriate to the current level of detail

Author: jordyn

Created: 2021-02-15 Mon 16:09