* Lecture 6A: Streams, Pt 1 https://youtu.be/JkGKLILLy0I aside: good to see harry again -- feel like it's been a while "last time, gerry really let the cat out of the bag" with /A S S I G N M E N T/ /S T A T E/ /C H A N G E/ (F X) might have a side effect (F X) again might have a different value /T I M E/ /I D E N T I T Y/ /S H A R I N G/ the environment model is a far cry from the substitution model. we can no longer think about it like mathematics. it's philosophically harder. why did we get into this mess? to build modular systems. we'd like to build in the computer systems that fall into pieces in a way that mirrors reality. the reason building systems like this is hard has nothing to do with computers. the real reason we have these problems is with the way we view reality. time is an illusion and nothing ever changes. lets try some *stream processing* a couple example procedures: #+BEGIN_SRC scheme (define (sum-odd-squares tree) (if (leaf-node? tree) (if (odd? tree) (square tree) 0) (+ (sum-odd-squares (left-branch tree)) (sum-odd-squares (right-branch tree))))) #+END_SRC #+BEGIN_SRC scheme (define (odd-fibs n) (define (next k) (if (> k n) '() (let ((f (fib k))) (if (odd? f) (cons f (next (1+ k))) (next (1+ k)))))) (next 1)) #+END_SRC these two procedures look very different, but really they are rather doing the same thing. enumerate the leaves --> filter odd? --> map square --> accumulate of a tree add from 0 enumerate interval --> map fibbonacci --> filter odd? --> accumulate 1 to n cons from '() these natural steps don't appear in the two procedures because we don't have a good language for talking about them. let's build an appropriate language. /S T R E A M S/ a stream is a data abstraction, like anything else. : (cons-stream x y) : (head s) : (tail s) for any x y : (head (cons-stream x y)) ;; x : (tail (cons-stream x y)) ;; y "the empty stream", like the empty list #+BEGIN_SRC scheme (define (map-stream proc s) (if (empty-stream? s) the-empty-stream (cons-stream (proc (head s)) (map-stream proc (tail s))))) (define (filter pred s) (cond ((empty-stream? s) the-empty-stream) ((pred (head s)) (cons-stream (head s) (filter pred (tail s)))) (else (filter pred (tail s))))) ;; etc as notated in the book #+END_SRC with those stream primitives, #+BEGIN_SRC scheme (define (sum-odd-squares tree) (accumulate + 0 (map square (filter odd (enumerate-tree tree))))) (define (odd-fibs n) (accumulate cons '() (filter odd (map fib (enum-interval 1 n))))) #+END_SRC we are establishing a CONVENTIONAL INTERFACE that allows us to glue things together. --> break here are some more complicated examples {{1 2 3 ...} {18 28 38 ...} ...} with flatten, get contents of stream of streams #+BEGIN_SRC scheme (define (flatten st-of-st) (accumulate append-streams the-empty-stream st-of-st)) (define (flatmap func stream) (flatten (map func stream))) #+END_SRC GIVEN N, find all pairs of 0 < j < i ≤ n such that i + j is prime eg for N=6: | i | j | i+j | |---+---+-----| | 2 | 1 | 3 | | 3 | 2 | 5 | | 4 | 1 | 5 | #+BEGIN_SRC scheme (flatmap (lambda (i) (map (lambda (j) (list i j)) (enum-interval 1 (-1+ i)))) (enum-interval 1 n)) (filter (lambda (p) (prime? (+ (car p) (cadr p)))) (flatmap ...)) (define (prime-sum-pairs n) (map (lambda (p) (list (car p) (cadr p) (+ (car p) (cadr p)))) (filter ...))) #+END_SRC there is a lot of power in flatmaps of flatmaps of maps etc ... this is how you can do nested-loop type shit writing flatmaps of flatmaps ... is not fun, so we have the syntactic sugar 'collect' #+BEGIN_SRC scheme (define (prime-sum-pairs n) (collect (list i j (+ i j)) ((i (enum-interval 1 n)) (j (enum-interval 1 (-1+ i)))) (prime? (+ i j))) #+END_SRC we can solve the 8 queens problem : (safe? row col rest-of-positions) ;; is it safe to put a queen here? the traditional way to solve this is with a method called backtracking walk up and down the tree, but this is "too concerned with time" #+BEGIN_SRC scheme (define (queens size) (define (fill-cols k) (if (= k 0) (singleton empty-board) (collect (adjoin-position try-row k rest-queens) ((rest-queens (fill-cols (-1+ k))) (try-row (enum-interval 1 size))) (safe? try-row k rest-queens) ))) (fill-cols size)) #+END_SRC every time harry says "fill-cols" i hear "phil collins" lol --> break the weakness of "traditional" lookin programs (sans the streams) is that they are "mixed up" -- the filtering and enumerating and collecting is all intertwined. this exact feature is also it's strength in efficiency. we can have our cake and eat it too, by using these streams and giving them the efficiency of traditional techniques. this power comes from the fact that streams are not lists. there's a lot of tuggin goin on lol we need to implement on "on demand" data structure (like a python generator) : cons-stream : head : tail : (cons-stream x y) is an abbreviation for : (cons x (delay y)) , a promise : (head s) --> (car s) : (tail s) --> (force (cdr s)) force calls in the promise : (delay exp) is an abbreviation for : (λ () exp) : (force p) is just : (p) delay decouples the apparent order of events from the real order in the machine : (define (delay ex) : (memo-proc : (λ () ex))) memo-proc saves previous computations this great power comes from the lack of distinction between data and procedures. we've written data structures that are rather like procedures. that's what streams are.