I\'m having difficulty understanding what\'s going on with The Little Schemer\'s evens-only*&co
example on page 145.
Here\'s the code:
In equational pseudocode (a KRC-like notation, writing f x y
for the call (f x y)
, where it is unambiguous), this is
evens-only*&co l col
= col [] 1 0 , IF null? l
= evens-only*&co (cdr l)
( newl product sum =>
col (cons (car l) newl)
(opx (car l) product)
sum ) , IF atom? (car l) && even? (car l)
= evens-only*&co (cdr l)
( newl product sum =>
col newl product (op+ (car l) sum) ) , IF atom? (car l)
= evens-only*&co (car l)
( anewl aproduct asum =>
evens-only*&co (cdr l)
( dnewl dproduct dsum =>
col (cons anewl dnewl)
(opx aproduct dproduct)
(op+ asum dsum) ) ) , OTHERWISE
This is a CPS code which collects all evens from the input nested list (i.e. a tree) while preserving the tree structure, and also finds the product of all the evens; as for the non-evens, it sums them up:
if l
is an empty list, the three basic (identity) values are passed as arguments to col;
if (car l)
is an even number, the results of processing the (cdr l)
are newl
, product
and sum
, and then they are passed as arguments to col
while the first two are augmented by consing ⁄ multiplying with the (car l)
(the even number);
if (car l)
is an atom which is not an even number, the results of processing the (cdr l)
are newl
, product
and sum
, and then they are passed as arguments to col
with the third one augmented by summing with the (car l)
(the non-even number atom);
if (car l)
is a list, the results of processing the (car l)
are anewl
, aproduct
and asum
, and then the results of processing the (cdr l)
are dnewl
, dproduct
and dsum
, and then the three combined results are passed as arguments to col
.
[]
, 1
and 0
of the base case are the identity elements of the monoids of lists, numbers under multiplication, and numbers under addition, respectively. This just means special values that don't change the result, when combined into it.
As an illustration, for '((5) 2 3 4)
(which is close to the example in the question), it creates the calculation
evens-only*&co [[5], 2, 3, 4] col
=
col (cons [] ; original structure w only the evens kept in,
(cons 2 ; for the car and the cdr parts
(cons 4 [])))
(opx 1 ; multiply the products of evens in the car and
(opx 2 (opx 4 1))) ; in the cdr parts
(op+ (op+ 5 0) ; sum, for the non-evens
(op+ 3 0))
Similar to my other answer (to a sister question), here's another way to write this, with a patter-matching pseudocode (with guards):
evens-only*&co = g where
g [a, ...xs...] col
| pair? a = g a ( la pa sa =>
g xs ( ld pd sd =>
col [la, ...ld...] (* pa pd) (+ sa sd) ) )
| even? a = g xs ( l p s => col [ a, ...l... ] (* a p ) s )
| otherwise = g xs ( l p s => col l p (+ a s ) )
g [] col = col [] 1 0
The economy (and diversity) of this notation really makes it all much clearer, easier to just see instead of getting lost in the word salad of long names for functions and variables alike, with parens overloaded as syntactic separators for list data, clause groupings (like in cond
expressions), name bindings (in lambda
expressions) and function call designators all looking exactly alike. The same uniformity of S-expressions notation so conducive to the ease of manipulation by a machine (i.e. lisp's read
and macros) is what's detrimental to the human readability of it.