项目作者: HyunggyuJang

项目描述 :
SICP with Proof
高级语言: Scheme
项目地址: git://github.com/HyunggyuJang/SICP.git
创建时间: 2019-08-10T01:09:07Z
项目社区:https://github.com/HyunggyuJang/SICP

开源协议:

下载


Chapter 2: Building Abstractions with Data

Hierarchical Data and the Closure Property

Repersenting Sequences

  • Does it prints same as list to construct a sequence by conses?

    1. (cons 1 (cons 2 (cons 3 (cons 4 '()))))
    1. (list 1 2 3 4)

    →Yeah, looks same.

  • Exercise 2.17

    1. (define (last-pair items) ;assume items is non empty list
    2. (let ((next-p (cdr items)))
    3. (if (null? next-p)
    4. items
    5. (last-pair next-p))))

    Let’s test with it

    1. (last-pair (list 23 72 149 34))

    It works.

  • Exercise 2.18

    1. (define (reverse l)
    2. (define (iter l r)
    3. (if (null? l)
    4. r
    5. (iter (cdr l) (cons (car l) r))))
    6. (iter l nil)) ; we don't know what nil is but assume we have

    Or do not use nil explicitly analogous to last-pair.

    1. (define (reverse l) ;assume that l is not empty
    2. (let ((next-p (cdr l)))
    3. (if (null? next-p)
    4. l
    5. (append (reverse next-p) (list (car l))))))
    6. (define (append l1 l2)
    7. (if (null? l1)
    8. l2
    9. (cons (car l1) (append (cdr l1) l2))))
  • Exercise 2.19

    Let’s review the change-counting program. We know that the number of
    change-count can be reduced as follows:

    • Exchange the change using the first coin and
    • not using the first coin

    The above states the reductive process. To complete the whole process of it, we
    need to specify the base case:

    • when the amount is zero, then we have only one way to change.

    It is quite tricky at first why this is true; but think it this way: simulate
    the situation with some simple case,e.g. the amount is 6 cents or like that.
    Then we come to realize it really need to count as 1 when amount is zero in this
    recursive process.

    That’s not the only base case; by the reductive process we have only two cases left to consider:

    • when we have 0 coins to change, we count this as 0 and
    • when we have negative amount to change, there is no way to change the amount; 0.

    Again, we can got the guts by simulating the simple cases or more formally we
    should reason this argument using induction. I think it is reasonable to use the
    computer aided proof check like this situation because humankind inherited the
    error-prone property. Nonetheless let’s code.

    Then we can code this recursive process:

    1. (define us-coins (list 50 25 10 5 1)) ;examples of how the parameter, coin-values, constructed
    2. (define (cc amount coin-values)
    3. (cond ((= amount 0) 1)
    4. ((< amount 0) 0)
    5. ((no- wjmore? coin-values) 0) ;↑base cases
    6. (else (+ (cc (- amount
    7. (first-denomination coin-values)) ;first reduction branch
    8. coin-values)
    9. (cc amount ;second (last) reduction branch
    10. (except-first-denomination coin-values))))))
    11. (define (no-more? coin-values)
    12. (null? coin-values)) ;we could (define no-more? null?) but we don't like to mess up the debugger
    13. (define (first-denomination coin-values)
    14. (car coin-values)) ;we provided that the coin-values not empty
    15. (define (except-first-denomination coin-values)
    16. (cdr coin-values)) ;we provided that the coin-values not empty

    We can reason that the order of the list coin-values does not affect the
    answer produced by cc; it just only takes more times to evaluate it.

    We can assure that by experimenting with some examples:

    1. (define us-reverse (reverse us-coins))
    2. (cc 100 us-reverse)
    3. (cc 100 us-coins)
    4. (cc 112 us-reverse)
    5. (cc 112 us-coins)

    The us-reverse results to same as us-coins. Why should it be?: The reductive
    process and base case I’ve wrote above does not mention about the order of
    coins; it should behave as same whatever order is.

    Then how about the efficiency (complexity) of space and times? We can guess the
    reversed order should be more complex than the previous case; but it is not
    obvious.

    Let’s do some trick. While we have not studied the set! statement, but it is
    useful to estimate the complexity and get some intuition about the general case:

    1. (define (cc amount coin-values)
    2. (define (cc-counter amount coin-values)
    3. (cond ((= amount 0)
    4. (set! count (1+ count))
    5. 1)
    6. ((< amount 0)
    7. (set! count (1+ count))
    8. 0)
    9. ((no-more? coin-values)
    10. (set! count (1+ count))
    11. 0) ;↑base cases
    12. (else
    13. (set! count (1+ count))
    14. (+ (cc-counter (- amount
    15. (first-denomination coin-values)) ;first reduction branch
    16. coin-values)
    17. (cc-counter amount ;second (last) reduction branch
    18. (except-first-denomination coin-values))))))
    19. (define count 0) ;count the steps needed to evaluate cc
    20. (cons count (cc-counter amount coin-values))) ;return the pair of count and number of cc ways

    Then we can inspect by using that:

    1. (cc 100 us-reverse) ;=> (38901 . 292)
    2. (cc 100 us-coins) ;=> (15499 . 292)

    Yeah, as we expected, the reversed version needs more than double steps than original.
    How can we reason this? Let’s we do some simulation with simple argument using
    substitution model. After some experiment, I’ve realized it is hard to prove
    that the reversed one has more step complexity than previous in asymptotic notation.
    To prove that the reversed one has more step & space complextity, we should
    prove that using inequality sign not asymptotic notation; it will convolve more
    subtle argument. We should use proof assistant otherwise it become really tricky
    to prove.

  • Exercise 2.20

    I’ve started not to think about the structure. It’s the evidence that I’ve
    exhausted completely. Anyway here is the code:

    1. (define (same-parity first . rest)
    2. (let ((same? (if (even? first)
    3. even?
    4. odd?)))
    5. (cons first (filter same? rest)))) ;wishful tinking
    6. (define (filter include? l)
    7. (if (null? l)
    8. l
    9. (let ((hd (car l)) ;it would be clearer if I use the let* notation.
    10. (tl (cdr l))) ;but I've not learned yet.
    11. (let ((filtered
    12. (filter include? tl)))
    13. (if (include? hd)
    14. (cons hd filtered)
    15. filtered)))))

    Isn’t this obvious to explain? Anyway, I’ll skip that.

  • Mapping over lists

    1. (define (map proc items)
    2. (if null? items)
    3. nil
    4. (cons (proc (car items))
    5. (map proc (cdr items))))
  • Exercise 2.21

    1. (define (square-list items)
    2. (if (null? items)
    3. nil
    4. (cons (square (car items) (square-list (cdr items))))))
    5. (define (square-list items)
    6. (map square items))
  • Exercise 2.22

    1. Because the structure of the helper procedure iter is same as our first
      definition of reverse.
    2. List is, by definition, sequence of pairs. But the tried implementation
      returns something other than list: (cons answer (square (car things))) is
      not chained pairs.
  • Exercise 2.23

    First try

    1. (define (for-each proc items)
    2. (if (null? items) ;base case
    3. true ;done case (termination)
    4. .....

    ↑ We can not use if clause for evaluation of sequenced statement.
    So we should use cond clause instead.

    1. (define (for-each proc items)
    2. (cond ((null? items) true) ;termination (base) case return true, which can be arbitrary value.
    3. (else
    4. (proc (car items))
    5. (for-each proc (cdr items))))

    Let’s test it:

    1. (for-each (lambda (x) (newline) (display x))
    2. (list 57 321 88))

    Yeah works well.

Hierarchical Structures

Let’s implement the count-leaves. It should behave like follows:

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

The implementation of count-leaves is analogous to length:

  • count-leaves of the empty list is 0.

But in the reduction step, we should take a count the car part:

  • count-leaves of tree x is count-leaves of the car of x plus
    count-leaves of the cdr of x.

After repeatedly apply the reduction step, we reach the another base case:

  • count-leaves of a leaf is 1.

And we can test whether it is leaf by using the primitive predicate pair? we
test whether it is pair or not.

Then we can complete the procedure:

  1. (define (count-leaves x)
  2. (cond ((null? x) 0)
  3. ((not (pair? x)) 1)
  4. (else (+ (count-leaves (car x))
  5. (count-leaves (cdr x))))))
  • Exercise 2.24

    I’ve drawn assigned task in Digital Papper.

  • Exercise 2.25

    1. (cadaddr (list 1 3 (list 5 7) 9)) results to 7.
      The notation (cadaddr x) is abbreviation for (car (cdr (car (cdr (cdr x))))).
      Let we use this notation from this point for the space.
    2. (caar (list (list 7))).
    3. (cadadadadadadr x), where x is (1 (2 (3 (4 (5 (6 7)))))).
  • Exercise 2.26

    1. (append x y) would result to (1 2 3 4 5 6).
    2. (cons x y) would result to ((1 2 3) 4 5 6).
    3. (list x y) would result to ((1 2 3) (4 5 6)).

    We can verify those by evaluating:

    1. (define x (list 1 2 3))
    2. (define y (list 4 5 6))
    3. (append x y)
    4. (cons x y)
    5. (list x y)

    Yeah the results are same as above.

  • Exercise 2.27

    We should implement, so called, deep-reverse. It behaves as follows:

    1. (define x (list (list 1 2) (list 3 4))) ;((1 2) (3 4))
    2. (reverse x) ;((3 4) (1 2))
    3. (deep-reverse x) ;((4 3) (2 1))

    It is obvious that we should use, so called, tree recursion for it. Or wishful
    thinking so to say.

    Let’s start with reduction step:

    • We assume that subtrees are deep reversed. Then we can complete the whole
      procedure:

      1. (reverse (cons (deep-reverse (car x)) (deep-reverse (cdr x))))
    • Then the results are reduced to the subproblem whose argument is subtree of x.

    There are two base case with which the reduction step end up:

    • By cdring down the list, we optain nil at the very end.
    • Or, we could encounter the leaf, not the pair by caring the list.

    As consequence, we can complete the procedure analogous to count-leaves:

    1. (define (deep-reverse x)
    2. (cond ((null? x) x)
    3. ((not (pair? x)) x)
    4. (else (reverse (cons
    5. (deep-reverse (car x))
    6. (deep-reverse (cdr x)))))))
  • Exercise 2.28

    Implement fringe. Whose behavior are the followings:

    1. (define x (list (list 1 2) (list 3 4)))
    2. (fringe x) ;(1 2 3 4)
    3. (fringe (list x x)) ;(1 2 3 4 1 2 3 4)

    The strategy is similar with above:

    1. (define (fringe x)
    2. (cond ((null? x) x)
    3. ((not (pair? x)) (list x))
    4. (else (append (fringe (car x))
    5. (fringe (cdr x))))))

    Note that we return the singleton list in the case of leaf; it is necessary at
    the reduction step.

  • Exercise 2.29

    Here we model the binary mobile, which consists of two branches–a left branch
    and right branch. Each branch is a rod of a certain length, from which hangs
    either a weight or another binary mobile.

    We can construct it by using list:

    1. (define (make-mobile left right)
    2. (list left right))

    A branch is constructed from a length, which must be a number, together with a
    structure, which may be either a number–a simple weight– or another mobile:

    1. (define (make-branch length structure)
    2. (list length structure))
    • a.

      Then we can implement the selectors, namely left-branch and right-branch:

      1. (define (left-branch mobile)
      2. (car mobile))
      3. (define (right-branch mobile)
      4. (cadr mobile))

      And the branch’ structure:

      1. (define (branch-structure branch)
      2. (cadr branch))
    • b.

      The procedure that returns weight of mobile, total-weight, is similar with
      above tree recursion processes: 1

      • Reduction process:

        1. (+ (total-weight (branch-structure (left-branch mobile)))
        2. (total-weight (branch-structure (right-branch mobile))))

        which end up with

      • Base case:

        1. (if (not (mobile? x)) x) ;a simple weight case

        And the mobile? test whether it is mobile:

        1. (define (mobile? x) (pair? x))

      And then we complete the procedure:

      1. (define (total-weight mobile)
      2. (if (not (mobile? mobile))
      3. mobile
      4. (+ (total-weight (branch-structure (left-branch mobile)))
      5. (total-weight (branch-structure (right-branch mobile))))))
    • c. Design the predicate that tests whether a binary mobile is balanced.

      Here we also exploit the wishful thinking about the tree structure:

      • Reduction process:

        1. (let ((left (left-branch mobile))
        2. (right (right-branch mobile)))
        3. (let ((mobile-l (branch-structure left))
        4. (mobile-r (branch-structure right)))
        5. (if (and (balanced? mobile-l)
        6. (balanced? mobile-r))
        7. (= (* (branch-length left)
        8. (total-weight mobile-l))
        9. (+ (branch-length right)
        10. (total-weight mobile-r)))
        11. false)))

        Here we used the branch-length whose definition is

        1. (define (branch-length branch)
        2. (car branch))

        Yeah, it’s quite dirty; but straight forward. We can revise the if clause as
        follow:

        1. (and (balanced? mobile-l)
        2. (balanced? mobile-r)
        3. (= (* (branch-length left)
        4. (total-weight mobile-l))
        5. (+ (branch-length right)
        6. (total-weight mobile-r))))

        as the consequence and alternative of if clause are boolean. This end up with

      • Base case (a simple weight):

        1. (if (not (mobile? x)) true)

        which can be reduced to

        1. (not (mobile? x))

      The complete code:

      1. (define (blanced? mobile)
      2. (and (not (mobile? mobile))
      3. (let ((left (left-branch mobile))
      4. (right (right-branch mobile)))
      5. (let ((mobile-l (branch-structure left))
      6. (mobile-r (branch-structure right)))
      7. (and (balanced? mobile-l)
      8. (balanced? mobile-r)
      9. (= (* (branch-length left)
      10. (total-weight mobile-l))
      11. (+ (branch-length right)
      12. (total-weight mobile-r))))))))
    • d.

      Suppose we change the representation of mobiles so that the constructors are

      1. (define (make-mobile left right)
      2. (cons left right))
      3. (define (make-branch length structure)
      4. (cons length structure))

      Then we should change only the followings:

      1. (define (right-branch mobile)
      2. (cdr mobile))
      3. (define (branch-structure branch)
      4. (cdr branch))

      Boom! That’s it. The power of abstract barrier!

  • Mapping over trees

    We can define the scale-tree procedure analogous to scale-list using map:

    1. (define (scale-tree tree factor)
    2. (map (lambda (sub-tree)
    3. (if (pair? sub-tree)
    4. (scale-tree sub-tree factor)
    5. (* sub-tree factor)))
    6. tree))
  • Exercise 2.30

    Define a procedure square-tree analogous to the square-list procedure of exercise 2.21.
    That is, square-tree should behave as follows:

    1. (square-tree
    2. (list 1
    3. (list 2 (list 3 4) 5)
    4. (list 6 7))) ;(1 (4 (9 16) 25) (36 49))
    • Define square-tree without higher-order procedures

      We can do this by analogy to scale-tree:

      1. (define (square-tree tree)
      2. (cond ((null? tree) tree)
      3. ((not (pair? tree)) (square tree))
      4. (else (cons (square-tree (car tree))
      5. (square-tree (cdr tree))))))
    • Define square-tree using map

      1. (define (square-tree tree)
      2. (map (lambda (sub-tree)
      3. (if (pair? sub-tree)
      4. (square-tree sub-tree)
      5. (square sub-tree)))
      6. tree))
  • Exercise 2.31

    Define tree-map that behave as follows:

    1. (define (square-tree tree) (tree-map square tree))

    It is easy:

    1. (define (tree-map proc tree)
    2. (map (lambda (sub-tree)
    3. (if (pair? sub-tree)
    4. (tree-map proc sub-tree)
    5. (proc sub-tree)))
    6. tree))
  • Exercise 2.32

    We can get the whole subsets of s by reducing that:

    • The subsets that include the element a of s plus
    • The subsets that do not include the element a.

    Then we can complete the code:

    1. (define (subsets s)
    2. (if (null? s)
    3. (list s)
    4. (let ((rest (subsets (cdr s))))
    5. (append rest
    6. (map (lambda (subset)
    7. (cons (car s) subset))
    8. rest)))))

Sequences as Conventional Interfaces

  • Sequence Operations

    The main procedures that complete the signal processing interfaces are
    followings:

    • map
    • filter
    • accumulate

      1. (define (accumulate op initial sequence)
      2. (if (null? sequence)
      3. initial
      4. (op (car sequence)
      5. (accumulate op initial (cdr sequence)))))

    All that remains to implement signal-flow diagrams is to enumerate the sequence
    of elements to be processed. That is, to make signals which processed afterward.

    • make initial signal; produces list.
      To enumerate the interval of integers, we can code

      1. (define (enumerate-interval low high)
      2. (if (> low high)
      3. nil
      4. (cons low (enumerate-interval (+ low 1) high))))

      For tree, we can transform the tree as follows2:

      1. (define (enumerate-tree tree)
      2. (cond ((null? tree) nil)
      3. ((not (pair? tree)) (list tree))
      4. (else (append (enumerate-tree (car tree))
      5. (enumerate-tree (cdr tree))))))
  • Exercise 2.33

    By comparing the map procedure and accumulate we can define the map as follows:

    1. (define (map p sequence)
    2. (accumulate (lambda (x y) (cons (p x) y))
    3. nil
    4. sequence))

    And the others also can be implemented analogous to above:

    1. (define (append seq1 seq2)
    2. (accumulate cons seq2 seq1))
    3. (define (length sequence)
    4. (accumulate (lambda (x y) (1+ y)) 0 sequence))
  • Exercise 2.34

    Horner’s rule.

    1. (define (horner-eval x coefficient-sequence)
    2. (accumulate (lambda (this-coeff higher-terms) (+ this-coeff (* x higher-terms)))
    3. 0
    4. coefficient-sequence))
    5. ;; test
    6. (horner-eval 2 (list 1 3 0 5 0 1)) ;79

    Actually I’ve used this in the exercise in Isabelle.

  • Exercise 2.35

    We can redefine count-leaves from this using accumulate, which means that we
    can formulate count-leaves as signal processing interface.

    At first, let’s try out without lingering over it; just compare those structure
    and code it appropriately:

    1. (define (count-leaves t)
    2. (accumulate (lambda (x y)
    3. (if (not (pair? x))
    4. (1+ y)
    5. (+ (count-leaves x)
    6. y)))
    7. 0
    8. t))

    It works; but it is not the one the text wanted: Text want we process the t
    with map before feed it to accumulate:

    1. (define (count-leaves t)
    2. (accumulate <??> <??> (map <??> <??>)))

    And more, it is no more clear than we did previously; just cramming all of the
    messy things from count-leaves into the op of accumulate.

    We can do better than that. Let’s follow text’s intention; but how we get the
    count-leaves coded? As we trying to formulate this as signal processing
    interface, first we need to processing the input t as ‘signal’–list–so that
    we can use conventional interfaces for this.

    Then the remain is simple:

    1. (define (count-leaves t)
    2. (accumulate + 0 (map (lambda (x) 1)
    3. (enumerate-tree t))))

    Yeah, who even said that we shouldn’t use enumerate-tree here? I think the
    intention of this exercise was in that capturing recursive process in signal
    processing framework. It looks silly when we map all the list items into number
    1 like this; but it will compensate someday I think. Whatever, let’s move on.

  • Exercise 2.36

    The generalized version of accumulate, accumulate-n deal with sequence of
    sequences that all have same number of elements. The behavior is as follows:

    1. (accumulate-n + 0 (list (list 1 2 3) (list 4 5 6) (list 7 8 9) (list 10 11 12)))
    2. ;(22 26 30)

    Here we use wishful thinking as usual:

    • Reduction process:
      Let we assume that we were given the results of
      accumulate-n with sequence of sequences except the first element of each.
      Then we can complete the process by accumulate the first element of each and
      then consing it with the given:

      1. (cons (accumulate op init (map car seqs))
      2. (accumulate-n op init (map cdr seqs)))

      After repeatedly applying this process we get

    • Base case:
      we end up with sequence of nil:

      1. (if (null? (car seqs))
      2. (car seqs))

      This is the only base case because nil is not the sequence of sequences.

    Then the complete code can be synthesized as usual:

    1. (define (accumulate-n op init seqs)
    2. (if (null? (car seqs))
    3. (car seqs)
    4. (cons (accumulate op init (map car seqs))
    5. (accumulate-n op init (map cdr seqs)))))
  • Exercise 2.37

    From now on, I’ll pace up because I’ve already designed the strucutre of these
    exercises on my note.

    1. (define (dot-product v w)
    2. (accumulate + 0 (map * v w)))
    3. (define (matrix-*-vector m v)
    4. (map (lambda (m_i) (dot-product m_i u))
    5. m))
    6. (define (transpose mat)
    7. (accumulate-n cons '() mat))
    8. (define (matrix-*-matrix m n)
    9. (let ((cols (transpose n)))
    10. (map (lambda (m_i)
    11. (matrix-*-vector cols m_i))
    12. m)))
  • Exercise 2.38

    To get equal value from both fold-right and fold-left, op should satisfy
    the associative law of algebra and also commutative law with repect to init.

  • Exercise 2.39

    1. (define (snoc x y)
    2. (append y (list x)))
    3. (define (reverse sequence)
    4. (fold-right (lambda (x y)
    5. (snoc x y))
    6. '()
    7. sequence))
    8. (define (reverse sequence)
    9. (fold-left (lambda (x y)
    10. (cons y x))
    11. '()
    12. sequence))
  • Exercise 2.40

    1. (define (flatmap proc seq)
    2. (accumulate append '() (map proc seq)))
    3. (define (unique-pairs n)
    4. (flatmap
    5. (lambda (i)
    6. (map (lambda (j) (list i j))
    7. (enumerate-interval 1 (- i 1))))
    8. (enumerate-interval 1 n)))
  • Exercise 2.41

    1. (define (triple-sum-to-s n s)
    2. (filter (lambda (triple)
    3. (= s (fold-right + 0 triple)))
    4. (flatmap (lambda (k)
    5. (map (lambda (p)
    6. (snoc k p))
    7. (unique-pairs (- k 1))))
    8. (enumerate-interval 1 n))))
  • Exercise 2.42

    1. (define (queens board-size)
    2. (define (queen-cols k)
    3. (if (= k 0)
    4. (list empty-board)
    5. (filter
    6. (lambda (positions) (safe? k positions))
    7. (flatmap
    8. (lambda (rest-of-queens)
    9. (map (lambda (new-row)
    10. (adjoin-position new-row k rest-of-queens))
    11. (enumerate-interval i board-size)))
    12. (queen-cols (- k 1))))))
    13. (queen-cols board-size))
    14. (define empty-board '())
    15. (define (adjoin-position new-row k rest-of-queens)
    16. (cons new-row rest-of-queens))
    17. (define (safe? k positions)
    18. (define (equal-not-to? nr rest)
    19. (or (null? rest)
    20. (and (not (= nr (car rest)))
    21. (equal-not-to? nr (cdr rest)))))
    22. (define (pm-i-not-equal-to? nr i rest)
    23. (or (null? rest)
    24. (and (not (or (= (+ nr i) (car rest))
    25. (= (- nr i) (car rest))))
    26. (pm-i-not-equal-to? nr (1+ i) (cdr rest)))))
    27. (let ((new-row (car positions))
    28. (rest-queens (cdr positions)))
    29. (and (equal-not-to? new-row rest-queens) ;provided that positions not empty
    30. (pm-i-not-equal-to? new-row 1 rest-queens))))

    Note that here we didn’t use the parameter k none of helper procedures–
    adjoin-position and safe? – because we don’t need to; I’ve thought it is
    natural to think the first k-1 columns as counted from the rightmost.

    1. #<unspecified>
  • Exercise 2.43

    Because by exchange the order of flatmap and map in the procedure
    queen-cols now the procedure call (queen-cols (- k 1)) become evaluated
    duplicately every new-row of (enumerate-interval 1 board-size), i.e
    board-size times.

    We can reason the asymptotic time complexity of both cases:

    • The original one is θ(n3)
    • The troubled one is θ(nn)

    Here we used the n as board-size. As consequence, when board-size is 8 and
    let the time taken by original one T, then the Louis’s one takes approximately
    more than 85 × T.

Example: A Picture Language

  • The picture language

    • Primitives: painter
    • Means of combination: beside, below
      • Create new painter from existing one: filp-vert, flip-horiz
    • Means of abstraction: define in scheme language; As means of combination are
      all procedure in scheme, we can abstract them like any other procedure in scheme.

    The results of combination of painter are also painters; by this closure
    property with scheme’s picture language inherit closure property in complete
    sense.

    I’ve tried to configure the picture language framework in mit-scheme; but to no vail.
    I’ve searched the github of mit-scheme, which has the directory named “6001”
    that contains 6001.pkg; to use that package, I’ve read the relevant content of
    MIT scheme’s user manual, also to no use; I’ve complied it with sf method in
    edwin editor–this package depends on the edwin package, so I have no choice to
    use emacs in this case–, and I found myself it is useless I need to put more
    time in configuration than solving the exercises. The easiest alternative was to
    use racket’s sicp package, which I knew but have stuck with using pure
    mit-scheme as I felt it is more canonical. Well, whatever, I’ve given up with
    that; let’s go with racket.

  • Exercise 2.44

    We can define up-split analogous to right-split.

    1. (define (up-split painter n)
    2. (if (= n 0)
    3. painter
    4. (let ((smaller (up-split painter (- n 1))))
    5. (below painter (beside smaller smaller)))))
  • Exercise 2.45

    We can easily abstract this by higher order function:

    1. (define (split tran1 tran2)
    2. (lambda (painter n)
    3. (if (= n 0)
    4. painter
    5. (let ((smaller ((split tran1 tran2) painter (- n 1))))
    6. (tran1 painter (tran2 smaller smaller))))))
  • Exercise 2.46

    Note that I implement sub-vect using the scale-vect and add-vect; it is
    subtle matter but I thought this definition is more canonical in algebraical
    sense:

    1. (define (make-vect x y)
    2. (cons x y))
    3. (define (xcor-vect v)
    4. (car v))
    5. (define (ycor-vect v)
    6. (cdr v))
    7. (define (add-vect v1 v2)
    8. (make-vect (+ (xcor-vect v1)
    9. (xcor-vect v2))
    10. (+ (ycor-vect v1)
    11. (ycor-vect v2))))
    12. (define (scale-vect s v)
    13. (make-vect (* (xcor-vect v) s)
    14. (* (ycor-vect v) s)))
    15. (define (sub-vect v1 v2)
    16. (add-vect v1 (scale-vect -1 v2)))
  • Exercise 2.47

    For first representation, we can implement selectors accordingly:

    1. (define (origin-frame f)
    2. (car f))
    3. (define (edge1-frame f)
    4. (cadr f))
    5. (define (edge2-frame f)
    6. (caddr f))

    For the latter, similarly:

    1. (define (origin-frame f)
    2. (car f))
    3. (define (edge1-frame f)
    4. (cadr f))
    5. (define (edge2-frame f)
    6. (cddr f))
  • Exercise 2.48

    Didn’t we solved this in exercise 2.2? In there, we made segment using points
    not vector but the representation axiom is same in both; we can reuse that
    definiiton:

    1. ;; Constructor make-segment
    2. (define (make-segment start-pt end-pt)
    3. (cons start-pt end-pt))
    4. ;; Selector start-segment
    5. (define (start-segment segment)
    6. (car segment))
    7. ;; Selector end-segment
    8. (define (end-segment segment)
    9. (cdr segment))
  • Exercise 2.49

    • a.

      The painter that draws the outline of the designated frame.

      1. (define outliner
      2. (let ((o (make-vect 0 0))
      3. (br (make-vect 1 0))
      4. (tr (make-vect 1 1))
      5. (tl (make-vect 0 1)))
      6. (segments->painter (list (make-segment o br)
      7. (make-segment br tr)
      8. (make-segment tr tl)
      9. (make-segment tl o)))))
    • b.

      The painter that draws an “X” by connecting opposite corners of the frame.

      1. (define x-liner
      2. (let ((o (make-vect 0 0))
      3. (br (make-vect 1 0))
      4. (tr (make-vect 1 1))
      5. (tl (make-vect 0 1)))
      6. (segments->painter (list (make-segment o tr)
      7. (make-segment br tl)))))
    • c.

      The painter that draws a diamond shape by connecting the midpoints of the sides
      of the frame.

      1. (define dia-liner
      2. (let ((o (make-vect 0 0))
      3. (br (make-vect 1 0))
      4. (tr (make-vect 1 1))
      5. (tl (make-vect 0 1)))
      6. (let ((left (scale-vect 0.5 tl))
      7. (bottom (scale-vect 0.5 br)))
      8. (let ((right (add-vect br left))
      9. (top (add-vect tl bottom)))
      10. (segments->painter (list (make-segment left top)
      11. (make-segment top right)
      12. (make-segment right bottom)
      13. (make-segment bottom left)))))))
    • DONE d.

      The wave painter.

      1. (define wave
      2. (let ((lhl (make-vect 0 0.65)) ;left hand
      3. (lhh (make-vect 0 0.8))
      4. (rhh (make-vect 1 0.35))
      5. (rhl (make-vect 1 0.2))
      6. (lal (make-vect 0.24 0.45)) ;left arm joint
      7. (lah (make-vect 0.24 0.6))
      8. (lsl (make-vect 0.4 0.6)) ;left shoulder
      9. (lsh (make-vect 0.4 0.65))
      10. (ln (make-vect 0.45 0.65))
      11. (rn (make-vect 0.55 0.65))
      12. (rs (make-vect 0.6 0.65))
      13. (lfa (make-vect 0.43 0.8))
      14. (rfa (make-vect 0.57 0.8))
      15. (lh (make-vect 0.45 1))
      16. (rh (make-vect 0.55 1))
      17. (lv (make-vect 0.43 0.55))
      18. (rv (make-vect 0.57 0.55))
      19. (lfo (make-vect 0.3 0))
      20. (rfo (make-vect 0.7 0))
      21. (lfo1 (make-vect 0.4 0))
      22. (rfo1 (make-vect 0.6 0))
      23. (cl (make-vect 0.5 0.3)))
      24. (segments->painter (list (make-segment lhh lah)
      25. (make-segment lah lsh)
      26. (make-segment lsh ln)
      27. (make-segment ln lfa)
      28. (make-segment lfa lh) ;from left hand high to left head
      29. (make-segment lhl lal)
      30. (make-segment lal lsl)
      31. (make-segment lsl lv)
      32. (make-segment lv lfo) ;from left hand low to left foot
      33. (make-segment lfo1 cl)
      34. (make-segment cl rfo1) ;from left foot1 to right foot1
      35. (make-segment rfo rv)
      36. (make-segment rv rhl) ;from left foot to right hand low
      37. (make-segment rhh rs)
      38. (make-segment rs rn)
      39. (make-segment rn rfa)
      40. (make-segment rfa rh))) ;from left hand high left head
      41. ))
  • Transforming and combining painters

    As we noted in the introductive session of this section, the combinators created
    new painter element using the existing ones. Now we implement the combinators
    using more general procedure, transform-painter:

    1. (define (transform-painter painter origin corner1 corner2)
    2. (lambda (frame)
    3. (let ((m (frame-coord-map frame)))
    4. (let ((new-origin (m origin)))
    5. (painter
    6. (make-frame new-origin
    7. (sub-vect (m corner1) new-origin)
    8. (sub-vect (m corner2) new-origin)))))))

    Here we exploit the fact that painter is actually just procedure, which takes
    frame for its arguemnt, so to transform the painter all we need to do is
    transform the frame appropriately. Also note that we could have chosen edge1
    and edge2 for transformation instead corner1 and corner2; we just found
    from the use case it is more convenient.

    Then we can implement a lot of combinators:

    • filp-vert

      1. (define (flip-vert painter)
      2. (transform-painter painter
      3. (make-vect 0.0 0.0)
      4. (make-vect 1.0 1.0)
      5. (make-vect 0.0 0.0)))
    • We can also shrink the frame:

      1. (define (shrink-to-upper-right painter)
      2. (transform-painter painter
      3. (make-vect 0.5 0.5)
      4. (make-vect 1.0 0.5)
      5. (make-vect 0.5 1.0)))
    • Also we can rotate the frame:

      1. (define (rotate90 painter)
      2. (transform-painter painter
      3. (make-vect 1.0 0.0)
      4. (make-vect 1.0 1.0)
      5. (make-vect 0.0 0.0)))
    • We can combine two or more paitners

      1. (define (beside painter1 painter2)
      2. (let ((split-point (make-vect 0.5 0.0)))
      3. (let ((paint-left
      4. (transform-painter painter1
      5. (make-vect 0.0 0.0)
      6. split-point
      7. (make-point 0.0 1.0)))
      8. (paint-right
      9. (transform-painter painter2
      10. split-point
      11. (make-vect 1.0 0.0)
      12. (make-vect 0.5 1.0))))
      13. (lambda (frame)
      14. (paint-left frame)
      15. (paint-right frame)))))
  • Exercise 2.50

    We can define flip-horiz analogous to flip-vert:

    1. (define (flip-horiz painter)
    2. (transform-painter painter
    3. (make-vect 1.0 0.0) ;new origin
    4. (make-vect 0.0 0.0) ;new end of edge1
    5. (make-vect 0.0 1.0))) ;new end of edge2

    We can define rotating frames in either way, using rotate90 or directly:

    • Using rotate90:

      1. (define (rotate180 painter)
      2. (rotate90 (rotate90 painter)))
      3. (define (rotate270 painter)
      4. (rotate90 (rotate180 painter)))
    • define directly:

      1. (define (rotate180 painter)
      2. (transform-painter painter
      3. (make-vect 1.0 1.0)
      4. (make-vect 0.0 1.0)
      5. (make-vect 1.0 0.0)))
      6. (define (rotate270 painter)
      7. (transform-painter painter
      8. (make-vect 0.0 1.0)
      9. (make-vect 0.0 0.0)
      10. (make-vect 1.0 1.0)))
  • Exercise 2.51

    • Define below analogous to beside:

      1. (define (below painter1 painter2)
      2. (let ((split-point (make-vect 0.0 0.5)))
      3. (let ((paint-bottom
      4. (transform-painter painter1
      5. (make-vect 0.0 0.0)
      6. (make-point 1.0 0.0)
      7. split-point))
      8. (paint-top
      9. (transform-painter painter2
      10. split-point
      11. (make-vect 1.0 0.5)
      12. (make-vect 0.0 1.0))))
      13. (lambda (frame)
      14. (paint-bottom frame)
      15. (paint-top frame)))))
    • Using beside:

      1. (define (below painter1 painter2)
      2. (rotate270 (beside (rotate90 painter2)
      3. (rotate90 painter1))))
  • Exercise 2.52

    • a.

      Let’s add some smile to our wave:

      1. (define wave
      2. (let ((lhl (make-vect 0 0.65)) ;left hand
      3. (lhh (make-vect 0 0.8))
      4. (rhh (make-vect 1 0.35))
      5. (rhl (make-vect 1 0.2))
      6. (lal (make-vect 0.24 0.45)) ;left arm joint
      7. (lah (make-vect 0.24 0.6))
      8. (lsl (make-vect 0.4 0.6)) ;left shoulder
      9. (lsh (make-vect 0.4 0.65))
      10. (ln (make-vect 0.45 0.65))
      11. (rn (make-vect 0.55 0.65))
      12. (lm (make-vect 0.48 0.77)) ;smile~
      13. (rm (make-vect 0.52 0.77))
      14. (cm (make-vect 0.5 0.75))
      15. (rs (make-vect 0.6 0.65))
      16. (lfa (make-vect 0.43 0.8))
      17. (rfa (make-vect 0.57 0.8))
      18. (lh (make-vect 0.45 1))
      19. (rh (make-vect 0.55 1))
      20. (lv (make-vect 0.43 0.55))
      21. (rv (make-vect 0.57 0.55))
      22. (lfo (make-vect 0.3 0))
      23. (rfo (make-vect 0.7 0))
      24. (lfo1 (make-vect 0.4 0))
      25. (rfo1 (make-vect 0.6 0))
      26. (cl (make-vect 0.5 0.3)))
      27. (segments->painter (list (make-segment lhh lah)
      28. (make-segment lah lsh)
      29. (make-segment lsh ln)
      30. (make-segment ln lfa)
      31. (make-segment lfa lh) ;from left hand high to left head
      32. (make-segment lhl lal)
      33. (make-segment lal lsl)
      34. (make-segment lsl lv)
      35. (make-segment lv lfo) ;from left hand low to left foot
      36. (make-segment lfo1 cl)
      37. (make-segment cl rfo1) ;from left foot1 to right foot1
      38. (make-segment rfo rv)
      39. (make-segment rv rhl) ;from left foot to right hand low
      40. (make-segment rhh rs)
      41. (make-segment rs rn)
      42. (make-segment rn rfa)
      43. (make-segment rfa rh) ;from left hand high left head
      44. (make-segment lm cm)
      45. (make-segment cm rm))) ;smile~
      46. ))
    • b.

      Change corner-split using only one copy of the up-split and right-split:

      1. (define (corner-split painter n)
      2. (if (= n 0)
      3. painter
      4. (let ((up (up-split painter (- n 1)))
      5. (right (right-split painter (- n 1))))
      6. (let ((top-left up)
      7. (bottom-right right))
      8. (beside (below painter top-left)
      9. (below bottom-right (corner-split painter (- n 1))))))))
    • c.

      Modify square-limit so that the big Mr. Rogers look outward from each corner
      of the square:

      1. (define (squre-limit painter n)
      2. (let ((combine4 (square-of-four flip-vert
      3. rotate180
      4. identity
      5. flip-horiz)))))
  • Resulting Pictures

    1. #lang racket
    2. (require sicp-pict)
    3. (define/contract wave
    4. (listof segment?)
    5. (let ((lhl (make-vect 0 0.65)) ;left hand
    6. (lhh (make-vect 0 0.8))
    7. (rhh (make-vect 1 0.35))
    8. (rhl (make-vect 1 0.2))
    9. (lal (make-vect 0.24 0.45)) ;left arm joint
    10. (lah (make-vect 0.24 0.6))
    11. (lsl (make-vect 0.4 0.6)) ;left shoulder
    12. (lsh (make-vect 0.4 0.65))
    13. (ln (make-vect 0.45 0.65))
    14. (rn (make-vect 0.55 0.65))
    15. (rs (make-vect 0.6 0.65))
    16. (lm (make-vect 0.48 0.77)) ;smile~
    17. (rm (make-vect 0.52 0.77))
    18. (cm (make-vect 0.5 0.75))
    19. (lfa (make-vect 0.43 0.8))
    20. (rfa (make-vect 0.57 0.8))
    21. (lh (make-vect 0.45 1))
    22. (rh (make-vect 0.55 1))
    23. (lv (make-vect 0.43 0.55))
    24. (rv (make-vect 0.57 0.55))
    25. (lfo (make-vect 0.3 0))
    26. (rfo (make-vect 0.7 0))
    27. (lfo1 (make-vect 0.4 0))
    28. (rfo1 (make-vect 0.6 0))
    29. (cl (make-vect 0.5 0.3)))
    30. (list (make-segment lhh lah)
    31. (make-segment lah lsh)
    32. (make-segment lsh ln)
    33. (make-segment ln lfa)
    34. (make-segment lfa lh) ;from left hand high to left head
    35. (make-segment lhl lal)
    36. (make-segment lal lsl)
    37. (make-segment lsl lv)
    38. (make-segment lv lfo) ;from left hand low to left foot
    39. (make-segment lfo1 cl)
    40. (make-segment cl rfo1) ;from left foot1 to right foot1
    41. (make-segment rfo rv)
    42. (make-segment rv rhl) ;from left foot to right hand low
    43. (make-segment rhh rs)
    44. (make-segment rs rn)
    45. (make-segment rn rfa)
    46. (make-segment rfa rh) ;from left hand high left head
    47. (make-segment lm cm)
    48. (make-segment cm rm)) ;smile~
    49. ))
    50. (define wave-p (segments->painter wave))
    51. (paint wave-p)

Symbolic Data

Quotation

  • Exercise 2.53

    This is easy stuff to get familized with quote notation

    1. (list 'a 'b 'c) ;(a b c)
    2. (list (list 'george)) ;((george))
    3. (cdr '((x1 x2) (y1 y2))) ;((y1 y2))
    4. (pair? (car '(a short list))) ;#f
    5. (memq 'red '((red shoes) (blue socks))) ;#f
    6. (memq 'red '(red shoes blue socks)) ;(red shoes blue socks)
  • Exercise 2.54

    We can think equal? procedure as two independent unit:

    • check if two arguments are symbols–not list and if it is then delegate to eq?.
    • otherwise, it means both are list; delegate this to another helper procedure
      eqList?.

    Here we used our old strategy wishful thinking: We haven’t eqList?; we defered
    to implement it.

    1. (define (equal? s1 s2)
    2. (or (and (symbol? s1)
    3. (symbol? s2)
    4. (eq? s1 s2))
    5. (eqList? s1 s2)))

    Then we should implement eqList?. We can design it using the type constrains,
    of which Standard ML is good at:

    1. fun eqList [] [] = true
    2. | eqList _ [] = false
    3. | eqList [] _ = false
    4. | eqList (x::xs) (y::ys) = (eq x y) andalso (eqList xs ys);

    We can translate to scheme easily:

    1. (define (eqList? xs ys)
    2. (cond ((and (null? xs) (null? ys))
    3. true)
    4. ((and (not (null? xs)) (null? ys))
    5. false)
    6. ((and (null? xs) (not (null? ys))) ;base case
    7. false)
    8. (else (and (eq? (car xs) (car ys)) ;recursive case
    9. (eqList? (cdr xs) (cdr ys))))))
  • Exercise 2.55

    Because '... is just syntatic sugar for (quote ...). So it is same as
    (quote (quote ...)). That is, as inner parts of the outermost quote are
    treated symbolically, (car (quote ...)) is quote. There is no surprise here.

Example: Symbolic Differentiation

By allowing the symbolic notation in our language, we can cope with algebraic
manipulation symbolically. For simplicity, here we consider only simple
differentiation rules:

  • (\frac{dc}{dx} = 0) for (c) a constant or a variable different from (x)
  • (\frac{dx}{dx} = 1)
  • (\frac{\left( u + v \right)}{dx} = \frac{du}{dx} + \frac{dv}{dx})
  • (\frac{\left( uv \right)}{dx} = u \left( \frac{dv}{dx} \right) + v \left(
    \frac{du}{dx} \right))

Here we delegate implementing of representation of symbolic differentiation
system by using wishful thinking: We just need specify what we want.

First let’s try to code deriv that calculate the derivative of given
expression with respect to var along with differentiation rules:

  1. (define (deriv exp var)
  2. (cond ((number? exp) 0)
  3. ((variable? exp)
  4. (if (same-variable exp var) 1 0))
  5. ((sum? exp)
  6. (make-sum (deriv (addend exp) var)
  7. (deriv (augend exp) var)))
  8. ((product? exp)
  9. (make-sum
  10. (make-product (multiplicand exp)
  11. (deriv (multiplier exp) var))
  12. (make-product (deriv (multiplicand exp) var)
  13. (multiplier exp))))
  14. (else
  15. (error "unknown expression type -- DERIV" exp))))

Note that we just used all the procedure that we need without implementing
that. Also note that this deriv procedure coded directly the differential
rules from calculus.

Can we represent the expression that we used in deriv? Yes sure!

  1. (define (variable? x) (symbol? x))
  2. (define (same-variable? v1 v2)
  3. (and (symbol? v1) (symbol? v2) (eq? v1 v2)))
  4. (define (make-sum a1 a2)
  5. (list '+ a1 a2))
  6. (define (make-product m1 m2)
  7. (list '* m1 m2))
  8. (define (sum? s)
  9. (and (pair? s) (eq? (car s) '+)))
  10. (define (addend s) (cadr s))
  11. (define (augend s) (caddr s))
  12. (define (product? p)
  13. (and (pair? p) (eq? (car p) '*)))
  14. (define (multiplier p) (cadr p))
  15. (define (multiplicand P) (caddr p))

Here we embedded the symbolic expression analogous to lisp’s compound
expression–prefix notation.

Let’s test this:

  1. (deriv '(+ x 3) 'x) ;(+ 1 0)
  2. (deriv '(* x y) 'x) ;(+ (* x 0) (* 1 y))
  3. (deriv '(* (* x y) (+ x 3)) 'x) ;(+ (* (+ x 3) (+ (* y 1) (* 0 x))) (* (+ 1 0) (* x y)))

Is what we expected? Well, yes and no: The program produces answer that are
correct with respect with the differentiation rules; but it is so verbose. We
need some simplifying steps; we can accomplish this using the analogy to
rational number arithmetic system; we don’t need to alter the deriv procedure,
which uses the constructors and selectors–the lower lever of language.

Let’s specify what we exactly expect from the simplification:

  • For multiplication
    • (x \times 0 = 0)
    • (y \times 1 = y)
  • For summation
    • (x + 0 = 0)

Also we want to fold the constants:
If the two argument of symbolic operation are both number than we calculate
appropriately, i.e.

  • ('+ 5 4) should be simplified to 9
  • ('* 5 4) should be simplified to 20

Then we can implement this by amending the low level language.

  1. (define (make-sum a1 a2)
  2. (cond ((=number? a1 0) a2)
  3. ((=number? a2 0) a1)
  4. ((and (number? a1) (number? a2)) (+ a1 a2))
  5. (else (list '+ a1 a2))))
  6. (define (=number? s n)
  7. (and (number? s) (= s n)))
  8. (define (make-product m1 m2)
  9. (cond ((or (=number? m1 0) (=number? m2 0)) 0)
  10. ((=number? m1 1) m2)
  11. ((=number? m2 1) m1)
  12. ((and (number? m1) (number? m2))
  13. (* m1 m2))
  14. (else (list '* m1 m2))))
  • Exercise 2.56

    Add the exponentiation to our symbolic algebraic system.
    Our differentiation system become

    1. (define (deriv exp var)
    2. (cond ((number? exp) 0)
    3. ((variable? exp)
    4. (if (same-variable? exp var) 1 0))
    5. ((sum? exp)
    6. (make-sum (deriv (addend exp) var)
    7. (deriv (augend exp) var)))
    8. ((product? exp)
    9. (make-sum
    10. (make-product (multiplicand exp)
    11. (deriv (multiplier exp) var))
    12. (make-product (deriv (multiplicand exp) var)
    13. (multiplier exp))))
    14. ((exponentiation? exp)
    15. (make-product (make-product (exponent exp)
    16. (make-exponentiation (base exp) (- (exponent exp) 1)))
    17. (deriv (base exp) var)))
    18. (else
    19. (error "unknown expression type -- DERIV" exp))))

    with the representation

    1. (define (make-exponentiation base exponent)
    2. (cond ((=number? exponent 0) 1)
    3. ((=number? exponent 1) base)
    4. ((and (number? base) (number? exponent))
    5. (expt base exponent))
    6. (else (list '** base exponent))))
    7. (define (exponentiation? e)
    8. (and (pair? e) (eq? (car e) '**)))
    9. (define (base ex)
    10. (cadr ex))
    11. (define (exponent ex)
    12. (caddr ex))

    Let’s test it:

    • The normal differentiation:

      1. (deriv '(** x 5) 'x) ;(* 5 (** x 4))
    • Check if it simplify appropriately:

      1. (deriv '(** x 2) 'x) ;(* 2 x)
  • Exercise 2.57

    Extend our program to handle sums and product of arbitrary numbers (two or more)
    of terms; e.g.

    • our last example of test, (deriv '(* (* x y) (+ x 3)) 'x), can be expressed
      as (deriv '(* x y (+ x 3)) 'x).

    Just amending the lower level of language–constructors and selectors.
    Note that the differential rules– deriv– doesn’t assume that the (v) is just
    symbol; if we can recognize the (v) part appropriately, we are done.

    Here is the idea:
    [\frac{d\left[ u + \left( v + w + \cdots \right) \right]}{dx} = \frac{du}{dx} +
    \frac{d\left( v + w + \cdots \right)}{dx}]
    That is, if we can amend (augend exp) to return (+ v w ...) then we complete
    our mission.

    We can code it directly:

    1. (define (augend s)
    2. ;; provided that s has more than two number of terms
    3. (if (null? (cdddr s))
    4. (caddr s) ;it has exactly two terms addend augend.
    5. (cons '+ (cddr s)))) ;it has more than that

    Similarly,

    1. (define (multiplicand m)
    2. ;; provided that s has more than two number of terms
    3. (if (null? (cdddr m))
    4. (caddr m) ;it has exactly two terms.
    5. (cons '* (cddr m)))) ;it has more than that

    We don’t need to fix any of the constructors. We don’t like to fix any so that
    our construct afford to make expression with arbitrary terms; that only make
    things more complicate.

  • Exercise 2.58

    The mathematicians want to use infix form instead of prefix form like lisp
    expression. Suppose we have to program our differentiation procedure for such
    mathematicians; i.e. we have to modify our differentiation program so that it
    works with ordinary mathematical notation–infix form.

    • a. binary infix form

      Here we consider the infix operator accept only two terms, i.e. binary infix
      operator. By experimenting with some examples, we can conclude that in the
      correct syntax form of expression, the operator should position at cadr of it.

      Then we can accomplish it by coding that observation:

      1. (define (sum? iexp)
      2. (and (pair? iexp)
      3. (pair? (cdr iexp))
      4. (eq? (cadr iexp) '+)))

      Here we used pair? twice to assure it will not raise exception when we call
      (cadr iexp). It is necessary not to raise exception because the caller of
      sum? doesn’t know and doesn’t need to know the gross detail like whether
      iexp satisfy the constraint of representation of sum; they doesn’t need to
      know the representation of those.

      Likewise,

      1. (define (product? iexp)
      2. (and (pair? iexp)
      3. (pair? (cdr iexp))
      4. (eq? (cadr iexp) '*)))

      The selectors can be expressed as follows

      1. (define (addend is) (car is))
      2. (define (augend is) (caddr is))
      3. (define (multiplier im) (car im))
      4. (define (multiplicand im) (caddr im))

      Similarly, the constructors represented as

      1. (define (make-sum a1 a2)
      2. (cond ((=number? a1 0) a2)
      3. ((=number? a2 0) a1)
      4. ((and (number? a1) (number? a2)) (+ a1 a2)) ;↑ simplification
      5. (else (list a1 '+ a2))))
      6. (define (make-product m1 m2)
      7. (cond ((or (=number? m1 0) (=number? m2 0)) 0)
      8. ((=number? m1 1) m2)
      9. ((=number? m2 1) m1)
      10. ((and (number? m1) (number? m2)) ;↑ simplification
      11. (* m1 m2))
      12. (else (list m1 '* m2))))

      The test works well:

      1. (deriv '((x * 5) + (y + 4)) 'x) ;5
    • b. Standard algebraic notation

      Now we want to deal with more general case, standard algebraic notation, e.g.
      (x + 3 * (x + y + 2)), which drops unnecessary parentheses and assumes that
      multiplication is done before addition, i.e. multiplication has higher
      precedence than addition.

      To simplify the task, we develop new language, that is, the semantics of
      standard algebraic notation. We can be rephrased as add new layer under the
      representation; or to layer out between semantics and syntax.

      First let’s experiment with some examples:
      ((x + y) * 5 * z), (x * y + (z + y) * 5)

      We can reconstruct using tree representation. Unfortunately I myself don’t know
      yet how to print the tree figure here3; so I’ve drew these in my personal
      note. Well, anyway there is one way to represent the tree structure in text:
      Lisp! As said in text or lecture of SICP, we can represent the tree as list!
      If we do that now, we obtain following results:

      • (* (+ x y) 5 z) or (* (+ x y) (* 5 z))
      • (+ (* x y) (* (+ z y) 5))

      Note that in the first example we can represent in both, binary tree or tree
      with arbitrary branches. Also note that this situation exactly match with the
      previous exercise, ex 2.57.

      Consequently, all we need to do is to convert infix form to prefix form allowed
      to have arbitrary number of terms; the rest would be handled by ex 2.57 as said.

      Then how? Again by wishful thinking assume that all the subtrees are constructed
      appropriately. Let’s try out.

      If (car exp) is compound data, then delegate it to subtree constructor and
      also we know that if it is correct infix form the (cadr exp) is
      operator4. Now we can construct whole tree by delegating the (cddr exp)
      and then constructing ((cadr exp) sub1 sub2).

      We constructed the abstract design of our code. The thing is, is it correct?
      No, it isn’t! The second example shows why it is; we have not considered the
      precedence relation appropriately! To cope with it, we have to build the whole
      tree either when we encounter with the lowest precedence operator or when we
      get faced with last operator of expression at the top level.

      This time, it works! How can we be sure? Can we prove it? Well, at least it is
      worth to prove for practice or because it is not obvious; yet we are left with
      long way to go. Let’s consider that first. We’ve built the so called AST–
      Abstract Syntactic Tree. Are we good to go and code this idea? Unfortunately no;
      there is something we missed: The selectors that used by deriv should return
      infix form not the prefix form as AST.

      That is, this time, we have to linger over the opposite way than we have
      constructed so far: To convert from AST to infix form. Here we also use the
      wishful thinking in recursive case: The subtrees are constructed for us; the
      rest is to deal with at the very top level.

      For simplicity, let we ignore the unnecessary parentheses, i.e. we don’t have to
      omit the verbose parenthesis for conciseness when we convert to infix form. Then
      we can easily do the task: (cov-sub1 op cov-sub2)

      Yeah It was quite long way to accomplish this! We did the design things! All the
      left is to code it!

      If we code that directly without further design–naive approach, then we get

      1. (define (->AST iexp)
      2. (define (constructor left stack)
      3. (let ((op (cadr left)))
      4. (cond ((or (lowest? op) (last? op left))
      5. (list op
      6. (->AST (cons (car left) stack))
      7. (->AST (cddr left))))
      8. (else (constructor
      9. (cddr left)
      10. (cons op
      11. (cons (car left)
      12. stack)))))))
      13. (cond ((null? (cdr iexp)) ;in the top level, it appears as singlton expression
      14. (if (pair? (car iexp))
      15. (->AST (car iexp)) ;compound expression
      16. (car iexp))) ;singleton
      17. (else (constructor iexp '()))))
      18. (define (lowest? op)
      19. (and (symbol? op) (eq? op '+)))
      20. (define (last? op left)
      21. (and (symbol? op) (null? (cdddr left))))

      We can test it; it works:

      1. ;; test
      2. (->AST '((x + y) * 5 * z)) ;(* (* 5 (+ x y)) z)
      3. (->AST '(x * y + (z + y) * 5)) ;(+ (* y x) (* (+ z y) 5))

      The reverse process can be coded likely:

      1. (define (->infix ast)
      2. (cond ((not (pair? ast)) ast) ;base case
      3. (else (list (->infix (cadr ast)) ;recursive case
      4. (car ast)
      5. (->infix (caddr ast))))))

      It also works:

      1. (->infix (->AST '((x + y) * 5 * z))) ;((5 * (x + y)) * z)

      With those, we can complete the code:

      1. (define (sum? iexp)
      2. (and (pair? iexp)
      3. (pair? (cdr iexp))
      4. (let ((ast (->ast iexp)))
      5. (eq? (car ast) '+))))
      6. (define (product? iexp)
      7. (and (pair? iexp)
      8. (pair? (cdr iexp))
      9. (let ((ast (->ast iexp)))
      10. (eq? (car ast) '*))))
      11. (define (addend iexp)
      12. (let ((ast (->ast iexp)))
      13. (->infix (cadr ast))))
      14. (define (augend iexp)
      15. (let ((ast (->ast iexp)))
      16. (->infix (caddr ast))))
      17. (define (multiplier iexp)
      18. (let ((ast (->ast iexp)))
      19. (->infix (cadr ast))))
      20. (define (multiplicand iexp)
      21. (let ((ast (->ast iexp)))
      22. (->infix (caddr ast))))

      The constructor should work as a. without modification.

      Let’s test it:

      1. (deriv '(x + 3 * (x + y + 2)) 'x) ;4
      2. (deriv '((x + y) * 5 * z) 'x) ;(z * 5)
      3. (deriv '(x * y + (z + y) * 5) 'x) ;y

      It works well. Phew, we did it.

      Well, we did it, but we did not include the exponentiation expression. Our
      standard infix form should accommodate that expression? Although the statement
      of this exercise did not mention it, isn’t it cool if we can handle the
      exponentiation? If we can accomplish that thing, we can extend further more!

      First we need to redesign our ->AST procedure. It is possible to cope with
      exponentiation just modifying our existing procedure; but the result would be
      quite mess–hard to recognize and maintain, no logic behind, etc.

      • Try: Let we assume there are specialist who can only handle specific operation.

        For instance, expt->AST can convert only exponentiation to AST and so on.

        The flow of overall ->AST procedure can be described as

        1. infix form => inital setup + handle base case => expt->AST => prod->AST
        2. => sum->AST => AST(output)

        We can specify the <??>->AST‘s behavior:

        • should take infix form with partially built AST.
        • should return partially built AST whit rest infix form where the first
          operation is not <??>.

        So, for example, the output of expt->AST should satisfy that its first op is
        either + or * (or no operation left in infix form); by the same argument,
        when it comes to sum->AST the first op should + (or as mentioned no more
        operation).

        Let we code it:

        1. (define (->AST iexp)
        2. (cond ((not (pair? iexp)) ;symbol--singleton
        3. iexp)
        4. ((null? (cdr iexp)) ;eof mark
        5. (if (pair? (car iexp))
        6. (->AST (car iexp)) ;turns out compound exp
        7. (car iexp))) ;it was singleton
        8. (else (expt->AST (cdr iexp) (->AST (car iexp)))))) ;recursive process
        9. (define (expt->AST iexp AST)
        10. (cond ((null? iexp) AST) ;eof
        11. ((and (symbol? (car iexp))
        12. (eq? (car iexp) '**))
        13. (expt->AST (cddr iexp) (list '** AST (->AST (cadr iexp)))))
        14. (else (prod->AST iexp AST))))
        15. (define (prod->AST iexp AST)
        16. (cond ((null? iexp) AST)
        17. ((and (symbol? (car iexp))
        18. (eq? (car iexp) '*))
        19. (prod->AST (cddr iexp) (list '* AST (->AST (cadr iexp)))))
        20. (else (sum->AST iexp AST))))
        21. (define (sum->AST iexp AST)
        22. (cond ((null? iexp) AST)
        23. ((and (symbol? (car iexp))
        24. (eq? (car iexp) '+))
        25. (list '+ AST (->AST (cdr iexp))))))

        The results:

        1. (->AST '(x + 3 * (x + y + 2))) ;(+ x (* 3 (+ x (+ y 2))))
        2. (->AST '(x ** 4 * (x * 2 + y + 2))) ;(* (** x 4) (+ (* x 2) (+ y 2)))

        Works well!

        The rest are the predicate, selectors and constructors of exponentiation, which
        can be accomplished using analogy with others:

        1. (define (exponentiation? iexp)
        2. (and (pair? iexp)
        3. (pair? (cdr iexp))
        4. (let ((ast (->ast iexp)))
        5. (eq? (car ast) '**))))
        6. (define (base iexp)
        7. (let ((ast (->ast iexp)))
        8. (->infix (cadr ast))))
        9. (define (exponent iexp)
        10. (let ((ast (->ast iexp)))
        11. (->infix (caddr ast))))
        12. (define (make-exponentiation base exponent)
        13. (cond ((=number? exponent 0) 1)
        14. ((=number? exponent 1) base)
        15. ((and (number? base) (number? exponent))
        16. (expt base exponent))
        17. (else (list base '** exponent))))

        Let’s test our last piece:

        1. (deriv '(x ** 4 * (x * 2 + y + 2)) 'x)
        2. ;;((((x * 2) + (y + 2)) * (4 * (x ** 3))) + (2 * (x ** 4)))

        Done.

Example: Representing Sets

We can abstract out the representation of set by using data abstraction: As long
as we have union-set, intersection-set, element-of-set?, and adjoin-set
we can do anything as with normal set. Those procedures can be thought as
interface of object set or axioms of set:

  • (element-of-set? x (union-set S1 S2)) is equivalent to
    (or (element-of-set? x S1) (element-of-set? x S2)
  • (element-of-set? x (intersection-set S1 S2)) is equivalent to
    (and (element-of-set? x S1) (element-of-set? x S2)
  • For any element x and set S, (element-of-set? x (adjoin-set x S)) is true

All the representation that satisfy all the axioms above can be treat as set.

  • Sets as unordered lists

    Here we represent a set as list of its elements in which no element appears more
    than once. As we are going to represent sets as ordered lists, we name this
    representation as unordered list representation.

    As we mentioned adjoin-set should adjoin member if and only if member is not
    already element-of-set?:

    1. (define (adjoin-set x set)
    2. (if (element-of-set? x set)
    3. set
    4. (cons x set)))

    Then element-of-set? get straightforward:

    1. (define (element-of-set? x set)
    2. (cond ((null? set) false)
    3. ((equal? x (car set)) true)
    4. (else (element-of-set? x (cdr set)))))

    For the intersection-set we can rely on induction on either of each arguments:

    1. (define (intersection-set set1 set2)
    2. (cond ((or (null? set1) (null? set2)) '())
    3. ((element-of-set? (car set1) set2)
    4. (cons (car set1)
    5. (intersection-set (cdr set1) set2)))
    6. (else (intersection-set (cdr set1) set2))))
  • Exercise 2.59

    Finally, union-set can be implemented as append-like procedure:

    1. (define (union-set set1 set2)
    2. (cond ((null? set1) set2)
    3. (else (adjoin-set (car set1)
    4. (union-set (cdr set1) set2)))))

    This procedure has Θ(n2) step complexity as intersection-set.

  • Exercise 2.60

    What if we represent a set as a list allowed to be duplicate? We don’t need to
    check if the member already in the given set to adjoin:

    1. (define (adjoin-set x set)
    2. (cons x set))

    And others doesn’t have to change at all but the complexity change. As
    adjoin-set became Θ(1) of step complexity, union-set, which depends on
    adjoin-set, get to have Θ(n) of step complexity, where n is the size of set1.

    The complexity of the others– element-of-set?, intersection-set– doesn’t
    change at all but now the concept of size of set has changed; although it is
    same along with the specification, the size of it can depend on representation,
    e.g. {1,2,1,1,1,1} has two member in it–1,2– and also it is {1,2} but
    those differ in size–6, 2 respectively.

    If we use adjoin-set (and union-set) a lot and sarcely element-of-set (or
    intersection-set) then current version would be more efficient than previous
    one, vice versa.

  • Sets as ordered lists

    To simplify our discussion, we consider only the case where the set elements are
    numbers. This section is well documented in text book. So we just jot down
    necessary procedures here:

    1. (define (element-of-set? x set)
    2. (cond ((null? set) false)
    3. ((= x (car set)) true)
    4. ((< x (car set)) false)
    5. (else (element-of-set? x (cdr set)))))
    6. (define (intersection-set set1 set2)
    7. (if (or (null? set1) (null? set2))
    8. '()
    9. (let ((x1 (car set1))
    10. (x2 (car set2)))
    11. (cond ((= x1 x2) (cons x1 (intersection-set (cdr set1)
    12. (cdr set2))))
    13. ((< x1 x2) (intersection-set (cdr set1) set2))
    14. ((> x1 x2) (intersection-set set1 (cdr set2)))))))
  • Exercise 2.61

    We know that if the member, which we are about to adjoin in given set, is
    smaller then the (car set) then the member are not contained in given set;
    if the member equals to (car set) then we don’t have to do anything,
    else we should look up the cdr of set recursively. Then we end up with
    base case–nil of set; we just cons our member with nil:

    1. (define (adjoin-set x set)
    2. (if (null? set)
    3. (list x)
    4. (let ((hd (car set)))
    5. (cond ((< x hd) (cons set))
    6. ((= x hd) set)
    7. (else
    8. (adjoin-set x (cdr set)))))))
  • Exercise 2.62

    To give Θ(n) growth union-set, we exploit the idea behind the
    intersection-set. I’ve got quite bored with jotting down all the rules that
    governs the algorithm; as this implementation is straightforward, let me just
    show them:

    1. (define (union-set set1 set2)
    2. (cond ((null? set1) set2)
    3. ((null? set2) set1)
    4. (let ((x1 (car set1))
    5. (x2 (car set2)))
    6. (cond ((= x1 x2) (cons x1 (union-set (cdr set1) (cdr set2))))
    7. ((< x1 x2) (cons x1 (union-set (cdr set1) set2)))
    8. ((> x1 x2) (cons x2 (union-set set1 (cdr set2))))))))
  • Sets as binary trees

    Here we represent sets as binary trees–composed by entry, left branch, right
    branch. In turns, we have to represent the binary tree which satisfy following
    axioms:

    • (entry (make-tree entry left right)) equals to entry
    • (left-branch (make-tree entry left right)) equals to left
    • (right-branch (make-tree entry left right)) equals to right

    Here is one of representation that satisfy above axioms:

    1. (define (entry tree) (car tree))
    2. (define (left-branch tree) (cadr tree))
    3. (define (right-branch tree) (caddr tree))
    4. (define (make-tree entry left right)
    5. (list entry left right))

    Given with above, we can write element-of-set? procedure:

    1. (define (element-of-set? x set)
    2. (if (null? set)
    3. false
    4. (let ((hd (entry set)))
    5. (cond ((= x hd) true)
    6. ((< x hd) (element-of-set? x (left-branch set)))
    7. ((> x hd) (element-of-set? x (right-branch set)))))))

    For the adjoin-set we recursively branching down the tree whether entry is
    equals to given member or not; if there is no such entry, it end up with empty
    tree, at that point, we should construct new tree and put the all pieces
    together appropriately:

    1. (define (adjoin-set x set)
    2. (if (null? set)
    3. (make-tree x '() '())
    4. (let ((hd (entry set)))
    5. (cond ((= x hd) set)
    6. ((< x hd) (make-tree hd
    7. (adjoin-set x (left-branch set))
    8. (right-branch set)))
    9. ((> x hd) (make-tree hd
    10. (left-branch set)
    11. (adjoin-set x (right-branch set))))))))
  • Exercise 2.63

    1. (define (tree->list-1 tree)
    2. (if (null? tree)
    3. '()
    4. (append (tree->list-1 (left-branch tree))
    5. (cons (entry tree)
    6. (tree->list-1 (right-branch tree))))))
    7. (define (tree->list-2 tree)
    8. (define (copy-to-list tree result-list)
    9. (if (null? tree)
    10. result-list
    11. (copy-to-list (left-branch tree)
    12. (cons (entry tree)
    13. (copy-to-list (right-branch tree)
    14. result-list)))))
    15. (copy-to-list tree '()))
    • a.

      We prove both produce the same result– ordered list.
      For the former, we can prove the assertion by induction on the depth of tree.
      For the latter, we prove the assertion by using the invariance that
      copy-to-list returns ordered list of given tree with appending the
      result-list at the end.

      As consequence, the figure 2.16 end up with (1 2 3 4 5 6 7).

    • b.

      For the former, if we estimate the size of input as number of entries of tree,
      then we get the following differential equation:
      [f \left( n \right) - 2 f \left( n/2 \right) = \Theta \left( n/2 \right)],
      which end up with
      [f \left( n \right) = \Theta(n) + \Theta (n \log n)].
      As consequence, we got (\Theta(n \log n)) step complexity.

      For the latter, it becomes
      [ f(n) - 2 f(n/2) = \Theta (1) ];
      it can be concluded as
      [f(n) = \Theta(n) + \Theta(\log n) = \Theta(n)], i.e. (\Theta(n)) step complexity.

  • Exercise 2.64

    1. (define (list->tree elements)
    2. (car (partial-tree elements (length elements))))
    3. (define (partial-tree elts n)
    4. (if (= n 0)
    5. (cons '() elts)
    6. (let ((left-size (quotient (- n 1) 2)))
    7. (let ((left-result (partial-tree elts left-size)))
    8. (let ((left-tree (car left-result))
    9. (non-left-elts (cdr left-result))
    10. (right-size (- n (+1 left-size))))
    11. (let ((this-entry (car non-left-elts))
    12. (right-result (partial-tree (cdr non-left-elts)
    13. right-size)))
    14. (let ((right-tree (car right-result))
    15. (remaining-elts (cdr right-result)))
    16. (cons (make-tree this-entry
    17. left-tree
    18. right-tree)
    19. remaining-elts))))))))
    • a.

      The partial-tree‘s behavior is as follows

      • given with ordered list elts it returns pair that contains balanced tree
        with first n elements from the elts with ordered list but first n elements.

      This is just specification that we want to implement for list->tree. Then how
      to implement it? We have the powerful strategy, wishful thinking:
      Let we assume that for all inputs that has less than n partial-tree
      implemented by John. Our task is to complete the partial-tree for the inputs
      with n. Here is the idea:

      1. First, build the left half tree with the elts.
      2. And then, take one element from the remaining elts from step 1.
      3. Using the rest elements construct right half tree.
      4. Build whole tree using make-tree with above left tree, entry, right tree
        from 1, 2, 3, respectively.
      5. Returns pair consisting the tree built from step 4 with remaining elements
        from step 3.

      The straightforward implementation is shown above. Note that we used for the
      left-size since we have to satisfy the relation– (n - 1 \ge 2 ~\text{left-size}).

    • b.

      Analogous to previous exercise, we can deduce that the order of growth of this
      implementation is Θ(n).

  • Exercise 2.65

    • intersection-set

      After I’ve played with some examples, I concluded that the ordered list version
      of it is quite ideal implementation than using tree. Given with exercise 2.63,
      we can revert the (balanced) tree to ordered list.

      Then we simply put together all the modules:

      1. (define (intersection-set set1 set2)
      2. (let ((ol1 (tree->list-2 set1))
      3. (ol2 (tree->list-2 set2)))
      4. (list->tree (intersection-setl ol1 ol2))))

      Here I rephrase the previous version of intersection-set as
      intersection-setl for denoting ordered list.

      Similarly, we can do same thing to union-set:

      1. (define (union-set set1 set2)
      2. (let ((ol1 (tree->list-2 set1))
      3. (ol2 (tree->list-2 set2)))
      4. (list->tree (union-setl ol1 ol2))))

      These have all Θ(n) order of growth.

  • Exercise 2.66

    As we know that lookup is analogous to element-of-set?, here also we can
    implement using that observation:

    1. (define (lookup given-key set)
    2. (if (null? set)
    3. false
    4. (let ((hd (key (entry set))))
    5. (cond ((= given-key hd) true)
    6. ((< given-key hd) (lookup given-key (left-branch set)))
    7. ((> given-key hd) (lookup given-key (right-branch set)))))))

Example: Huffman Encoding trees

  • Representing Huffman trees

    Here we represent Huffman tree as weighted tree. The leaf can be represented as

    1. (define (make-leaf symbol weight)
    2. (list 'leaf symbol weight))
    3. (define (leaf? object)
    4. (eq? (car object) 'leaf))
    5. (define (symbol-leaf x) (cadr x))
    6. (define (weight-leaf x) (caddr x))

    A general tree will be a list of left branch, a right branch, a set of symbols,
    and as weight. The set of symbols will be simply a list of the symbols, which
    contains all the symbols of leaves under the tree. And weight will be number
    which indicate sum of leaves’ weights under the tree. As I noted before, the
    append operation is very similar to union. Here, as we represent the set of
    symbols as just list, we can get the behavior of union using append:

    1. (define (make-code-tree left right)
    2. (list left
    3. right
    4. (append (symbols left) (symbols right))
    5. (+ (weight left) (weight right))))

    The other interface procedures determined by this representation:

    1. (define (left-branch tree) (car tree))
    2. (define (right-branch tree) (cadr tree))
    3. (define (symbols tree)
    4. (if (leaf? tree)
    5. (list (symbol-leaf tree))
    6. (caddr tree)))
    7. (define (weight tree)
    8. (if (leaf? tree)
    9. (weight-leaf tree)
    10. (cadddr tree)))
  • The decoding procedure

    Given bits with Huffman tree, we can start decode with each bit from the
    leftmost bit to determine how to choose next branch in the tree. If we
    encountered with leaf, then it means we decoded one symbol in message.
    Repeatedly decoding the symbols, and accumulating the symbols in list, we
    complete the decoding process. It is natural to decompose this decoding
    procedure to decoding one symbol, procedure to choose branch with the current
    bit and branch of tree, accumulating the result symbols and determining
    termination condition. Here is one possible implementation:

    1. (define (decode bits tree)
    2. (define (decode-1 bits current-branch)
    3. (if (null? bits)
    4. '()
    5. (let ((next-branch
    6. (choose-branch (car bits) current-branch)))
    7. (if (leaf? next-branch)
    8. (cons (symbol-leaf next-branch)
    9. (decode-1 (cdr bits) tree))
    10. (decode-1 (cdr bits) next-branch)))))
    11. (decode-1 bits tree))
    12. (define (choose-branch bit branch)
    13. (cond ((= bit 0) (left-branch branch))
    14. ((= bit 1) (right-branch branch))
    15. (else (error "bat bit -- CHOOSE-BRANCH" bit))))

    Here we merged somehow decode one symbol with accumulating & terminating; we
    found that it is way more concise than bothering to decompose with the described
    fashion.

  • Sets of weighted elements

    The tree-generating algorithm requires to choose smallest nodes in the set. To
    do this, it is more convenient to represent a set as an ordered list as we did
    before. However, in this situation, provided that the element being adjoined to
    the set is not in the set. Using this fact we can adjoin-set more efficiently:

    1. (define (adjoin-set x set)
    2. (cond ((null? set) (list x))
    3. ((< (weight x) (weight (car set))) (cons x set))
    4. (else (cons (car set)
    5. (adjoin-set x (cdr set))))))

    Using this, we can construct the ordered list of pairs, which contains symbol
    with frequency:

    1. (define (make-leaf-set pairs)
    2. (if (null? pairs)
    3. '()
    4. (let ((pair (car pairs)))
    5. (adjoin-set (make-leaf (car pair) ;symbol
    6. (cadr pair)) ;frequency
    7. (make-leaf-set (cdr pairs))))))
  • Exercise 2.67

    1. (define sample-tree
    2. (make-code-tree (make-leaf 'A 4)
    3. (make-code-tree
    4. (make-leaf 'B 2)
    5. (make-code-tree (make-leaf 'D 1)
    6. (make-leaf 'C 1)))))
    7. (define sample-message '(0 1 1 0 0 1 0 1 0 1 1 1 0))
    8. ;; test
    9. (decode sample-message sample-tree) ;(a d a b b c a)
  • Exercise 2.68

    The encode process can be decomposed as

    1. (define (encode message tree)
    2. (if (null? message)
    3. '()
    4. (append (encode-symbol (car message) tree)
    5. (encode (cdr message) tree))))

    Then we should implement the encode-symbol procedure that encode one symbol as
    sequence of bits. This procedure also should raise exception when there is not
    such symbol in the tree. We can deduce the algorithm by playing with it:

    1. Test whether the symbol included in tree.
    2. If not raise error, else choose next branch that contains given symbol.
    3. If we get all the way down to the leaf (which also contains given symbol by
      contract) return the accumulated bits.

    When we code this algorithm, we get

    1. (define (element-of-set? x set)
    2. (cond ((null? set) false)
    3. ((equal? x (car set)) true)
    4. (else (element-of-set? x (cdr set)))))
    5. (define (encode-symbol symbol tree)
    6. (define (encode-1 current-branch)
    7. (if (leaf? current-branch)
    8. '()
    9. (if (element-of-set? symbol
    10. (symbols (left-branch current-branch)))
    11. (cons 0
    12. (encode-1 (left-branch current-branch)))
    13. (cons 1
    14. (encode-1 (right-branch current-branch))))))
    15. (if (not (element-of-set? symbol (symbols tree)))
    16. (error "bad symbol -- ENCODE-SYMBOL" symbol)
    17. (encode-1 tree)))
    18. ;; test
    19. (encode (decode sample-message sample-tree) sample-tree) ;(0 1 1 0 0 1 0 1 0 1 1 1 0)
  • Exercise 2.69

    I’ve designed in my personal note. The result:

    1. (define (generate-huffman-tree pairs)
    2. (successive-merge (make-leaf-set pairs)))
    3. (define (successive-merge leaf-set) ;provided that leaf-set is list
    4. (cond ((null? leaf-set) '())
    5. ((null? (cdr leaf-set)) (car leaf-set)) ;termination condition
    6. (else ;provided that it has atleast 2 leaves in this
    7. (let ((leaf1 (car leaf-set))
    8. (leaf2 (cadr leaf-set))
    9. (rest-set (cddr leaf-set)))
    10. (successive-merge (adjoin-set (make-code-tree leaf1 leaf2)
    11. rest-set))))))
    12. ;;test
    13. (define sample-pairs '((A 4) (B 2) (C 1) (D 1)))
    14. (decode sample-message (generate-huffman-tree sample-pairs)) ;(a d a b b c a)
  • Exercise 2.70

    If we do the instructions, we get

    1. (define rock-pairs '((A 2) (BOOM 1) (GET 2) (JOB 2) (NA 16) (SHA 3) (YIP 9) (WAH 1)))
    2. (define rock-tree (generate-huffman-tree rock-pairs))
    3. (define rock-message '(Get a job
    4. Sha na na na na na na na na
    5. Get a job
    6. Sha na na na na na na na na
    7. Wah yip yip yip yip yip yip yip yip yip
    8. Sha boom))
    9. (define rock-code (encode rock-message rock-tree))
    10. (length rock-code) ;84

    In Huffman encoding, we need 84 bits for encoding the message. If we have used
    fixed-length code for eight-symbol alphabet–we need at least 3 bits per
    symbol, we get (* 3 (length rock-message)) that evaluated to 108. We saved
    about 22% bits for encoding the message.

  • Exercise 2.71

    We can prove by induction on n or on depth of tree that the most frequent
    symbol requires 1 bit to encode and for the least frequent one n-1 bits.

  • Exercise 2.72

    For the given special case, we can prove that for the most frequent one the
    order of growth is Θ(n) and for the least frequent one Θ(n2) at the
    worst case.

Data-Directed Programming and Additivity

  • Exercise 2.73

    We can convert the symbolic differentiation system to data-directed style:

    1. (define (deriv exp var)
    2. (cond ((number? exp) 0)
    3. ((variable? exp) (if (same-variable? exp var) 1 0))
    4. (else ((get 'deriv (operator exp)) (operands exp) var))))
    5. (define (operator exp) (car exp))
    6. (define (operands exp) (cdr exp))
    • a.

      Here we recognized the exp as typed datum as being discussed in this section,
      i.e. we found that we use operator only for determining the type of procedure
      to be applied at operands; this is exactly what we discussed so far. Note that
      we handle the number and variable case explicitly because they aren’t attached a
      type– just contents. And also if we strugle to attach auxilary type to those,
      our code got cluttered more than present one.

      To summarize, deriv procedure do

      1. Check the exp is number or variable and do the right thing for that
      2. If it isn’t that means it’s compound expression, on which attached type. So
        deriv look up the table with operation name– deriv– and expression
        type– operator to get appropriate procedure for that expression.
      3. Then apply that procedure given arguments– operands with respect to var.
    • b.

      We can accomplish this by using analogy with the complex number representation.
      Here we decide to use operator’s symbol as type consistently:

      1. (define (install-sum--package)
      2. ;; constructor
      3. (define (make-sum a1 a2)
      4. (cond ((=number? a1 0) a2)
      5. ((=number? a2 0) a1)
      6. ((and (number? a1) (number? a2)) (+ a1 a2))
      7. (else (list '+ a1 a2))))
      8. ;; selectors
      9. (define (addend operands) (car operands))
      10. (define (augend operands) (cadr operands))
      11. ;; interface to the rest of the system.
      12. (put 'make '+ make-sum)
      13. (put 'addend '+ addend)
      14. (put 'augend '+ augend)
      15. ;; For differential algebraic system.
      16. (put 'deriv '+
      17. (lambda (operands var) (make-sum (deriv (addend operands) var)
      18. (deriv (augend operands) var))))
      19. 'done)
      20. (define (install-product-package)
      21. ;;constructor
      22. (define (make-product m1 m2)
      23. (cond ((or (=number? m1 0) (=number? m2 0)) 0)
      24. ((=number? m1 1) m2)
      25. ((=number? m2 1) m1)
      26. ((and (number? m1) (number? m2))
      27. (* m1 m2))
      28. (else (list '* m1 m2))))
      29. ;;selectors
      30. (define (multiplier p) (car p))
      31. (define (multiplicand p) (cadr p))
      32. ;; interface to the rest of the system.
      33. (put 'make '* make-product)
      34. (put 'multiplier '* multiplier)
      35. (put 'multiplicand '* multiplicand)
      36. ;; For differential algebraic system
      37. (put 'deriv '*
      38. (lambda (operands var)
      39. ((get 'make '+)
      40. (make-product (multiplier operands)
      41. (deriv (multiplicand operands) var))
      42. (make-product (deriv (multiplier operands) var)
      43. (muliplicand operands)))))
      44. 'done)
    • c.

      To add some additional differentiation rule, all we need to do is to install
      that specific package, anything else not affected by that:

      1. (define (install-exponentiation-package)
      2. ;; constructor
      3. (define (make-exponentiation base exponent)
      4. (cond ((=number? exponent 0) 1)
      5. ((=number? exponent 1) base)
      6. ((and (number? base) (number? exponent))
      7. (expt base exponent))
      8. (else (list '** base exponent))))
      9. ;; selectors
      10. (define (base ex)
      11. (car ex))
      12. (define (exponent ex)
      13. (cadr ex))
      14. ;; differentiation
      15. (define (deriv-expt operands var)
      16. (let ((make-product (get 'make '*)))
      17. (make-product (make-product (exponent operands)
      18. (make-exponentiation (base operands) (- (exponent operands) 1)))
      19. (deriv (base operands) var))))
      20. ;; interface
      21. (put 'make '** make-exponentiation)
      22. (put 'base '** base)
      23. (put 'exponent '** exponent)
      24. (put 'deriv '** deriv-expt)
      25. 'done)
    • d.

      If we changed our mind from

      1. ...
      2. (else ((get 'deriv (operator exp)) (operands exp) var))))
      3. ...

      to

      1. ...
      2. (else ((get (operator exp) 'deriv) (operands exp) var))))
      3. ...

      Then, in our implementation, we need change all the code in each package
      according to that change:

      1. (define (install-sum-package)
      2. ...
      3. (put '+ 'deriv ...)
      4. ...
      5. 'done)
      6. (define (install-product-package)
      7. ...
      8. (put '* 'deriv ...)
      9. ...
      10. 'done)
      11. (define (install-exponentiation-package)
      12. ...
      13. (put '** 'deriv ...)
      14. ...
      15. 'done)
  • Exercise 2.74

    We can solve the problem by using the table which contains all the employee’s
    recode from all the division. As noted in the text, as the data representation
    all different from division to division, to use data-directed programming, these
    individual division’s personal file should have type tag that indicate specific
    division. Further more, we can assume that in the specific division they know
    how to retrieves a specified employee’s record– as mentioned in the statement
    the employee’s name would be supplied for specifying.

    • a.

      We can think of get-record take personal file with employee’s name specified
      for its arguments and then take off the type tag– division– from personal
      file to retrieve:

      1. (define (get-record record-file employee-name)
      2. ;; we assume that each file has division name as its type.
      3. (let ((division (type-tag record-file)))
      4. ((get 'get-record division) employee-name)))

      Here we assume that each division put the procedure to retrieve their
      employee’s record in that division to table; we can get that procedure.

      More formally we only require that

      • each division should attach their record file type tag, which specify their division.
      • each division should register their own method for retrieving specific
        employee’s record with universal key, employee’s name.

      As long as those specification satisfied, how the recored structured do not
      affect above implementation.

    • b.

      By the same argument with a., to select specific information from given
      record we should know the division to which the employee belong. So to speak,
      each record should have division information as its type.

      1. (define (get-salary record)
      2. (let ((division (type-tag record)))
      3. ((get 'get-salary division) record)))

      It’s not our business but if get-record attach division as type to its return
      value– record, we don’t need to modify any code in individual division file to
      work with above procedure.

    • c.

      Here we only consider one record; it is possible that some employees have same
      name, but we do not consider that: as soon as we find record with name given, we
      only return that one.

      Here we exploit the fact that any scheme data treated as true when it used with
      boolean operation:

      1. (or false 0 5) ;0
      2. (and true 5) ;#t

      Using this fact, we can implement the given task:

      1. (define (find-employee-record division-files employee-name)
      2. (fold-left (lambda (x y) (or x y))
      3. false
      4. (map (lambda (division-file)
      5. (get-record division-file employee-name))
      6. division-files)))

      Note that we assumed get-record return false if it couldn’t find one.

    • d.

      To incorporate with the other division, the new division should

      • attach its own division name to its personal record file,
      • register its own procedure which retrieves its employee’s record to the lookup
        table,
      • also put procedure that retrieves some information from record data to the
        lookup table.

      That’s it. It doesn’t matter how many the employee’s records under that division.
      Cool!

  • Message passing

    I found this section discuss the skeleton of modern object oriented programming.
    To consider how it connected to OOP, we should keep learning the remaining
    chapters in it, but I’ve felt this methodology, message passing, is the most
    simple object that contains all the operations that operate on given object. We
    can consider the constructor of object as somewhat analogous to class
    declaration:

    1. (define (make-from-real-imag x y)
    2. (define (dispatch op)
    3. (cond ((eq? op 'real-part) x)
    4. ((eq? op 'imag-part) y)
    5. ((eq? op 'manitude)
    6. (sqrt (+ (square x) (square y))))
    7. ((eq? op 'angle) (atan y x))
    8. (else
    9. (error "Unknown op -- MAKE-FROM-REAL-IMAG" op))))
    10. dispatch)
  • Exercise 2.75

    We can do the same thing to make-from-mag-ang as above:

    1. (define (make-from-mag-ang r a)
    2. (define (dispatch op)
    3. (cond ((eq? op 'real-part) (* r (cos a)))
    4. ((eq? op 'imag-part) (* r (sin a)))
    5. ((eq? op 'manitude) r)
    6. ((eq? op 'angle) a)
    7. (else
    8. (error "Unknown op -- MAKE-FROM-MAG-ANG" op))))
    9. dispatch)
  • Exercise 2.76

    We can characterizes the three strategies as following:

    • data directed programming: dealing with table, i.e. 2 dimensional. We can
      add arbitrary operation or type (data) on the table without interfering
      existing code.
    • dispatch on type: dealing with operation row. Each of the operation can be
      thought as individual entity. We can add new operation without altering
      existing code.
    • message passing: dealing with type (data) column. Each of data can be
      thought as object which has operations that operate on it. We can add new
      data (type) additively.

    With these information, we are likely to think data directed programming is
    most ideal strategies to deal with big project. However, it has another edge: As
    it allow maximum flexibility, the individual cells in the table get likely to
    lack of connectivity. For instance, when we modeling the real world object, it
    is more intuitive to implement using OOP (in this context, message passing).

    I’ve demonstrated enough to answer the given question: For system in which new
    types must often be added, message passing would be the best choice, whereas
    for system in which new operations must often be added dispatch on type or
    data directed programming both can be good choice as we seen in the complex
    number system. If you could not determine how your system would evolves then
    give data directed programming a shot; it will answer to you.

    Or OOP can be thought as the one that takes both advantages of using type and the
    intuitive representation of message passing.

Lecture 4A: Pattern Matching and Rule-based Substitution

In this lecture, the professor discuss the pattern matching algorithm in
general. I think it is main feature of all the functional programming language
such as Haskell, ML, Scala, and so on. In their language– especially Scala,
which I’ve used commercially– user also can add arbitrary pattern variable by
using special notation– case class in Scala. These feature is the instance of
application of data-directed programming so that we can modify the compiler’s
behavior seamlessly.

We can introduce pattern matching feature by ourself:

  1. (define (match pat exp dict)
  2. (cond ((eq? dict 'failed) 'failed)
  3. ((not (pair? pat))
  4. (if (not (pair? exp))
  5. (if (eq? pat exp)
  6. dict
  7. 'failed)
  8. 'failed))
  9. ((arbitrary-constant? pat)
  10. (if (constant? exp)
  11. (extend-dictionary pat exp dict)
  12. 'failed))
  13. ((arbitrary-variable? pat)
  14. (if (variable? exp)
  15. (extend-dictionary pat exp dict)
  16. 'failed))
  17. ((arbitrary-expression? pat)
  18. (extend-dictionary pat exp dict))
  19. ((not (pair? exp)) 'failed)
  20. (else
  21. (match (cdr pat)
  22. (cdr exp)
  23. (match (car pat)
  24. (car exp)
  25. dict)))))
  26. (define (arbitrary-constant? pat)
  27. (and (pair? pat) (eq? (car pat) '?c)))
  28. (define (arbitrary-variable? pat)
  29. (and (pair? pat) (eq? (car pat) '?v)))
  30. (define (arbitrary-expression? pat)
  31. (and (pair? pat) (eq? (car pat) '?)))
  32. (define (constant? exp)
  33. (number? exp))
  34. (define (variable? exp)
  35. (symbol? exp))
  36. (define (instantiate skeleton dict)
  37. (define (loop s)
  38. (cond ((not (pair? s)) s)
  39. ((skeleton-evaluation? s)
  40. (evaluate (eval-exp s) dict))
  41. (else (cons (loop (car s))
  42. (loop (cdr s))))))
  43. (loop skeleton))
  44. (define (skeleton-evaluation? s)
  45. (and (pair? s) (eq? (car s) ':)))
  46. (define (eval-exp s)
  47. (cadr s))
  48. (define (simplifier the-rules)
  49. ; (define (simplify-exp exp)
  50. ; (try-rules (if (compound? exp)
  51. ; (simplify-parts exp)
  52. ; exp)))
  53. ; (define (simplify-parts exp)
  54. ; (if (null? exp)
  55. ; '()
  56. ; (cons (simplify-exp (car exp))
  57. ; (simplify-parts (cdr exp)))))
  58. ;; Another idiom
  59. (define (simplify-exp exp)
  60. (try-rules
  61. (if (compound? exp)
  62. (map simplify-exp exp)
  63. exp)))
  64. (define (try-rules exp)
  65. (define (scan rules)
  66. (if (null? rules)
  67. exp
  68. (let ((dict
  69. (match (pattern (car rules))
  70. exp
  71. (empty-dictionary))))
  72. (if (eq? dict 'failed)
  73. (scan (cdr rules))
  74. (simplify-exp
  75. (instantiate
  76. (skeleton (car rules))
  77. dict))))))
  78. (scan the-rules))
  79. simplify-exp)
  80. (define (pattern rule)
  81. (car rule))
  82. (define (skeleton rule)
  83. (cadr rule))
  84. (define (compound? exp) (pair? exp))
  85. (define (evaluate form dict)
  86. (if (not (pair? form))
  87. (lookup form dict)
  88. (apply
  89. (eval (lookup (car form) dict)
  90. user-initial-environment)
  91. (map (lambda (v)
  92. (lookup v dict))
  93. (cdr form)))))
  94. (define (empty-dictionary) '())
  95. (define (extend-dictionary pat dat dict)
  96. (let ((name (variable-name pat)))
  97. (let ((v (assq name dict)))
  98. (cond ((not v)
  99. (cons (list name dat) dict))
  100. ((eq? (cadr v) dat) dict)
  101. (else 'failed)))))
  102. (define (variable-name pat)
  103. (cadr pat))
  104. (define (lookup var dict)
  105. (let ((v (assq var dict)))
  106. (if (not v) var (cadr v))))
  107. ;; (cdr '(x . y)) ;y
  108. (define deriv-rules
  109. '(
  110. ( (dd (?c c) (? v)) 0 )
  111. ( (dd (?v v) (? v)) 1 )
  112. ( (dd (?v u) (? v)) 0 )
  113. ( (dd (+ (? x1) (? x2)) (? v))
  114. (+ (dd (: x1) (: v))
  115. (dd (: x2) (: v))) )
  116. ( (dd (* (? x1) (? x2)) (? v))
  117. (+ (* (: x1) (dd (: x2) (: v)))
  118. (* (dd (: x1) (: v)) (: x2))) )
  119. ))
  120. (define algebra-rules
  121. '(
  122. (((? op) (?c e1) (?c e2))
  123. (: (op e1 e2)))
  124. (((? op) (? e1) (?c e2))
  125. ((? op) (: e2) (: e1)))
  126. ((+ 0 (? e))
  127. (: e))
  128. ((* 1 (? e))
  129. (: e))
  130. ((* 0 (? e))
  131. 0)
  132. ((* (?c e1) (* (?c e2) (? e3)))
  133. (* (: (* e1 e2)) (: e3)))
  134. ((* (? e1) (* (?c e2) (? e3)))
  135. (* (: e2) (* (: e1) (: e3))))
  136. ((* (* (? e1) (? e2)) (? e3))
  137. (* (: e1) (* (: e2) (: e3))))
  138. ((+ (?c e1) (+ (?c e2) (? e3)))
  139. (+ (: (+ e1 e2)) (: e3)))
  140. ((+ (? e1) (+ (?c e2) (? e3)))
  141. (+ (: e2) (+ (: e1) (: e3))))
  142. ((+ (+ (? e1) (? e2)) (? e3))
  143. (+ (: e1) (+ (: e2) (: e3))))
  144. ((+ (* (?c c) (? a)) (* (?c d) (? a)))
  145. (* (: (+ c d)) (: a)))
  146. ((* (? c) (+ (? d) (? e)))
  147. (+ (* (: c) (: d)) (* (: c) (: e))))
  148. ))

Then we can calculate length of list like Standard ML:

  1. (define length-list
  2. '(
  3. ((ll ()) 0) ;base case
  4. ((ll ((? x) . (? y))) (+ 1 (ll (: y)))) ;recursive case
  5. ))
  6. ((simplifier length-list) '(ll (x y z))) ;(+ 1 (+ 1 (+ 1 0)))
  7. ((simplifier algebra-rules) ((simplifier length-list) '(ll (x y z)))) ;3

Note that here as we don’t used stack for recursive process, we have to stack up
explicitly using symbols; then we simplify using algebraic rules.

Then we can test the previous deriv test using dsimp:

  1. (define dsimp
  2. (simplifier deriv-rules))
  3. (define asimp
  4. (simplifier algebra-rules))
  5. (deriv '(* (* x y) (+ x 3)) 'x) ;(+ (* x y) (* y (+ x 3)))
  6. (asimp (dsimp '(dd (* (* x y) (+ x 3)) x))) ;(+ (* x y) (+ (* 3 y) (* y x)))

Well, we need more clever algebraic simplifying rules here.

Systems with Generic Operations

Generic Arithmetic Operations

Now we want to integrate all the arithmetic system to generic arithmetic system;
that is, by just using add we want to add whatever arithmetic type that we
constructed so far: rational numbers, complex numbers, and primitive numbers in
scheme.

To do so, first we should attach tag at each arithmetic system for
data-directed programming. After that we use the generic arithmetic by using
apply-generic as we did in section 2.4.3:

  1. (define (add x y) (apply-generic 'add x y))
  2. (define (sub x y) (apply-generic 'sub x y))
  3. (define (mul x y) (apply-generic 'mul x y))
  4. (define (div x y) (apply-generic 'div x y))

For the primitive numbers, all we need to do is just attach tag on each
construction:

  1. (define (install-scheme-number-package)
  2. (define (tag x)
  3. (attach-tag 'scheme-number x))
  4. (put 'add (scheme-number scheme-number)
  5. (lambda (x y) (tag (+ x y))))
  6. (put 'sub (scheme-number scheme-number)
  7. (lambda (x y) (tag (- x y))))
  8. (put 'mul (scheme-number scheme-number)
  9. (lambda (x y) (tag (* x y))))
  10. (put 'div (scheme-number scheme-number)
  11. (lambda (x y) (tag (/ x y))))
  12. (put 'make 'scheme-number
  13. (lambda (x) (tag x)))
  14. 'done)

And user of this package would access constructor by following procedure:

  1. (define (make-scheme-number n)
  2. ((get 'make 'scheme-number) n))

Exploiting data-directed programming, we don’t need modify any parts of
previous code; we just add the put s for interfacing others.

Here we show the complex number package for instance, rational numbers can be
implemented similarly (see text book):

  1. (define (install-complex-package)
  2. ;; improted procedures from rectangular and polar packages
  3. (define (make-from-real-imag x y)
  4. ((get 'make-from-real-imag 'rectangular) x y))
  5. (define (make-from-mag-ang r a)
  6. ((get 'make-from-mag-ang 'polar) x y))
  7. ;; internal procedure
  8. (define (add-complex z1 z2)
  9. (make-from-real-imag (+ (real-part z1) (real-part z2))
  10. (+ (imag-part z1) (imag-part z2))))
  11. *** other procedures goes here
  12. ;; interface to rest of the system
  13. (define (tag z) (attach-tag 'complex z))
  14. (put 'add '(complex complex)
  15. (lambda (z1 z2) (tag (add-complex z1 z2))))
  16. *** other procedures goes here
  17. (put 'make-from-real-imag 'complex
  18. (lambda (x y) (tag (make-from-real-imag x y))))
  19. (put 'make-from-mag-ang 'complex
  20. (lambda (r a) (tag (make-from-mag-ang r a))))
  21. 'done)

Then users can call the constructor granted the procedure:

  1. (define (make-complex-from-real-imag x y)
  2. ((get 'make-from-real-imag 'complex) x y))
  3. (define (make-complex-from-mag-ang r a)
  4. ((get 'make-from-mag-ang 'complex) r a))
  • Exercise 2.77

    By calling

    1. (put 'real-part '(complex) real-part)
    2. (put 'imag-part '(complex) imag-part)
    3. (put 'magnitude '(complex) magnitude)
    4. (put 'angle '(complex) angle)

    we can call each selector for complex number. How? let’s trace the call, for
    instance here we call the magnitude:

    1. Call (magnitude (make-complex-from-mag-ang r a)). Then
    2. By the definition of

      1. (define (magnitude z) (apply-generic 'magnitude z))

      (magnitude z) call apply-generic (here we abbreviated the object as z)

    3. Then apply generic strip the type tag– complex type– and look up the operation-type table
      to get appropriate operation to apply. By above put s, the returned
      operation is also magnitude; then apply that procedure to contents of z
    4. We do the same thing as step 3. but with 'polar tag; then apply-generic
      get (lambda (z) (car z)) to apply with contents of (r . a).
    5. It return r as expected.

    So to summarize, the number of invoking apply-generic is 2 and the first
    dispatch returns magnitude which we just started with and then the last
    dispatch do the real work.

  • Exercise 2.78

    To exploit the internal type system of scheme, we should modify the procedures
    that we made for type: type-tag, contents, attach-tag. The naive approach
    would add conditional clause that check whether it has internal type:

    1. (define (attach-tag type-tag contents)
    2. (cond ((number? contents) contents) ;do nothing: it has internal type system
    3. ;; We can add more primitive data here
    4. (else (cons type-tag contents))))
    5. (define (type-tag datum)
    6. (cond ((pair? datum) (car datum))
    7. ((number? datum) 'scheme-number)
    8. ;; other primitive goes here
    9. (else (error "Bad tagged datum -- TYPE-TAG" datum))))
    10. (define (contents datum)
    11. (cond ((pair? datum) (cdr datum))
    12. ((number? datum) datum)
    13. (else (error "Bad tagged datum -- CONTENTS" datum))))

    Or we can accomplish this data-directed style (revised):

    1. (define (attach-tag type-tag contents)
    2. (cond ((number? contents) contents) ;do nothing: it has internal type system
    3. ;; We can add more primitive data here
    4. (else (cons type-tag contents))))
    5. (define (type-tag datum)
    6. (cond ((pair? datum) (car datum))
    7. ((number? datum) ((get 'type 'scheme-number) datum)) ;data-directed programming
    8. ;; other primitive goes here
    9. (else (error "Bad tagged datum -- TYPE-TAG" datum))))
    10. (define (contents datum)
    11. (cond ((pair? datum) (cdr datum))
    12. ((number? datum) datum)
    13. (else (error "Bad tagged datum -- CONTENTS" datum))))

    Then we don’t need change any parts of previous code.
    Then we need to change scheme-number package accordingly:

    1. (define (install-scheme-number-package)
    2. (define (scheme-number-type num) ;actually this means we implemented abstract class scheme-number.
    3. (cond ((and (integer? num) (exact? num)) 'integer) ;inter? -> exact? for cope with (raise (raise (raise 5)))
    4. ((real? num) 'real)
    5. (else (error "Unknown scheme number -- SCHEME-NUMBER-TYPE" num))))
    6. (put 'type 'scheme-number scheme-number-type) ;for type tag
    7. (put 'make 'real exact->inexact)
    8. (put 'make 'integer inexact->exact)
    9. (define subtypes '(real integer))
    10. (for-each
    11. (lambda (t1)
    12. (for-each
    13. (lambda (t2)
    14. (for-each
    15. (lambda (op)
    16. (put (car op) (list t1 t2)
    17. (lambda (x y) ((cdr op) x y))))
    18. (list (cons 'add +) (cons 'sub -) (cons 'mul *) (cons 'div /))))
    19. subtypes)
    20. (put 'make t1 identity-procedure) ;do nothing just warpping.
    21. )
    22. subtypes)
    23. 'done)
    24. (define make-real (get 'make 'real))
    25. (define make-integer (get 'make 'integer))
  • Exercise 2.79

    We can do this by either modifying each arithmetic packages or adding new
    package that add equ?. Here we implement the latter one:

    1. ;; Exercise 2.79
    2. (define (install-equ-package)
    3. ;; import from rational number package
    4. (define (numerator r) ((get 'numerator '(rational)) r))
    5. (define (denominator r) ((get 'denominator '(rational)) r))
    6. ;; internal procedures
    7. (define scheme-types '(real integer))
    8. (for-each (lambda (type) (put 'equ? (list type type) =)) scheme-types)
    9. (put 'equ? '(rational rational) (lambda (r1 r2)
    10. (and (= (numerator r1) (numerator r2))
    11. (= (denominator r1) (denominator r2)))))
    12. (put 'equ? '(complex complex) (lambda (c1 c2)
    13. (and (= (real-part c1) (real-part c2))
    14. (= (imag-part c1) (imag-part c2)))))
    15. 'done)
    16. (define (equ? x y) (apply-generic 'equ? x y))

    Here we assumed that the selectors of rational number– numer, denom
    generally accessible somehow. We can accomplish this adding that procedures in
    operation-type table in rational number package.

  • Exercise 2.80

    Here we use equ? for implementing =zero? and also exploit the fact
    (= 0.0 0); #t for ordinary number:

    1. (define (install-zero-package)
    2. ;; import from rational number package
    3. (define (numerator r) ((get 'numerator '(rational)) r))
    4. (define (denominator r) ((get 'denominator '(rational)) r))
    5. (define scheme-types '(real integer))
    6. (for-each (lambda (type) (put '=zero? (list type) (lambda (e) (equ? e 0)))) scheme-types)
    7. (put '=zero? '(rational) (lambda (r) (equ? (numerator r) 0)))
    8. (put '=zero? '(complex) (lambda (c)
    9. (equ? (attach-tag 'complex c) (make-complex-from-real-imag 0 0))))
    10. 'done)
    11. (define (=zero? x) (apply-generic '=zero? x))

Combining Data of Different Types

  • Exercise 2.81

    We have apply-generic that cope with coercion:

    1. (define (apply-generic op . args)
    2. (let ((type-tags (map type-tag args)))
    3. (let ((proc (get op type-tags)))
    4. (if proc
    5. (apply proc (map contents args))
    6. (if (= (length args) 2)
    7. (let ((type1 (car type-tags))
    8. (type2 (cadr type-tags))
    9. (a1 (car args))
    10. (a2 (cadr args)))
    11. (let ((t1->t2 (get-coercion type1 type2))
    12. (t2->t1 (get-coercion type2 type1)))
    13. (cond (t1->t2
    14. (apply-generic op (t1->t2 a1) a2))
    15. (t2->t1
    16. (apply-generic op a1 (t2->t1 a2)))
    17. (else
    18. (error "No method for these types"
    19. (list op type-tags))))))
    20. (error "No method for these types"
    21. (list op type-tags)))))))
    • a.

      As t1->t2 is not false by the self coercion method, we get into infinite loop.

    • b.

      It works as it is. Note that in the sub clause of t1->t2 we call again
      apply-generic with coerced types; if we coerced already to the same type, and
      then also the operation for that types doesn’t exist on table, it get into
      coerce check again, but this time fails as it should since the coercion to its
      own type is not defined at all. However as it is, it has overhead that lookup
      the table for checking whether there exist coercion to same type. We can avoid this.

    • c.

      Result:

      1. (define (apply-generic op . args)
      2. (define (raise-exception)
      3. (error "No method for these types"
      4. (list op type-tags)))
      5. (let ((type-tags (map type-tag args)))
      6. (let ((proc (get op type-tags)))
      7. (if proc
      8. (apply proc (map contents args))
      9. (if (= (length args) 2)
      10. (let ((type1 (car type-tags))
      11. (type2 (cadr type-tags))
      12. (a1 (car args))
      13. (a2 (cadr args)))
      14. (if (not (eq? type1 type2))
      15. (let ((t1->t2 (get-coercion type1 type2))
      16. (t2->t1 (get-coercion type2 type1)))
      17. (cond (t1->t2
      18. (apply-generic op (t1->t2 a1) a2))
      19. (t2->t1
      20. (apply-generic op a1 (t2->t1 a2)))
      21. (else
      22. (raise-exception))))
      23. (raise-exception))
      24. (raise-exception))))))

      It’s messy.

  • Exercise 2.82

    Extend to arbitary number of operands in apply-generic. Let first we try what
    the text suggested: To attempt to coerce all the arguments to the type of the
    first argument, then to the type of the second arguemnt, and so on.

    Here is the algorithm:

    1. Take one type to which all the arguments to be tried to coerce.
    2. Then coerce each argument (except the one that taken as “to type” argument).
    3. Then try to get operation with coerced types.
    4. Keep doing from 1 to 3 steps until either such operation found or tried all
      of the types of given argument (this time we should error appropriately).

    It is reasonable to define the step 2 procedure standalone unit. Also this unit
    can be decomposed to

    • check whether such coerce procedure exist,

      1. (define (try-coerce-> type types)
      2. (let ((coercions
      3. (map (lambda (t)
      4. (if (eq? t type)
      5. identity-procedure ;return taken argument itself
      6. (get-coercion t type)))
      7. types)))
      8. (let ((exist?
      9. (fold-left (lambda (x y) (and x y))
      10. true
      11. coercions)))
      12. (if exist?
      13. coercions
      14. false))))
    • if it exists, then apply to each arguments.

      1. (define (apply-each procs args) ;provided that these are same length
      2. (if (null? procs)
      3. '()
      4. (cons ((car procs) (car args))
      5. (apply-each (cdr procs) (cdr args)))))

    Armed with this, we can implement what we want using signal processing scheme:

    1. (define (apply-generic op . args)
    2. (define (raise-exception)
    3. (error "no method for these types"
    4. (list op type-tags)))
    5. (let ((type-tags (map type-tag args)))
    6. (let ((proc (get op type-tags)))
    7. (cond (proc (apply proc (map contents args)))
    8. ((fold-left (lambda (t1 t2) ;check whether all the types are same
    9. (if t1
    10. (if (eq? t1 t2)
    11. t1
    12. false)
    13. false))
    14. type-tags)
    15. (raise-exception)) ;error
    16. (else
    17. (let ((result (fold-left (lambda (x y) (or x y))
    18. false
    19. (map (lambda (tArgs)
    20. (let ((types (map type-tag tArgs)))
    21. (let ((tProc (get op types)))
    22. (if tProc
    23. (cons tProc (map contents tArgs))
    24. false))))
    25. (map (lambda (coercions)
    26. (apply-each coercions args))
    27. (filter identity-procedure
    28. (map (lambda (type)
    29. (try-coerce-> type type-tags))
    30. type-tags)))))))
    31. (if result
    32. (apply (car result) (cdr result))
    33. (raise-exception))))))))

    It’s total mess. Can you read this? Even for me it’s hard to understand there’re
    a lot of tricks; I should refactor my procedure.

    Let think about the situation where this strategy don’t work. For instance, we
    can think the circumstance where we got task that implement matrix in our
    arithmetic systems. In that system, we have to implement scalar multiplication
    of matrix; as we wanted to make our system as general as possible, we have
    chosen to implement that with complex number (the supremum type of number
    system). And our user used that procedure with integer number; however it gives
    an error saying “no method for these types”. What happend? Think about it.

    This time let we try the refactoring. There’s no end for it.

    • Try 1: Extract common pattern

      1. (define (apply-generic op . args)
      2. (define (raise-exception)
      3. (error "no method for these types"
      4. (list op type-tags)))
      5. (define (try aArgs types?)
      6. (let ((type-tags (if types?
      7. types?
      8. (map type-tag aArgs))))
      9. (let (proc (get op type-tags))
      10. (if proc
      11. (cons proc (map contents aArgs))
      12. false))))
      13. (let ((type-tags (map type-tag args)))
      14. (let ((procPair (try args type-tags)))
      15. (cond (procPair (apply (car procPair) (cdr procPair)))
      16. ((fold-left (lambda (t1 t2) ;check whether all the types are same
      17. (if t1
      18. (if (eq? t1 t2)
      19. t1
      20. false)
      21. false))
      22. type-tags)
      23. (raise-exception)) ;error
      24. (else
      25. (let ((result (fold-left (lambda (x y) (or x y))
      26. false
      27. (map (lambda (tArgs)
      28. (try tArgs false))
      29. (map (lambda (coercions)
      30. (apply-each coercions args))
      31. (filter identity-procedure
      32. (map (lambda (type)
      33. (try-coerce-> type type-tags))
      34. type-tags)))))))
      35. (if result
      36. (apply (car result) (cdr result))
      37. (raise-exception))))))))

      Well, it’s still mess; as I’ve exploited signal interface I’m persuading myself
      that it’s the style of that.

    • Conclusion

      You have to consider or design the algorithm thoroughly until the end; otherwise
      if you jump in the middle of that like me, you get in such trouble.

      Let’s try thorough design in signal processing scheme:

      1. Take type list
      2. Filter it with have coercion to?
      3. Filter it with have such operation?
      4. If it is null signal error else
      5. Then apply args coerce to car of it
      6. and then apply found operation to it’s contents

      We can do these by wishful thinking and using conventional interface. Now we
      should implement the auxiliary procedures

      • have-coercion-to?

        We can accomplish this amending try-coerce->:

        1. (define (have-coercion-to? type types)
        2. (fold-left (lambda (x y) (and x y))
        3. true
        4. (map (lambda (t)
        5. (if (eq? t type)
        6. identity-procedure ;return taken argument itself
        7. (get-coercion t type)))
        8. types)))
      • have such operation?

        To implement this we should supply length of args to test with (get op type); this is no problem if we define these procedure in the apply-generic:

        1. (define (have-such-operation? type)
        2. (get op (map (lambda (t) type)
        3. args)))
      • Put together

        1. (define (apply-generic op . args)
        2. (define (raise-exception)
        3. (error "no method for these types"
        4. (list op type-tags)))
        5. (let ((type-tags (map type-tag args)))
        6. (let ((proc (get op type-tags)))
        7. (cond (proc (apply proc (map contents args))) ;base case?
        8. ((have-same-types? type-tags) (raise-exception)) ;error
        9. (else
        10. (coerce-and-apply op args type-tags raise-exception))))))
        11. (define (coerce-and-apply op args types exception)
        12. (define (have-coercion-to? type)
        13. (fold-left (lambda (x y) (and x y))
        14. true
        15. (map (lambda (t)
        16. (if (eq? t type)
        17. identity-procedure ;return taken argument itself
        18. (get-coercion t type)))
        19. types)))
        20. (define (have-such-operation? type)
        21. (get op (map (lambda (t) type)
        22. args)))
        23. (let ((avail-type (filter have-such-operation?
        24. (filter have-coercion-to?
        25. types))))
        26. (if (null? avail-type)
        27. (exception)
        28. (let ((type (car avail-type)))
        29. (let ((proc (get op (map (lambda (t) type)
        30. types)))
        31. (coerced-args (apply-each
        32. (map (lambda (t)
        33. (if (eq? t type)
        34. identity-procedure
        35. (get-coercion t type)))
        36. types)
        37. args)))
        38. (apply proc coerced-args))))))
        39. (define (have-same-types? types)
        40. (fold-left (lambda (t1 t2) ;check whether all the types are same
        41. (if t1
        42. (if (eq? t1 t2)
        43. t1
        44. false)
        45. false))
        46. (car types)
        47. (cdr types)))

        Here we used apply-each, which defined before.

        Let’s test:

        1. (define (install-test)
        2. (put 'add '(real real real real) +)
        3. (put-coercion 'integer 'real exact->inexact)
        4. (add 5.4 3 2 .43))

        And to support arbitrary arguments in add procedure:

        1. (define (add . xs) (apply apply-generic (cons 'add xs)))

        Then the test return 10.83. Works well.

        Way better. Isn’t it?

  • Exercise 2.83

    Here we also implement this feature as one isolated package. This is
    straightforward:

    1. (define (install-raise)
    2. ;; import from ratinal number
    3. (define numer (get 'numerator '(rational)))
    4. (define denom (get 'denominator '(rational)))
    5. ;; internal procedures
    6. (define (real->complex num)
    7. (make-complex-from-real-imag num 0))
    8. (define (rational->real num)
    9. (make-real (/ (numer num) (denom num))))
    10. (define (integer->rational num)
    11. (make-rational num 1.0))
    12. ;; interface to rest system
    13. (put 'raise '(integer) integer->rational)
    14. (put 'raise '(rational) rational->real)
    15. (put 'raise '(real) real->complex)
    16. 'done)

    We’ve already coped with this situation in previous exercise.

  • Exercise 2.84

    Here is the algorithm:

    1. Check all the arguments’ type are in tower:

      1. (define tower '(integer rational real complex))
    2. Then, check all the types are equal (apply-generic handles the first check).
    3. If not, raise the arguement whose type is the subtype of others.
    4. Do step 3 until all the types are equal. Then apply apply-generic to it.

    To find the argument whose type is minimum in the arguments,
    we need to devise the method of comparing the rank of types. The easiest way
    is mapping type to rank

    1. Maps the type to rank number.
    2. Then compare the numbers!

    Here is the mapping procedure:

    1. (define (type->rank type)
    2. (define (iter towerstack)
    3. (cond ((null? towerstack)
    4. (error "given type not included in tower TYPE->RANK" type))
    5. ((eq? (car towerstack) type) (length towerstack))
    6. (else (iter (cdr towerstack)))))
    7. (iter tower))

    Or, more straightforward encoding would be

    1. Let assume that we compare two types in the tower.
    2. If latter type is in the former’s supertype then former < latter.
    3. else, former ≥ latter.
    1. (define (type< t1 t2)
    2. (define (super-of-t1 towerstack)
    3. (cond ((null? towerstack)
    4. (error "given type not included in tower TYPE<" t1))
    5. ((eq? (car towerstack) t1) (cdr towerstack))
    6. (else (super-of-t1 (cdr towerstack)))))
    7. (element-of-set? t2 (super-of-t1 tower)))

    Using this, we can find the minimum type in given types:

    1. (define (mintype types) ;provided that types are in tower
    2. (fold-left (lambda (t1 t2)
    3. (if (type< t1 t2)
    4. t1
    5. t2))
    6. (car types)
    7. (cdr types)))

    Here is the result:

    1. (define (apply-generic op . args)
    2. (let ((type-tags (map type-tag args)))
    3. (let ((proc (get op type-tags))
    4. (raise-exception
    5. (lambda () (error "no method for these types"
    6. (list op type-tags)))))
    7. (cond (proc (apply proc (map contents args))) ;base case?
    8. ((have-same-types? type-tags) (raise-exception)) ;error
    9. ((all-in-tower? type-tags)
    10. (apply
    11. apply-generic
    12. (cons op
    13. (do-until raise-minimum
    14. (lambda (args)
    15. (have-same-types? (map type-tag args)))
    16. (raise-minimum args)))))
    17. (else
    18. (coerce-and-apply op args type-tags raise-exception))))))

    We capture the common procedure pattern by

    1. (define (all-in-tower? types)
    2. (fold-left (lambda (t1 t2)
    3. (and t1
    4. (element-of-set? t2 tower)))
    5. true
    6. types))
    7. (define (raise-minimum args)
    8. (let ((minT (mintype (map type-tag args))))
    9. (map (lambda (arg)
    10. (if (eq? (type-tag arg) minT)
    11. (raise arg)
    12. arg))
    13. args)))
    14. (define (do-until proc pred args)
    15. (define (iter args)
    16. (if (pred args)
    17. args
    18. (iter (proc args))))
    19. (iter args))

    And we can test this feature:

    1. (add (make-complex-from-real-imag 5 3) 5 (make-rational 9 16) 5.3)
    2. ;; (complex rectangular 15.8625 . 3)

    We used generalized version of arithmetic operations:

    1. (define (add . xs)
    2. (fold-left (lambda (x y)
    3. (apply-generic 'add x y))
    4. (car xs)
    5. (cdr xs)))
    6. (define (sub . xs)
    7. (fold-left (lambda (x y)
    8. (apply-generic 'sub x y))
    9. (car xs)
    10. (cdr xs)))
    11. (define (mul . xs)
    12. (fold-left (lambda (x y)
    13. (apply-generic 'mul x y))
    14. (car xs)
    15. (cdr xs)))
    16. (define (div . xs)
    17. (fold-left (lambda (x y)
    18. (apply-generic 'div x y))
    19. (car xs)
    20. (cdr xs)))
  • Exercise 2.85

    • Implementing proeject

      As implied in text– avoided to project 1.5 to rational, in fact, the
      projection from real to rational is quite complicate process. For
      simplicity, we also avoid the projection from real to rational.

      1. (define (install-project)
      2. ;; internal procedures
      3. (define (complex->real num)
      4. (make-real (real-part num)))
      5. (define (real->integer num)
      6. (make-integer (round num)))
      7. (define (rational->integer num)
      8. (make-integer (round (raise num)))) ;depends on raise package
      9. ;; interface to rest system
      10. (put 'project '(rational) rational->integer)
      11. (put 'project '(real) real->integer)
      12. (put 'project '(complex) complex->real)
      13. 'done)
    • Implementing drop

      We can deduce rules from the text:

      1. Check whether project defined with respect to the given argument
      2. If it is, then try drop one level: Project it and raise to what we started
        with and then check it equals to given argument using equ? then project one
        level.
      3. Do step 2 until neither step 1 is satisfied nor step 2.

      The result:

      1. (define (drop term)
      2. (let ((type (type-tag term)))
      3. (cond ((not (get 'project type))
      4. term)
      5. (else
      6. (let ((drop-1 (project term)))
      7. (if (equ? term
      8. (do-until raise
      9. (lambda (arg)
      10. (eq? (type-tag arg)
      11. type))
      12. drop-1))
      13. (drop drop-1)
      14. term))))))
    • Put together to apply-generic

      1. (define (apply-generic op . args)
      2. (let ((type-tags (map type-tag args)))
      3. (let ((proc (get op type-tags))
      4. (raise-exception
      5. (lambda () (error "no method for these types"
      6. (list op type-tags)))))
      7. (cond (proc (drop (apply proc (map contents args)))) ;simply result
      8. ((have-same-types? type-tags) (raise-exception)) ;error
      9. ((all-in-tower? type-tags)
      10. (apply
      11. apply-generic
      12. (cons op
      13. (do-until raise-minimum
      14. (lambda (args)
      15. (have-same-types? (map type-tag args)))
      16. (raise-minimum args)))))
      17. (else
      18. (coerce-and-apply op args type-tags raise-exception))))))

      Let’s test:

      1. (add (raise 5) (raise (raise (raise 5)))) ;fall into infinite loop!!

      Uoh, it falls into infinite loop! It’s because raise itself applied by drop,
      and then drop apply raise

      We have to revise this. Note that we don’t want to drop every result of
      apply-generic; for now, it is sufficient to drop the result of clause,
      all-in-tower?. Then let’s redo our test:

      1. (add (raise 5) (raise (raise (raise 5)))) ;10

      Okay, let’s move on.

  • Exercise 2.86

    If we can convert all the arithmetic operations used in rectangular and polar
    package to generic arithmetic operations, then complex number can be made by
    arbitrary type generic arithmetic system supports.

    Here we jot down what operations need to be conversed:

    • For rectangular package:
      sqrt, atan, +, *, sin, cos, square
    • For polar package:
      *, +, cos, sin, atan, sqrt, square
    • For complex package:
      +, /, *, -

    So, as consequence, we only need to convert the followings:

    • sqrt, sin, cos, atan, square
    • +, -, *, /

    The latter ones are easy: We already have the generic operations namely, add,
    sub, mul, div.

    For the former ones, some of these are hard to think in the domain of complex
    number, so we constrain the domain of procedure to real:

    1. (define (raise-until upper x)
    2. (do-until raise
    3. (lambda (arg)
    4. (eq? (type-tag arg) upper))
    5. x))
    6. (define (make-general<= utype op opname)
    7. (lambda (x) (let ((type (type-tag x)))
    8. (cond ((type< type utype) ;the domain of procedure is until utype
    9. (op (raise-until utype x)))
    10. ((eq? type utype)
    11. (op x))
    12. (else
    13. (error (string-append "Bad argument type -- " opname) x))))))
    14. (define squareroot
    15. (make-general<= 'real sqrt "SQUARROOT"))
    16. (define sine
    17. (make-general<= 'real sin "SINE"))
    18. (define cosine
    19. (make-general<= 'real cos "COSINE"))
    20. (define (arctan y x)
    21. (let ((yT (type-tag y))
    22. (xT (type-tag x))
    23. (upper 'real))
    24. (cond ((and (or (type< yT upper)
    25. (eq? yT upper))
    26. (or (type< xT upper)
    27. (eq? xT upper)))
    28. (atan (raise-until upper y)
    29. (raise-until upper x)))
    30. (else
    31. (error "Bad arguments -- ARCTAN" y x)))))
    32. (define (square-gen x)
    33. (mul x x))

    Then the rest is simple.

    Then we should test it:

    1. (add (make-complex-from-real-imag (make-rational 5 2) 3)
    2. (make-complex-from-mag-ang (make-rational 3 2) 3.2))
    3. ;; (complex rectangular 1.0025578363078704 . 2.91243878485863)

    Work!

Example: Symbolic Algebra

In this section we will make a new language that manipulate symbolic algebra. As
we have learned from chapter 1, to construct powerful language5 we need to
specify

  • primitives
  • means of combination
  • means of abstraction
  • capturing common pattern

We can think numbers and variables as primitives in symbolic algebra; then we
can compound that primitives using operations like addition and multiplication.
As we embed the language we develop in scheme, we naturally inherite the means
of abstraction of scheme, namely define. For capturing common pattern, we have
two method for that: data-directed programming and closure property; with
combination of these, we can obtain generic arithmetic system we developed until
previous section but now extends this as encompass symbolic algebra.

Actually we have construct “partially” the symbolic algebra system when we dealt
with symbolic differentiation in section 2.3.2. In there, we used scheme’s
special expression quote for representing varibles, and used scheme’s number
system as representing numbers. We can exploit this fact or not: We don’t need
worry about that in this step; we can defer this implementation detail later.

Let’s recap what we’ve learned in previous section. We’ve learned the concept
abstract data type, generic operations that operates on various data type,
data-directed programming that enables us to develop each part of language
individually without any interfering with others.

Abstract data type is something that captures the common pattern or natural
entity which combines some data relevant with it. In the context of symbolic
algebra, we can think of linear combination, polynomial, rational function, or
trigonometric fucntion as common pattern where we organize symbolic entities; so
we can think these pattern as type in foregoing dialogue.

Here we construct polynomial data type for the start. To encode polynomial into
computer language, first, we need to specify what the polynomial is. For
simplicity, as wrote in text book, we only consider the univariate polynomial
and also we consider polynomial syntactically, i.e. (x^{2}+x+1) is different
from (y^{2}+y+1) although these have the same structure.

Now we consider the arithmetic on polynomials. First, we construct the system
about addition and multiplication and then we can add other operations using
data-directed programming as we did before. We use abstract data type for
representing polynomials, which would add flexibilities to our system. To
interfacing with abstract data type we need to specify what the constructor is
and what the selectors are, in other words, we need to deduce contract between
constructor of polynomial and selectors, which encode the properties of
polynomials naturally. For this task, we can play with polynomials for a while,
if you are done with this stuff, then you can deduce

  • polynomials are composed by a variable that is dominant in our univariate
    polynomial with term list where each term consist of its order and coefficient.
  • So, for constructing the polynomial we need to hand over term list and
    variable,
  • and then we have to select each part of it, i.e. variable and term list appropriately.

Note that we installed abstract layer under the polynomials– term and term
list; we separated the variable from its term. As we designing polynomials using
term (and its list), we can assume that terms can be combined using addition and
multiplication and so on. With this buildings, we can construct concretely
polynomial arithmetic using the abstract data type:

  1. (define (arith-poly term-op opname)
  2. (lambda (p1 p2)
  3. (if (same-variable? (variable p1) (variable p2))
  4. (make-poly (variable p1)
  5. (term-op (term-list p1)
  6. (term-list p2)))
  7. (error (string-append "Polys not in the same var -- " opname) (list p1 p2)))))
  8. (define add-poly
  9. (arith-poly add-terms "ADD-POLY"))
  10. (define mul-poly
  11. (arith-poly mul-terms "MUL-POLY"))

Here we used same-variable? that we defined in section 2.3.2 as we noted since
underlying primitives are same.

We are done with the very top level of our language, now we should tackle with
the lower level language, terms. We need to deduce the rules to terms as we did
with polynomial. Note that terms, or more precisely term list, consist of term.
So syntactically we can think term list constructed using term, i.e. there are
two language namely individual term langauge, term list language that using term.

This time, let we think about constructing term list using underlying term
language. Note that term list, as its name implies, having lots of analogy with
list data structure; so using that analogy, we can construct the rules that
govern term list as

  • constructors: - (adjoin-term term term-list) that adjoin a term at the very
    1. beginning of `term-list`, which is analogous to `cons`.
    • (the-empty-termlist) returns empty termlist upon which we keep constructing
      complex term list. This is analogous to nil.
  • selectors: - (first-term term-list) returns the first term in the term-list, which
    1. is analogous to `car`.
    • (rest-terms term-list) returns the term list but the first term, which
      is analogous to cdr.
  • predicates: (empty-termlist? L) return true when L has no term in it,
    else false. That is analogous to null?

In addition with these, we make contract with these that first-term returns
the highest order of term in the term list. And adjoin-term adds term to the
term list and resulting term list in decreasing order in order of term. We could
make this assumption into our code to validate this contract; but for now, we
just relying on these verbal contract.

With these set up we can implement the operations on term list, for the term
language let us construct that as we go:

  1. (define (add-terms L1 L2)
  2. (cond ((empty-termlist? L1) L2)
  3. ((empty-termlist? L2) L1)
  4. (else
  5. (let ((t1 (first-term L1)) (t2 (first-term L2)))
  6. (cond ((< (order t1) (order t2))
  7. (addjoin-term
  8. t2
  9. (add-terms L1
  10. (rest-terms L2))))
  11. ((> (order t1) (order t2))
  12. (addjoin-term
  13. t1
  14. (add-terms L2
  15. (rest-terms L1))))
  16. (else
  17. (adjoin-term
  18. (make-term (order t1)
  19. (add (coeff t1) (coeff t2)))
  20. (add-terms (rest-terms L1)
  21. (rest-terms L2)))))))))

Here we used what we wish to have in term language:

  • construtor: make-term that takes order of the term (integer) with coefficient
  • selectors: - order that return the order of given term,
    • coeff that return the coefficient of given term.

Also note that here we used add rather than + to incorporate the generic
arithmetic system in term, i.e. we can take complex number as coefficient of
term or even more, if we install our polynomial system into our generic
arithmetic system, then term can take polynomial as its coefficient, which is
equivalent to express the full multivariate polynomial in our system.

Multiplication of terms are litte more trickey. To deduce the algorithm, we can
play with multiplication of polynomials in hand using the knowledges of middle
school algebra. We can break down the whole process by

  1. multiply each term of the first addend by the second addend term list
  2. and then add each of these results using add-terms

We can accomplish these procedure using recursive definiiton:

  1. (define (mul-terms L1 L2)
  2. (if (empty-termlist? L1)
  3. (the-empty-termlist)
  4. (add-terms (mul-term-by-all-terms (first-term L1) L2)
  5. (mul-terms (rest-terms L1) L2))))
  6. (define (mul-term-by-all-terms t1 L)
  7. (if (empty-termlist? L)
  8. (the-empty-termlist)
  9. (let ((t2 (first-term L)))
  10. (addjoin-term (make-term (+ (order t1) (order t2))
  11. (mul (coeff t1) (coeff t2)))
  12. (mul-term-by-all-term t1 (rest-terms L))))))

Note that here we also used mul instead of * with the same reason above.

  • Representing term lists

    Here we made contract that interface term list as a ordered list in decreasing
    order in order of term. We could represent the term list as a set of
    coefficients keyed by the order of the term or we could let the order of term
    incoporated as index of term in the list– list of coefficients.

    The major factor that determine which is better is density. For the
    sparse6, the former would be more appropriate; while for the dense
    polynomial the latter would be more efficient. Or we could install both
    representation simultaneously like rectangular and polar representation of
    complex number. Let we first follow the text’s flow– the former one:

    1. (define (adjoin-term term term-list)
    2. (if (=zero? (coeff term))
    3. term-list
    4. (cons term term-list)))
    5. (define (the-empty-termlist) '())
    6. (define (first-term term-list) (car term-list))
    7. (define (rest-terms term-list) (cdr term-list))
    8. (define (empty-termlist? term-list) (null? term-list))
    9. (define (make-term order coeff) (list order coeff))
    10. (define (order term) (car term))
    11. (define (coeff term) (cadr term))

    Finally we can install our polynomial package into our generic arithmetic
    package:

    ```scheme
    (define (install-polynomial-package)
    ;; internal procedures
    ;; representation of poly
    (define (make-poly variable term-list)

    1. (cons variable term-list))

    (define (variable p) (car p))
    (define (term-list p) (cdr p))
    ;;[procedures same-variable? and variable? from section 2.3.2]

    ;; representation of terms and term lists
    ;;[procedures adjoin-term … coeff from text below]

    ;;(define (add-poly p1 p2) … )
    ;;[procedures used by add-poly]

    ;;(define (mul-poly p1 p2) … )
    ;;[procedures used by mul-poly]

    ;; interface to rest of the system
    (define (tag p) (attach-tag ‘polynomial p))
    (put ‘add ‘(polynomial polynomial)

    1. (lambda (p1 p2) (tag (add-poly p1 p2))))

    (put ‘mul ‘(polynomial polynomial)

    1. (lambda (p1 p2) (tag (mul-poly p1 p2))))

    (put ‘make ‘polynomial

    1. (lambda (var terms) (tag (make-poly var terms))))

    ‘done)

    (define (add-terms L1 L2)
    (cond ((empty-termlist? L1) L2)

    1. ((empty-termlist? L2) L1)
    2. (else
    3. (let ((t1 (first-term L1)) (t2 (first-term L2)))
    4. (cond ((> (order t1) (order t2))
    5. (adjoin-term
    6. t1 (add-terms (rest-terms L1) L2)))
    7. ((< (order t1) (order t2))
    8. (adjoin-term
    9. t2 (add-terms L1 (rest-terms L2))))
    10. (else
    11. (adjoin-term
    12. (make-term (order t1)
    13. (add (coeff t1) (coeff t2)))
    14. (add-terms (rest-terms L1)
    15. (rest-terms L2)))))))))

    (define (mul-terms L1 L2)
    (if (empty-termlist? L1)

    1. (the-empty-termlist)
    2. (add-terms (mul-term-by-all-terms (first-term L1) L2)
    3. (mul-terms (rest-terms L1) L2))))

    (define (mul-term-by-all-terms t1 L)
    (if (empty-termlist? L)

    1. (the-empty-termlist)
    2. (let ((t2 (first-term L)))
    3. (adjoin-term
    4. (make-term (+ (order t1) (order t2))
    5. (mul (coeff t1) (coeff t2)))
    6. (mul-term-by-all-terms t1 (rest-terms L))))))
  1. ;; Representing term lists
  2. (define (adjoin-term term term-list)
  3. (if (=zero? (coeff term))
  4. term-list
  5. (cons term term-list)))
  6. (define (the-empty-termlist) '())
  7. (define (first-term term-list) (car term-list))
  8. (define (rest-terms term-list) (cdr term-list))
  9. (define (empty-termlist? term-list) (null? term-list))
  10. (define (make-term order coeff) (list order coeff))
  11. (define (order term) (car term))
  12. (define (coeff term) (cadr term))
  13. ```
  • Exercise 2.87

    We installed =zero? package independently from each arithmetic package so far;
    however more aggregate the code in that package, harder it gets to maintain. So
    I’ve decided to distribute each =zero? procedure to relevant package. Here is
    that procedure for polynomial:

    1. (put '=zero? '(polynomial) (lambda (p) (empty-termlist? (term-list p))))

    In addition, I’ve implemented the equ? package to polynomial:

    1. ;; equ package
    2. (define (equ-poly? p1 p2)
    3. (and (same-variable? (variable p1) (variable p2))
    4. (equ-terms? (term-list p1) (term-list p2))))
    5. (define (equ-terms? L1 L2)
    6. (cond ((empty-termlist? L1) (empty-termlist? L2))
    7. ((empty-termlist? L2) false)
    8. (else
    9. (let ((t1 (first-term L1)) (t2 (first-term L2)))
    10. (and (= (order t1) (order t2))
    11. (equ? (coeff t1) (coeff t2))
    12. (equ-terms? (rest-terms L1)
    13. (rest-terms L2)))))))
  • Exercise 2.88

    Let we assume that we have generic neg procedure. Then we can extend
    polynomial package to include subtraction that is analogous to addition:

    1. ;; arithmetic operations on poly ( wrapper operation )
    2. (define (arith-poly term-op opname)
    3. (lambda (p1 p2)
    4. (if (same-variable? (variable p1) (variable p2))
    5. (make-poly (variable p1)
    6. (term-op (term-list p1)
    7. (term-list p2)))
    8. (error (string-append "Polys not in the same var -- " opname) (list p1 p2)))))
    9. (define (sub-terms L1 L2)
    10. (cond ((empty-termlist? L1) (neg-terms L2))
    11. ((empty-termlist? L2) L1)
    12. (else
    13. (let ((t1 (first-term L1)) (t2 (first-term L2)))
    14. (cond ((< (order t1) (order t2))
    15. (adjoin-term
    16. t2
    17. (sub-terms L1
    18. (rest-terms L2))))
    19. ((> (order t1) (order t2))
    20. (adjoin-term
    21. t1
    22. (sub-terms L2
    23. (rest-terms L1))))
    24. (else
    25. (adjoin-term
    26. (make-term (order t1)
    27. (sub (coeff t1) (coeff t2)))
    28. (sub-terms (rest-terms L1)
    29. (rest-terms L2)))))))))
    30. (define sub-poly
    31. (arith-poly sub-terms "SUB-POLY"))

    Then we should install neg operator to all arithmetic package:

    1. (define (install-scheme-number-package)
    2. ...
    3. (define subtypes '(real integer))
    4. (for-each
    5. (lambda (t1)
    6. ...
    7. (put 'neg (list t1) (lambda (e) (- e))))
    8. subtypes)
    9. 'done)
    10. (define (install-rational-package)
    11. ...
    12. (put 'neg '(rational)
    13. (lambda (r)
    14. (tag (make-rat (neg n)
    15. (neg d)))))
    16. 'done)
    17. (define (install-complex-package)
    18. ...
    19. (put 'neg '(complex) (lambda (c) (tag (make-from-real-imag ((neg (real-part c))
    20. (neg (imag-part c)))))))
    21. 'done)
    22. (define (install-polynomial-package)
    23. ...
    24. ;; neg package
    25. ;; (define (neg-terms L)
    26. ;; (map (lambda (term)
    27. ;; (make-term (order term)
    28. ;; (neg (coeff term))))
    29. ;; L))
    30. ;; We should not assume the representation detail about the lower level language.
    31. (define (neg-terms L)
    32. (if (empty-termlist? L)
    33. L
    34. (let ((t (first-term L)))
    35. (adjoin-term (make-term (order t) (neg (coeff t)))
    36. (neg-terms (rest-terms L))))))
    37. (define (neg-poly p)
    38. (make-poly (variable p)
    39. (neg-terms (term-list p))))
    40. ...
    41. 'done)

    Then the generic operator can be called by setting up

    1. (define (neg x) (apply-generic 'neg x))
  • Exercise 2.89

    As we build up the term list language upon term language, let we decompose term
    list language and term language from polynomial package:

    1. (define (install-term-package)
    2. ;; representation of term
    3. (define (make-term order coeff) (list order coeff))
    4. (define (order term) (car term))
    5. (define (coeff term) (cadr term))
    6. (define (tag t) (attach-tag 'term t))
    7. (put 'make 'term (lambda (order coeff) (tag (make-term order coeff))))
    8. (put 'order '(term) order)
    9. (put 'coeff '(term) coeff)
    10. )
    11. (define (install-sparse-termlist)
    12. ;; dependency
    13. (install-term-package)
    14. (define (coeff term) (apply-generic 'coeff term))
    15. ;; internal procedures
    16. (define (adjoin-term term term-list)
    17. (if (=zero? (coeff term))
    18. term-list
    19. (cons term term-list)))
    20. (define (the-empty-termlist) '())
    21. (define (first-term term-list) (car term-list))
    22. (define (rest-terms term-list) (cdr term-list))
    23. (define (empty-termlist? term-list) (null? term-list))
    24. ;; interface to rest of the system
    25. (define (tag L) (attach-tag 'sparse L))
    26. (put 'adjoin-term '(term sparse) (lambda (t L) (tag (adjoin-term (attach-tag 'term t) L))))
    27. (put 'first-term '(sparse) first-term)
    28. (put 'rest-terms '(sparse) (lambda (L) (tag (rest-terms L))))
    29. (put 'empty-termlist? '(sparse) empty-termlist?)
    30. (put 'the-empty-termlist 'sparse (lambda () (tag (the-empty-termlist))))
    31. )

    Analogous to sparse term list, we can implement dense term list package:

    1. (define (install-dense-termlist)
    2. ;; dependency
    3. (install-term-package)
    4. (define (make-term order coeff) ((get 'make 'term) order coeff))
    5. (define (order term) (apply-generic 'order term))
    6. (define (coeff term) (apply-generic 'coeff term))
    7. ;; internal procedures
    8. (define (the-empty-termlist) '())
    9. (define (empty-termlist? term-list) (null? term-list))
    10. (define (first-term term-list) (make-term (- (length term-list) 1) (car term-list)))
    11. (define (rest-terms term-list)
    12. (define (iter list)
    13. (cond ((empty-termlist? list) list)
    14. ((=zero? (car list)) (iter (cdr list)))
    15. (else list)))
    16. (iter (cdr term-list)))
    17. (define (adjoin-term term term-list)
    18. (cond ((=zero? (coeff term)) term-list)
    19. ((= (order term) (length term-list)) (cons (coeff term) term-list))
    20. (else (adjoin-term term (cons 0 term-list))))) ;we need consider general zero instead of 0
    21. ;; interface to rest of the system
    22. (define (tag L) (attach-tag 'dense L))
    23. (put 'adjoin-term '(term dense) (lambda (t L) (tag (adjoin-term (attach-tag 'term t) L))))
    24. (put 'first-term '(dense) first-term)
    25. (put 'rest-terms '(dense) (lambda (L) (tag (rest-terms L))))
    26. (put 'empty-termlist? '(dense) empty-termlist?)
    27. (put 'the-empty-termlist 'dense (lambda () (tag (the-empty-termlist))))
    28. )

    By this decomposing we can test each package seperately.

  • Exercise 2.90

    Here is the complete code that satisfy all the requirement:

    ```scheme
    (define (install-polynomial-package)
    ;; dependency
    (install-term-package)
    (install-dense-termlist)
    (install-sparse-termlist)
    ;; import from dependent package
    (define (empty-termlist? L) (apply-generic ‘empty-termlist? L))
    (define (adjoin-term t L) (apply-generic ‘adjoin-term t L))
    (define (first-term L) (apply-generic ‘first-term L))
    (define (rest-terms L) (apply-generic ‘rest-terms L))
    (define (the-empty-termlist) (sparse-empty-termlist))
    (define (sparse-empty-termlist) ((get ‘the-empty-termlist ‘sparse)))
    (define (dense-empty-termlist) ((get ‘the-empty-termlist ‘dense)))
    (define (make-term order coeff) ((get ‘make ‘term) order coeff))
    (define (order term) (apply-generic ‘order term))
    (define (coeff term) (apply-generic ‘coeff term))

    ;; internal procedures
    ;; representation of poly
    (define (make-poly variable term-list)

    1. (cons variable
    2. (->appropriate-rep term-list)))

    (define (variable p)

    1. (car p))

    (define (term-list p)

    1. (cdr p))

    ;; from section 2.3.2
    (define (variable? x) (symbol? x))
    (define (same-variable? v1 v2)

    1. (and (symbol? v1) (symbol? v2) (eq? v1 v2)))

    ;; arithmetic operations on poly ( wrapper operation )
    (define (arith-poly term-op opname)

    1. (lambda (p1 p2)
    2. (if (same-variable? (variable p1) (variable p2))
    3. (make-poly (variable p1)
    4. (term-op (term-list p1)
    5. (term-list p2)))
    6. (error (string-append "Polys not in the same var -- " opname) (list p1 p2)))))

    ;; arithmetic operations on termlist
    (define (add-terms L1 L2)

    1. (cond ((empty-termlist? L1) L2)
    2. ((empty-termlist? L2) L1)
    3. (else
    4. (let ((t1 (first-term L1)) (t2 (first-term L2)))
    5. (cond ((< (order t1) (order t2))
    6. (adjoin-term
    7. t2
    8. (add-terms L1
    9. (rest-terms L2))))
    10. ((> (order t1) (order t2))
    11. (adjoin-term
    12. t1
    13. (add-terms L2
    14. (rest-terms L1))))
    15. (else
    16. (adjoin-term
    17. (make-term (order t1)
    18. (add (coeff t1) (coeff t2)))
    19. (add-terms (rest-terms L1)
    20. (rest-terms L2)))))))))

    (define (sub-terms L1 L2)

    1. (cond ((empty-termlist? L1) (neg-terms L2))
    2. ((empty-termlist? L2) L1)
    3. (else
    4. (let ((t1 (first-term L1)) (t2 (first-term L2)))
    5. (cond ((< (order t1) (order t2))
    6. (adjoin-term
    7. t2
    8. (sub-terms L1
    9. (rest-terms L2))))
    10. ((> (order t1) (order t2))
    11. (adjoin-term
    12. t1
    13. (sub-terms L2
    14. (rest-terms L1))))
    15. (else
    16. (adjoin-term
    17. (make-term (order t1)
    18. (sub (coeff t1) (coeff t2)))
    19. (sub-terms (rest-terms L1)
    20. (rest-terms L2)))))))))

    (define (mul-terms L1 L2)

    1. (if (empty-termlist? L1)
    2. (the-empty-termlist)
    3. (add-terms (mul-term-by-all-terms (first-term L1) L2)
    4. (mul-terms (rest-terms L1) L2))))
  1. (define add-poly
  2. (arith-poly add-terms "ADD-POLY"))
  3. (define mul-poly
  4. (arith-poly mul-terms "MUL-POLY"))
  5. (define sub-poly
  6. (arith-poly sub-terms "SUB-POLY"))
  7. (define (mul-term-by-all-terms t1 L)
  8. (if (empty-termlist? L)
  9. (the-empty-termlist)
  10. (let ((t2 (first-term L)))
  11. (adjoin-term (make-term (+ (order t1) (order t2))
  12. (mul (coeff t1) (coeff t2)))
  13. (mul-term-by-all-terms t1 (rest-terms L))))))
  14. ;; equ package
  15. (define (equ-poly? p1 p2)
  16. (and (same-variable? (variable p1) (variable p2))
  17. (equ-terms? (term-list p1) (term-list p2))))
  18. (define (equ-terms? L1 L2)
  19. (cond ((empty-termlist? L1) (empty-termlist? L2))
  20. ((empty-termlist? L2) false)
  21. (else
  22. (let ((t1 (first-term L1)) (t2 (first-term L2)))
  23. (and (= (order t1) (order t2))
  24. (equ? (coeff t1) (coeff t2))
  25. (equ-terms? (rest-terms L1)
  26. (rest-terms L2)))))))
  27. ;; neg package
  28. (define (neg-terms L)
  29. (map (lambda (term)
  30. (make-term (order term)
  31. (neg (coeff term))))
  32. L))
  33. (define (neg-poly p)
  34. (make-poly (variable p)
  35. (neg-terms (term-list p))))
  36. ;; number of terms
  37. (define (number-of-terms L)
  38. (if (empty-termlist? L)
  39. 0
  40. (1+ (number-of-terms (rest-terms L)))))
  41. (define (->appropriate-rep L)
  42. (let ((ratio 0.5)) ;determine the ratio at which change the representation of term list
  43. (cond ((empty-termlist? L) L)
  44. ((< (/ (number-of-terms L) (order (first-term L)))
  45. ratio)
  46. (->sparse L))
  47. (else (->dense L)))))
  48. (define (->sparse L)
  49. (define (recur L)
  50. (if (empty-termlist? L)
  51. (sparse-empty-termlist)
  52. (adjoin-term (first-term L)
  53. (recur (rest-terms L)))))
  54. (if (eq? 'sparse (type-tag L))
  55. L
  56. (recur L)))
  57. (define (->dense L)
  58. (define (recur L)
  59. (if (empty-termlist? L)
  60. (dense-empty-termlist)
  61. (adjoin-term (first-term L)
  62. (recur (rest-terms L)))))
  63. (if (eq? 'dense (type-tag L))
  64. L
  65. (recur L)))
  66. ;; interface to rest of the system
  67. (define (tag p) (attach-tag 'polynomial p))
  68. (put 'add '(polynomial polynomial)
  69. (lambda (p1 p2) (tag (add-poly p1 p2))))
  70. (put 'mul '(polynomial polynomial)
  71. (lambda (p1 p2) (tag (mul-poly p1 p2))))
  72. (put 'sub '(polynomial polynomial)
  73. (lambda (p1 p2) (tag (sub-poly p1 p2))))
  74. (put 'make 'polynomial
  75. (lambda (var terms) (tag (make-poly var terms))))
  76. (put 'equ? '(polynomial polynomial) equ-poly?)
  77. (put '=zero? '(polynomial) (lambda (p) (empty-termlist? (term-list p))))
  78. (put 'neg '(polynomial) (lambda (p) (tag (neg-poly p))))
  79. 'done)
  80. ```
  81. Also we should check it works as expected:
  82. ```scheme
  83. (sub (make-polynomial 'x '(dense 1 0 0 0 0 0 0))
  84. (make-polynomial 'x '(sparse (term 5 1) (term 4 2) (term 1 1))))
  85. (add (make-polynomial 'x '(dense 1 0 0 0 0 0 0))
  86. (make-polynomial 'x '(sparse (term 5 1) (term 4 2) (term 1 1))))
  87. (mul (make-polynomial 'x '(dense 1 0 0 0 0 0 0))
  88. (make-polynomial 'x '(sparse (term 5 1) (term 4 2) (term 1 1))))
  89. ```
  90. As noted in the exercise's statement, unlike the complex number package, this
  91. require global change rather than local chang, i.e. we should change
  92. polynomial package to maintain the representation of term list efficiently. We
  93. maintain the appropriate (efficient) representation every time we make a new
  94. polynomial.
  • Note

    As we noticed that the term list has lots of analogy with list
    structure in scheme, we should find that similarity between polynomial with
    strictly typed list structure like in ML or Scala, i.e. we can think variable of
    polynomial is analogous to type of the list element. Then we can also guess the
    work of complier in the strictly typed language as they perform type inference
    using the operator table like in this section.

    We can find this analogy in foregoing discussion.

  • Exercise 2.91

    It is easy: If we follow the specified rules in the text, we can complete the
    code accordingly:

    1. (define (div-terms L1 L2)
    2. (if (empty-termlist? L1)
    3. (list (the-empty-termlist) (the-empty-termlist))
    4. (let ((t1 (first-term L1))
    5. (t2 (first-term L2)))
    6. (if (> (order t2) (order t1))
    7. (list (the-empty-termlist) L1)
    8. (let ((new-c (div (coeff t1) (coeff t2)))
    9. (new-o (- (order t1) (order t2))))
    10. (let ((rest-of-result
    11. (div-terms (sub-terms L1
    12. (mul-term-by-all-terms
    13. (make-term new-o new-c)
    14. L2))
    15. L2)))
    16. (cons (adjoin-term (make-term new-o new-c)
    17. (car rest-of-result))
    18. (cdr rest-of-result))))))))

    Let’s test:

    1. (div (make-polynomial 'x '(dense 1 0 0 0 0 0 0))
    2. (make-polynomial 'x '(sparse (term 5 1) (term 4 2) (term 1 1))))
    3. ;; (polynomial x dense 1 -2)
  • Exercise 2.92

    • The Problem

      Our system works well even if we add multivariate polynomial, i.e. univariate
      polynomial whose coefficients are also univariate polynomial, by the recursive
      type dispatch operation. However that works well only we set up appropriately:
      While if we add (x^{2}\left( y + 1 \right) + x \left( y^{2} + y \right) +
      y^{2} + y) with (x \left( y^{3} + y \right))
      using our system, as the nested polynomial agree with variable in each level, it
      results in (x^{2}\left( y + 1 \right) + x \left( y^{3} +y^{2} + 2y \right) +
      y^{2} + y);
      whereas (x^{2}\left( y + 1 \right) + x \left( y^{2} + y \right) +
      y^{2} + y) with (y^{3} \left( x \right) + y \left( x \right))
      doesn’t work at all even if the first and second formula are same in the
      algebraic context (by commutative law of multiplication).
      To deal with this problem, i.e. to produce “right” answer consistently even if
      we hand over the second formula, we consider to impose “canonical” order in
      polynomial’s variable; e.g. (x) has higher priority than (y) so that
      (x \left( y^{3} + y \right)) is canonical form but (y^{3} \left( x \right) + y
      \left( x \right)).

    • Designing

      Then how to deal with the addition and multiplication operation with this
      impose? So far, we set up the abstraction barrier between each level of nested
      formula so that let our generic operator deal with each type of formula
      appropriately. Doing that, we actually set up the recursive process on data
      type such as equ? package and so on.

      As we learned in chapter 1 and practicing that skill with exercises– the
      powerful “wishful thinking”– that recursive process are really good at handling
      complex process by capturing local rules by which the process evolve. However,
      this time we are about to break that barrier: We should access all the level of
      nested polynomial to expand and rearrange.

      To design our arithmetic operations in this scheme, we should grasp the gist of
      it by playing with multivariate polynomial arithmetic by hand calculation. I’ve
      done this with my digital paper. You should do this process with your own note
      or something. After that, we could conclude that if we assume we can expand all
      the terms and rearrange that to canonical form, then

      • addition is obtained by expanding and rearranging addend and augend all together,
      • multiplication can be decomposed to multiply each term of multiplicand with
        multiplier and then expanding and rearranging all together as we did addition.

      Also note that multiply each term of one argument to term list of other argument
      is analogous to previous mul-terms procedure; but here we should notice that
      this process is also a part of expansion of terms, as consequence if we can
      implement the rearranging process the addition and multiplication of
      multivariate polynomial is just by product of it.

      • Expansion

        First we need to specify what we want from expansion:

        • The output should be ordered list or not, if it should be arranged how?
        • Should the output collapse the same form of term appropriately– add terms?
        • How should it deal with constant term – term with zero order?
        • Should we make abstract data type for it? That is, should we make constructor
          and selectors for its result?

        After playing with the expansion and reconstruction to canonical form, I found
        that

        • it should ordered by priority of variable in decreasing order,
        • yes, it should collapse appropriately,
        • we should consider the zeroth term as place holder – just strip variable part,
        • it is plausible to have abstract data type but type tag.

        would make ourself easier to implement that.

        • Data Abstraction

          Here is the specification of the abstract data type:

          • (collapse-term-to-each term expansion-list): The motivation of this
            procedure is following: (x \left( x y + 2 \right) = x^{2}y + 2x), i.e.
            collapse-term-to-each '(x 1) '(((x 1) (y 1) (num 1)) ((num 2))) should
            return (((x 2) (y 1) (num 1)) ((x 1) (num 2)))
          • (add-term-list-to-expansion termlist expansion-list): This procedure take
            term list that represent each term in the expansion – (x^{2}y) in
            (x^{2}y + 2x) to adjoin in the expansion list, in which each term list
            ordered in decreasing priority.

          With those procedure, we can implement recursive process of expansion:

          1. (define (expand nested-poly)
          2. (cond (<base>)
          3. (else
          4. (let ((x (variable nested-poly))
          5. (L (term-list nested-poly)))
          6. (let ((t (first-term L))
          7. (tl (rest-terms L)))
          8. (append-expansion
          9. (collapse-term-to-each
          10. (make-var-term x (order t))
          11. (expand (coeff t)))
          12. (expand (make-polynomial x tl))))))))
          13. (define (append-expansion e1 e2)
          14. (if (empty-expansion? e1)
          15. e2
          16. (add-term-list-to-expansion (first-term-list e1)
          17. (append-expansion (rest-expansion e1) e2))))

          Or we could decompose expand procedure more granularly:

          1. (define (expand nested-poly)
          2. (cond (<base>)
          3. (else
          4. (expand-at-level (variable nested-poly) (term-list nested-poly)))))
          5. (define (expand-at-level x L)
          6. (cond (<base>)
          7. (else
          8. (let ((t (first-term L)) (tl (rest-terms L)))
          9. (append-expansion
          10. (collapse-term-to-each
          11. (make-var-term x (order t))
          12. (expand (coeff t)))
          13. (expand-at-level x tl))))))

          Now we have to consider the base case. Note that in the recursive process, we
          pass coefficient of term to expand; so the only base case of it is not
          polynomial case– number coefficient:

          1. (define (expand nested-poly)
          2. (cond ((not (polynomial? nested-poly))
          3. (add-term-list-to-expansion
          4. (make-term-list-with-num nested-poly)
          5. (the-empty-expansion)))
          6. (else
          7. (expand-at-level (variable nested-poly) (term-list nested-poly)))))

          For the expand-at-level, the base case is empty-termlist:

          1. (define (expand-at-level x L)
          2. (cond ((empty-termlist? L) (the-empty-expansion))
          3. (else
          4. (let ((t (first-term L)) (tl (rest-terms L)))
          5. (append-expansion
          6. (collapse-term-to-each (make-var-term x (order t))
          7. (expand (coeff t)))
          8. (expand-at-level x tl))))))

          Are we implement all the specification that we started with? Aren’t we missed
          the constant clause? Well, no since we can deal with that in
          collapse-term-to-each procedure.

          We have introduced bunch of procedures that we didn’t specified prior; this is
          due to the power of data abstraction: We can add any procedure that we want and
          defer the implementation later.

        • Implementation

          Now we turn into implementation of representation that we abstracted out.
          Let we code the straightforward parts first:

          ```scheme
          (define (collapse-term-to-each term expansion-list)
          (if (= 0 (order term)) ;constant clause

          1. expansion-list
          2. (map (lambda (term-list) (collapse-term term term-list)) expansion-list)))
  1. ;; predicate for polynomial
  2. (define (polynomial? p) (eq? 'polynomial (type-tag p)))
  3. ;; representation for expansion
  4. (define (the-empty-expansion) '())
  5. ;; predicate
  6. (define (empty-expansion? e) (null? e))
  7. ;; selectors
  8. (define (first-term-list e) (car e))
  9. (define (rest-expansion e) (cdr e))
  10. ;; constructor
  11. (define (add-term-list-to-expansion term-list expansion-list)
  12. (if (empty-expansion? expansion-list)
  13. (list term-list)
  14. (let ((L (first-term-list expansion-list)))
  15. (cond ((term-list<? L term-list) (cons term-list expansion-list))
  16. ((term-list<? term-list L)
  17. (cons L (add-term-list-to-expansion
  18. term-list
  19. (rest-expansion expansion-list))))
  20. (else (add-term-list-to-expansion
  21. (add-term-list term-list L)
  22. (rest-expansion expansion-list)))))))
  23. ```
  24. Then we should consider the term list representation:
  25. - Should we treat numeral term same as variable term?
  26. That is, we should coerce numeral to polynomial term?
  27. - Or should we deal numeral term explicitly other than variable term?
  28. This question leads different representation of term list. We could use multiple
  29. representation as we did complex number and the earlier term list
  30. representation. That means we can develop each representation independently. Let
  31. we first consider the latter representation:
  32. ```scheme
  33. (define (add-term-list L1 L2)
  34. (make-term-list (var-terms L1) (add (num-term L1) (num-term L2))))
  35. ;; term-list representation
  36. (define (the-empty-list-term) '())
  37. ;; predicate
  38. (define (empty-var-term-list? L) (null? L))
  39. (define (term-list<? L1 L2)
  40. (var-term-list<? (var-terms L1) (var-terms L2)))
  41. (define (var-term-list<? L1 L2)
  42. (cond ((empty-var-term-list? L1) (not (empty-var-term-list? L2)))
  43. ((empty-var-term-list? L2) false)
  44. (else
  45. (let ((t1 (head-term L1)) (t2 (head-term L2)))
  46. (cond ((variable<? (var t1) (var t2))
  47. true)
  48. ((variable<? (var t2) (var t1))
  49. false)
  50. ((< (order t1) (order t2)) true)
  51. ((> (order t1) (order t2)) false)
  52. (else
  53. (var-term-list<? (tail-terms L1) (tail-terms L2))))))))
  54. ;; constructor
  55. (define (make-term-list-with-num num-term) (make-term-list '() num-term))
  56. (define (make-term-list var-terms num-term) (cons var-terms num-term))
  57. (define (collapse-term var-term term-list)
  58. (make-term-list
  59. (collapse-var-term var-term (var-terms term-list))
  60. (num-term term-list)))
  61. (define (collapse-var-term term term-list)
  62. (cond ((empty-var-term-list? term-list) (cons term (the-empty-list-term)))
  63. (else
  64. (let ((t (head-term term-list)))
  65. (cond ((variable<? (var t) (var term))
  66. (cons term term-list))
  67. ((variable<? (var term) (var t))
  68. (cons t
  69. (collapse-var-term term
  70. (tail-terms term-list))))
  71. (else
  72. (cons (make-var-term (var term)
  73. (+ (order term)
  74. (order t)))
  75. (tail-terms term-list))))))))
  76. ```
  77. The rest is straightforward:
  78. ```scheme
  79. ;; term representation
  80. (define (make-var-term var order) (list var order))
  81. ;; predicate
  82. (define (variable<? v1 v2)
  83. (symbol>? v1 v2))
  84. ;; selectors
  85. (define (var term) (car term))
  86. (define (order term) (cadr term))
  87. ```
  88. Let's test it:
  89. ```scheme
  90. (expand '(polynomial x sparse
  91. (term 3 (polynomial y dense (polynomial x dense 5 0 0) 0))
  92. (term 1 (polynomial z dense 2 0))))
  93. ;Value: ((((x 5) (y 1)) . 5) (((x 1) (z 1)) . 2))
  94. ```
  95. Yeah! We did half of our task.
  96. - Rearrange
  97. We should reverse the expansion list to polynomial in canonical order. We could
  98. accomplish the task that is almost analogous to our first half. As we already
  99. constructed abstract data type in expansion, here we can focus on put together
  100. existing code to make sense. Here is the final code:
  101. ```scheme
  102. (define (rearrange expansion-list)
  103. (cond ((number-expansion-list? expansion-list)
  104. (get-number expansion-list)) ; => num
  105. (else
  106. (let ((x (var (highest-priority-term expansion-list))))
  107. (make-polynomial x (gather-termlist x expansion-list)))))) ; => polynomial
  108. (define (highest-priority-term es)
  109. (head-term (var-terms (first-term-list es))))
  110. (define (number-expansion-list? expansion-list)
  111. (and (empty-var-term-list?
  112. (var-terms (first-term-list expansion-list)))
  113. (empty-expansion? (rest-expansion expansion-list))))
  114. (define (get-number e) (num-term (first-term-list e)))
  115. ;; dependency
  116. (define (adjoin-term t L) (apply-generic 'adjoin-term t L))
  117. (define (the-empty-termlist) (sparse-empty-termlist))
  118. (define (sparse-empty-termlist) ((get 'the-empty-termlist 'sparse)))
  119. (define (make-term order coeff) ((get 'make 'term) order coeff))
  120. (define (gather-termlist v es) ;=> termlist
  121. (cond ((empty-expansion? es) (the-empty-termlist)) ;base case 1
  122. ((number-expansion-list? es) (adjoin-term (make-term 0 (rearrange es)) ;base case 2
  123. (the-empty-termlist)))
  124. (else
  125. (let ((t (highest-priority-term es)))
  126. (if (not (variable=? v (var t)))
  127. (adjoin-term (make-term 0 (rearrange es)) ;base case 3
  128. (the-empty-termlist))
  129. (let ((unarranged-result (gather t es)))
  130. (let ((gathered (car unarranged-result))
  131. (rest (cadr unarranged-result)))
  132. (adjoin-term (make-term (var-order t)
  133. (rearrange gathered))
  134. (gather-termlist v rest)))))))))
  135. (define (gather t es)
  136. (cond ((number-expansion-list? es)
  137. (list (the-empty-expansion) es))
  138. ((empty-expansion? es)
  139. (list (the-empty-expansion) (the-empty-expansion)))
  140. (else
  141. (let ((t1 (highest-priority-term es)))
  142. (if (or (not (term=? t t1)))
  143. (list (the-empty-expansion) es)
  144. (let ((result (gather t (rest-expansion es))))
  145. (list (adjoin-term-list-to-expansion
  146. (term-list-except-first-var
  147. (first-term-list es))
  148. (car result))
  149. (cadr result))))))))
  150. (define (term-list-except-first-var ts)
  151. (make-term-list (tail-terms (var-terms ts))
  152. (num-term ts)))
  153. (define (term=? t1 t2)
  154. (and (variable=? (var t1) (var t2))
  155. (= (order t1) (order t2))))
  156. ```
  157. Here we used the analogy with [Exercise 2.58](#org8d35cce) when we dealt with AST. Also we
  158. exploit the type analysis to design these procedure.
  159. Test!
  160. ```scheme
  161. (define test
  162. (expand '(polynomial x sparse
  163. (term 3 (polynomial y dense
  164. (polynomial y dense 5 0 0) 0))
  165. (term 1 (polynomial y dense
  166. (polynomial x dense 3 0 0) 0 0 0)))))
  167. ;Value: ((((x 3) (y 3)) . 8))
  168. (rearrange test)
  169. ;Value: (polynomial x dense (polynomial y dense 8 0 0 0) 0 0 0)
  170. (define test
  171. (expand '(polynomial y sparse
  172. (term 3 (polynomial y dense (polynomial y dense 5 0 0) 0))
  173. (term 1 (polynomial y dense (polynomial x dense 3 0 0) 0 0 0)))))
  174. (rearrange test)
  175. ;Value: (polynomial x dense (polynomial y sparse (term 4 3)) 0 (polynomial y sparse (term 6 5)))
  176. ```
  177. Yes!
  178. - Addition
  179. With these armamentarium, we can easily implement addition:
  180. ```scheme
  181. (define (mul-poly-add p1 p2)
  182. (rearrange (append-expansion (expand p1)
  183. (expand p2))))
  184. ```
  185. - Multiplication
  186. We need some auxiliary procedure that is analogous to `union` procedure in
  187. [previous section](#orgf0e39c3) with `collapse-term`:
  188. ```scheme
  189. (define (collapse-term-list L1 L2) ;provided that L1 L2 is not empty
  190. (make-term-list
  191. (collapse-var-term-list (var-terms L1)
  192. (var-terms L2))
  193. (mul (num-term L1)
  194. (num-term L2))))
  195. (define (collapse-var-term-list vs1 vs2)
  196. (cond ((empty-var-term-list? vs1) vs2)
  197. ((empty-var-term-list? vs2) vs1)
  198. (else
  199. (let ((t1 (head-term vs1))
  200. (t2 (head-term vs2)))
  201. (cond ((variable<? (var t1) (var t2))
  202. (adjoin-var-term
  203. t2
  204. (collapse-var-term-list vs1 (tail-terms vs2))))
  205. ((variable<? (var t2) (var t1))
  206. (adjoin-var-term
  207. t1
  208. (collapse-var-term-list (tail-terms vs1) vs2)))
  209. ((variable=? (var t1) (var t2))
  210. (adjoin-var-term (make-var-term (var t1)
  211. (+ (order t1)
  212. (order t2)))
  213. (collapse-var-term-list
  214. (tail-terms vs1)
  215. (tail-terms vs2)))))))))
  216. ```
  217. This procedure collapse two term list (multiply two term list); using the
  218. algorithm of `mul-term-by-all-terms` in our previous polynomial package, we can
  219. accomplish the multiplication:
  220. ```scheme
  221. (define (mul-poly-mul p1 p2)
  222. (rearrange
  223. (mul-expansion (expand p1) (expand p2))))
  224. (define (mul-expansion e1 e2)
  225. (if (empty-expansion? e1)
  226. (the-empty-expansion)
  227. (append-expansion (mul-term-list-by-all-term-lists
  228. (first-term-list e1) e2)
  229. (mul-expansion (rest-expansion e1) e2))))
  230. (define (mul-term-list-by-all-term-lists L1 e)
  231. (if (empty-expansion? e)
  232. (the-empty-expansion)
  233. (let ((L2 (first-term-list e)))
  234. (adjoin-term-list-to-expansion
  235. (collapse-term-list L1 L2)
  236. (mul-term-list-by-all-term-lists L1 (rest-expansion e))))))
  237. ```
  238. Which conclude our example.
  239. - Deploy
  240. All we have to do is wrap around what we built as pacakge that extends
  241. polynomial pacakge:
  242. ```scheme
  243. (define (install-multivariate-polynomial-package)
  244. *** What we built
  245. ;; interface to rest of system
  246. (define (tag p) (attach-tag 'polynomial p)) ;for reattach the tag for working with this package
  247. (put 'add '(polynomial polynomial) (lambda (p1 p2) (mul-poly-add (tag p1) (tag p2))))
  248. (put 'mul '(polynomial polynomial) (lambda (p1 p2) (mul-poly-mul (tag p1) (tag p2))))
  249. (put 'sub '(polynomial polynomial) (lambda (p1 p2) (mul-poly-add (tag p1) (neg (tag p2))))) ;exploit generic operator, neg
  250. ;; test fragment
  251. ;; (define test '(polynomial x sparse (term 3 (polynomial y dense (polynomial y dense 5 0 0) 0)) (term 1 (polynomial y dense (polynomial x dense 3 0 0) 0 0 0))))
  252. ;; (define test1 '(polynomial y sparse (term 3 (polynomial y dense (polynomial y dense 5 0 0) 0)) (term 1 (polynomial y dense (polynomial x dense 3 0 0) 0 0 0))))
  253. ;; (add test test1)
  254. ;; (sub test test1)
  255. ;; (mul test test1)
  256. )
  257. ```
  • Lecture 4B: Generic Operators

    In this section, 2.5, we built our system primarily relying on data-directed
    programming; using this scheme, we were able to inherit automatically the
    ability to compute multivariate polynomial (in limited sense, as we noted
    before) by just implementing univariate polynomial arithmetic system and
    integrate that in our generic arithmetic system.

    In this programming style, we get rid of the “manager” and automated with
    generic procedure called apply-generic. By doing that, we allowed to develop
    our own package without consulting with other package developer; we only needed
    to add appropriate procedure in operator table so that apply-generic look for
    and dispatch on type appropriately.

    So far, we are quite happy with our craft; however we start to realize there are
    some unfortunate things even in this genuine strategy: As noted in the lecture,
    the individual package procedures can freeze the higher level procedure by
    adding some procedure that is constrained in some fashion. This “unfortunate”
    due to the highly decentralized structure of our programming scheme; for
    instance we couldn’t decide where specific coercion procedure should be
    included: We end up with making up other kind operator table that contains
    coercion procedure and we had to modify apply-generic accordingly. Even with
    that, our coercion scheme is limited in that we had to supply explicitly how our
    arithmetic types related each other– tower. It’s a very poorly structured
    hierarchical system. An obvious example of the freezing generic operation by
    user defined structure is exercise 2.94; previously we defined “simplifying
    procedure”– drop – and integrated in our apply-generic. But as we (user)
    extends the rational arithmetic system to accommodate all the object that
    satisfy the rational arithmetic axioms (see below), the drop procedure, which
    is specific to number system, start to not work at all. That root from our
    crude handling for hierarchical structure with too immense flexibility that we
    can not tether.

    Further more, in following exercise, we are about to convert rational number to
    generic rational object, which encompass all the object that has structure like
    rational number– object that have numerator and denominator with satisfying
    rational arithmetic axioms. That allows complex numbered rational object and
    rational polynomials and so on, “automatically”; however obvious there is no
    “GCD” operators for these. One way to deal with this problem is to give up to
    reduce the numerator and denominator to its lowest term, which leads to our
    rational number system also to be not the conventional rational number, which we
    learned in elementary school.

    On the other hand, this generic approach also leads some insight about the
    abstract algebra: As polynomial has its own “GCD” like operation as its division
    has a lot of analogy with integer division, we can abstract out the object that
    we have considered in concrete manner in the more generic context, which can be
    used in another specific field that have not considered previously. The other
    method for this problem, as I hinted already, use the generic properties of
    rational object, that is, to formulate the axioms objects should satisfy to have
    rational type; also we have to impose that condition to our rational arithmetic
    package to ensure the made rational object can be supplied to any other
    arithmetic operations as expected.

    Finally, the alternative approach have been adopted in many modern programming
    language: OOP. In that approach we can solve the hierarchical structure
    elegantly using super type and sub type relation. And more, the strictly typed
    language exploit this paradigm to maximize the flexibility, which constrained by
    the compile time type checking. Also this scheme capture the ADT concept as
    special expression called class using that structure, we can achieve the guts
    of ADT more elegantly. Now days, it seems like people found this scheme as most
    practical one7.

  • Exercise 2.93

    We can do what text indicated straightforwardly. Then we can test the given
    code:

    1. (define p1 (make-polynomial 'x '(sparse (term 2 1) (term 0 1))))
    2. (define p2 (make-polynomial 'x '(sparse (term 3 1) (term 0 1))))
    3. (define rf (make-rational p2 p1))
    4. ;Value: (rational (polynomial x dense 1 0 0 1) polynomial x dense 1 0 1)
    5. (add rf rf)
    6. ;Value: (rational (polynomial x dense 2 0 2 2 0 2) polynomial x dense 1 0 2 0 1)
  • Exercise 2.94

    We can support “GCD” procedure in univariate polynomial version by adding
    following code to polynomial package:

    1. (define (gcd-terms a b)
    2. (if (empty-termlist? b)
    3. a
    4. (gcd-terms b (remainder-terms a b))))
    5. (define (remainder-terms a b)
    6. (cadr (div-terms a b)))
    7. (define gcd-poly
    8. (arith-poly gcd-terms "GCD-POLY"))
    9. (put 'gcd '(polynomial polynomial)
    10. (lambda (p1 p2) (tag (gcd-poly p1 p2))))

    And we also implement GCD for integer number in scheme number package:

    1. (define (gcd a b)
    2. (if (= b 0)
    3. a
    4. (gcd b (remainder a b))))
    5. (put 'gcd '(integer integer) gcd)

    Then rest is the add GCD procedure to our disposal:

    1. (define greatest-common-divisor (make-generic-op 'gcd))

    Make our rational arithmetic package to exploit this generic operator:

    1. (define (make-rat n d)
    2. (let ((g (greatest-common-divisor n d)))
    3. (cons (div n g) (div d g))))

    Then let we test this with given example:

    1. (define p1 (make-polynomial 'x '(sparse (term 4 1) (term 3 -1) (term 2 -2) (term 1 2))))
    2. (define p2 (make-polynomial 'x '(sparse (term 3 1) (term 1 -1))))
    3. (greatest-common-divisor p1 p2)

    That end up into infinite loop, which was due to the integer division in the
    generic operator div:
    due to the ill-defined div-terms; however if we stick
    our integer division operation in our generic division operation, we will end up
    with above situation when we involves non-integer division in div-terms. So
    for now let we prevent that circumstance by following discussion.

    1. *** In scheme number package
    2. (put 'div '(integer integer) quotient)

    This would leads to zero coefficient in div-terms if we try to non-integer
    division; in turn, mul-term-by-all-terms with that term is zero. As
    consequence it end up looping with the same L1 and L2 in the div-terms.

    This kind of error is hard to extract even with this relatively small project.
    I should think this situation as chance to try bisection debugging method as
    scheme’s debugger can not work in this situation since this program does not
    halt. This time, I’ve used display procedure to debug somewhat, but
    systematically. I’ve tried to reason about the code rather than to approach the
    bug with systematic using bisection search. If we encounter with next chance I
    should try out the smarter debugging method rather than frustrating with the
    behavior of program.

    After fixing the bug, we can get result:

    1. (greatest-common-divisor p1 p2)
    2. ;Value: (polynomial x dense -10 0)

    Is it correct? Let we calculate the greatest common divisor of p1 and p2 by
    hand:

    • p1 = (x^{4} - x^{3} - 2x^{2} + 2x) = $\left( x2 - 2 \right) \left( x2 -

    x \right) = \left( x2 -2 \right) x \left( x - 1 \right)$

    • p2 = (x^{3} - x) = (x \left( x + 1 \right) \left( x - 1 \right))

    So, GCD of p1 and p2 should be (x \left( x - 1 \right)) not (-10 x) like our
    program. Yeah! Another bug! As soon as I mentioned, it comes out! Let we try out
    the bisection search method to debug.

    1. Let we prepare test case, which we know what is the correct expected value at
      each step of computation. To do this in our GCD case, we use above test case
      for this; we should calculate each step that greatest-common-divisor
      process by hand.
    2. Then add the probe procedure, display in gcd-terms:

      1. (define (gcd-terms a b)
      2. (display a)
      3. (if (empty-termlist? b)
      4. a
      5. (gcd-terms b (remainder-terms a b))))

      It produces (dense 1 -1 -2 2 0)(dense 1 0 -1 0)(dense 1 3 0)(sparse (term 1 -10)).

    3. Compare with the expected value. By hand calculation, we expect to have
      (dense -1 3 0) for the remainder of p1 and p2; however we got (dense 1 3 0). This means our remainder-terms malfunctions. As this function just
      select the relevant part from div-terms, in turn, it means the bug in div-terms.
    4. As the remainder of div-terms produced by successive sub-terms, we can
      deduce that the divergence of the result from what we expected is due to
      sub-terms part. To inspect the value produced by successive subtraction we
      add probe code to div-terms:

      1. (define (div-terms L1 L2)
      2. (if (empty-termlist? L1)
      3. (list (the-empty-termlist) (the-empty-termlist))
      4. (let ((t1 (first-term L1))
      5. (t2 (first-term L2)))
      6. (if (> (order t2) (order t1))
      7. (list (the-empty-termlist) L1)
      8. (let ((new-c (div (coeff t1) (coeff t2)))
      9. (new-o (- (order t1) (order t2))))
      10. (let ((rest-of-result
      11. (div-terms (sub-terms L1
      12. (mul-term-by-all-terms
      13. (make-term new-o new-c)
      14. L2))
      15. L2)))
      16. (display L1)
      17. (list (adjoin-term (make-term new-o new-c)
      18. (car rest-of-result))
      19. (cadr rest-of-result))))))))

      Then remove the previous probe code in step 2. Let’s run this code with our
      test code; it produces (dense -1 1 -2 0)(dense 1 -1 -2 2 0)(dense 3 -1 0)(dense 1 0 -1 0)(dense 3 0)(dense 1 3 0). It get to be obvious that
      the subtraction is the reason for this erroneous behavior.

    5. We narrowed down the bug to sub-terms. We could run the bisection search
      until we end up single unit that compute just one step; but here we realized
      that the first clause in recursive process of sub-terms is ill-defined. We
      should code

      1. (define (sub-terms L1 L2)
      2. (cond ((empty-termlist? L1) (neg-terms L2))
      3. ((empty-termlist? L2) L1)
      4. (else
      5. (let ((t1 (first-term L1)) (t2 (first-term L2)))
      6. (cond ((< (order t1) (order t2))
      7. (adjoin-term
      8. (make-term (order t2)
      9. (neg (coeff t2)))
      10. (sub-terms L1
      11. (rest-terms L2))))
      12. ((> (order t1) (order t2))
      13. (adjoin-term
      14. t1
      15. (sub-terms (rest-terms L1)
      16. L2)))
      17. (else
      18. (adjoin-term
      19. (make-term (order t1)
      20. (sub (coeff t1) (coeff t2)))
      21. (sub-terms (rest-terms L1)
      22. (rest-terms L2)))))))))

      not

      1. (define (sub-terms L1 L2)
      2. (cond ((empty-termlist? L1) (neg-terms L2))
      3. ((empty-termlist? L2) L1)
      4. (else
      5. (let ((t1 (first-term L1)) (t2 (first-term L2)))
      6. (cond ((< (order t1) (order t2))
      7. (adjoin-term
      8. t2
      9. (sub-terms L1
      10. (rest-terms L2))))
      11. ((> (order t1) (order t2))
      12. (adjoin-term
      13. t1
      14. (sub-terms L2
      15. (rest-terms L1))))
      16. (else
      17. (adjoin-term
      18. (make-term (order t1)
      19. (sub (coeff t1) (coeff t2)))
      20. (sub-terms (rest-terms L1)
      21. (rest-terms L2)))))))))

      I’ve not fully considered the difference between sub-terms and add-terms
      when I defining sub-terms using analogy with add-terms.

      After this fixing, our test code barfage

      1. (greatest-common-divisor p1 p2)
      2. ;Value: (polynomial x dense -1 1 0)

      as we expected.

  • Exercise 2.95

    We can easily encode given formula to our polynomial expression:

    1. (define p1 '(polynomial x dense 1 -2 1))
    2. (define p2 '(polynomial x dense 11 0 7))
    3. (define p3 '(polynomial x dense 13 5))
    4. (define q1 (mul p1 p2))
    5. (define q2 (mul p1 p3))

    And then, to inspect what’s going to happen in the gcd-terms we insert the
    probe code as we did before:

    1. (define (gcd-terms a b)
    2. (newline)
    3. (display a)
    4. (if (empty-termlist? b)
    5. a
    6. (gcd-terms b (remainder-terms a b))))

    Then let’s run our code: (greatest-common-divisor q1 q2). And it produce
    following:

    1. (dense 11 -22 18 -14 7)
    2. (dense 13 -21 3 5)
    3. (sparse (term 2 8.627218934911243) (term 1 -17.254437869822485) (term 0 8.627218934911243))
    4. ;Value: (polynomial x dense 8.627218934911243 -17.254437869822485 8.627218934911243)

    Intuitively we expected that the output would be same as p1 but not; it is due
    to the non-integer operation– more precisely real division – in GCD
    computation. Fortunately the output agree with our intuition in that given
    output is differ in constant factor from our expectation; it was mainly due to
    our previous amender to prevent to fall in infinite loop in div-terms.

  • Exercise 2.96

    First, we can prove the fact involving the integerizing factor
    c1+O1-O2 using induction on O1 - O2. As we proved the fact that
    if we multiply dividend by integerizing factor then the resulting polynomial can
    be divided by divisor using the div-terms without introducing any fractions,
    we can now use that fact to fix our previous malfunction.

    • a.

      We can implement given task with the existing code8:

      1. (define (pseudoremainder-terms L1 L2)
      2. (if (empty-termlist? L1)
      3. (the-empty-termlist)
      4. (let ((t1 (first-term L1)) (t2 (first-term L2)))
      5. (if (> (order t2) (order t1))
      6. L1 ;do nothing
      7. (remainder-terms
      8. (mul-term-by-all-terms (make-term 0
      9. (expt (coeff t2)
      10. (+ 1
      11. (- (order t1)
      12. (order t2)))))
      13. L1)
      14. L2)))))

      Then we should modify gcd-terms as follow

      1. (define (gcd-terms a b)
      2. (if (empty-termlist? b)
      3. a
      4. (gcd-terms b (pseudoremainder-terms a b))))

      Let’s test:

      1. (greatest-common-divisor q1 q2)
      2. ;Value: (polynomial x dense 1458 -2916 1458)

      We progressed: It returns polynomial with integer coefficient

    • b.

      First, let we extract common factor of given term list:

      1. (define (map-terms f L) ;term-list => list
      2. (if (empty-termlist? L)
      3. '()
      4. (cons (f (first-term L))
      5. (map-terms f (rest-terms L)))))
      6. (define (common-factor L)
      7. (if (empty-termlist? L)
      8. (error "invalid input term list -- COMMON-FACTOR" L)
      9. (let ((coeffs (map-terms coeff L)))
      10. (fold-left (lambda (x y) (greatest-common-divisor x y))
      11. (car coeffs)
      12. (cdr coeffs)))))

      Using this, we can reduce term list to its lowest term:

      1. (define (map-terms-using nil cons f L)
      2. (define (construct L)
      3. (if (empty-termlist? L)
      4. nil
      5. (cons (f (first-term L))
      6. (construct (rest-terms L)))))
      7. (construct L))
      8. (define (map-terms-to-list f L) ;term-list => list
      9. (map-terms-using '() cons f L))
      10. (define (common-factor L)
      11. (if (empty-termlist? L)
      12. (error "invalid input term list -- COMMON-FACTOR" L)
      13. (let ((coeffs (map-terms-to-list coeff L)))
      14. (fold-left (lambda (x y) (greatest-common-divisor x y))
      15. (car coeffs)
      16. (cdr coeffs)))))
      17. (define (reduce-terms L)
      18. (let ((c (common-factor L)))
      19. (map-terms-using (the-empty-termlist) adjoin-term
      20. (lambda (term)
      21. (make-term (order term)
      22. (div (coeff term) c)))
      23. L)))

      Here, we generalized map-terms to produce any list like structure type.

      The rest:

      1. (define (gcd-terms a b)
      2. (if (empty-termlist? b)
      3. (reduce-terms a)
      4. (gcd-terms b (pseudoremainder-terms a b))))

      And it produce the expected output:

      1. (greatest-common-divisor q1 q2)
      2. ;Value: (polynomial x dense 1 -2 1)
  • Exercise 2.97

    • a.

      As the algorithm needed to implement already supplied by text book, all we need
      to do is just encode that algorithm using scheme. When you really understand the
      guts of specified algorithm, then you should easily implement that without trouble.

      Here is the result:

      1. (define (scalar-div-terms L c)
      2. (map-terms-using (the-empty-termlist) adjoin-term
      3. (lambda (term)
      4. (make-term (order term)
      5. (div (coeff term) c)))
      6. L))
      7. (define (reduce-terms n d)
      8. (if (or (empty-termlist? n) (empty-termlist? d)) ;do nothing
      9. (list n d)
      10. (let ((g (greatest-common-divisor n d)) ;step 1
      11. (O1 (max (order (first-term n))
      12. (order (first-term d)))))
      13. (let ((t2 (first-term g)))
      14. (let (c (expt (coeff t2) (1+ (- O1 (order t2)))))
      15. (let ((nn (div-terms (scalar-terms c n) g))
      16. (dd (div-terms (scalar-terms c d) g))) ;step 2
      17. (let ((common (gcd-terms
      18. (common-factor nn)
      19. (common-factor dd))))
      20. (list (scalar-div-terms nn common)
      21. (scalar-div-terms dd common))))))))) ;step 3
    • b.

      reduce-poly is straightforward:

      1. (define (reduce-poly p1 p2)
      2. (if (same-variable? (variable p1) (variable p2))
      3. (map (lambda (t) (make-poly (variable p1) t))
      4. (reduce-terms (term-list p1)
      5. (term-list p2)))
      6. (error "Polys not in the same var -- REDUCE-POLY" (list p1 p2))))

      If we have done with the interfacing with the system, the left is modify
      constructor of rational number:

      1. (define (numer x) (car x))
      2. (define (denom x) (cadr x))
      3. (define (make-rat n d)
      4. (reduce n d))

      Then our test code run as expected:

      1. (define p1 (make-polynomial 'x '(sparse (term 1 1) (term 0 1))))
      2. (define p2 (make-polynomial 'x '(sparse (term 3 1) (term 0 -1))))
      3. (define p3 (make-polynomial 'x '(sparse (term 1 1))))
      4. (define p4 (make-polynomial 'x '(sparse (term 2 1) (term 0 -1))))
      5. (define rf1 (make-rational p1 p2))
      6. ;Value: (rational (polynomial x dense -1 -1) (polynomial x dense -1 0 0 1))
      7. (define rf2 (make-rational p3 p4))
      8. ;Value: (rational (polynomial x dense -1 0) (polynomial x dense -1 0 1))
      9. (add rf1 rf2)
      10. ;Value: (rational (polynomial x dense -1 -2 -3 -1) (polynomial x dense -1 -1 0 1 1))

      but negated. It origin our crude gcd:

      1. (define (gcd a b)
      2. (if (= b 0)
      3. a
      4. (gcd b (remainder a b))))
      5. (gcd 5 -1)
      6. ;Value: -1

      To fix this unfortunate, we can just remove above code from scheme-number
      package: Use primitive gcd. This fix our problem:

      1. rf1
      2. ;Value: (rational (polynomial x dense 1 1) (polynomial x dense 1 0 0 -1))
      3. rf2
      4. ;Value: (rational (polynomial x dense 1 0) (polynomial x dense 1 0 -1))
      5. (add rf1 rf2)
      6. ;Value: (rational (polynomial x dense 1 2 3 1) (polynomial x dense 1 1 0 -1 -1))

Chapter 3 Modularity, Objects, and State

Assignment and Local State

We now introduce “state” in our programming language at length. By introduction
this concept, we get to model real world system more naturally and modularly
combining with message passing programming.

Local State Variables

To introduce the local state variable, we need to special expression that can
change the state of variable. More specifically, that expression has to update
value of the state variable. For this, scheme has special syntax called `(set!

)`.

  • Exercise 3.1

    Using the general scheme that combines local variable with set! expression, we
    can easily implement what we asked:

    1. (define (make-accumulator sum)
    2. (lambda (num)
    3. (set! sum (+ sum num))
    4. sum))
  • Exercise 3.2

    Let we first specify what the make-monitored should satisfy:

    • It should use the message passing programming to dispatch on input to
      implement the special symbol input.
    • It should have local variable, say count, to count the number of calls of its
      procedure argument f; it should use set! to change the value of its
      internal state variable.

    Then we can encode that specification directly:

    1. (define (make-monitored f)
    2. (let ((count 0))
    3. (define (dispatch m)
    4. (cond ((eq? m 'how-many-calls?) count)
    5. ((eq? m 'reset-count) (set! count 0))
    6. (else
    7. (set! count (1+ count))
    8. (f m))))))
  • Exercise 3.3

    We can do what we should do by slightly amending the procedure defined in text:

    1. (define (make-account balance password)
    2. (define (withdraw amount)
    3. (if (>= balance amount)
    4. (begin (set! balance (- balance amount))
    5. balance)
    6. "Insufficient funds"))
    7. (define (deposit amount)
    8. (set! balance (+ balance amount))
    9. balance)
    10. (define (dispatch pd m)
    11. (if (eq? pd password)
    12. (cond ((eq? m 'withdraw) withdraw)
    13. ((eq? m 'deposit) deposit)
    14. (else (error "Unknown request -- MAKE-ACCOUNT"
    15. m)))
    16. "Incorrect password")))

    Unfortunately this code doesn’t pass the test case ((acc 'some-other-password 'deposit) 50), which returns

    1. ;The object "Incorrect password" is not applicable.
    2. ;To continue, call RESTART with an option number:
    3. ; (RESTART 2) => Specify a procedure to use in its place.
    4. ; (RESTART 1) => Return to read-eval-print level 1.

    not what expected:

    1. "Incorrect password"

    We can fix this by using type analysis: we should return procedure not the
    primitive data:

    1. (define (make-account balance password)
    2. (define (withdraw amount)
    3. (if (>= balance amount)
    4. (begin (set! balance (- balance amount))
    5. balance)
    6. "Insufficient funds"))
    7. (define (deposit amount)
    8. (set! balance (+ balance amount))
    9. balance)
    10. (define (dispatch pd m)
    11. (if (eq? pd password)
    12. (cond ((eq? m 'withdraw) withdraw)
    13. ((eq? m 'deposit) deposit)
    14. (else (error "Unknown request -- MAKE-ACCOUNT"
    15. m)))
    16. (lambda (x) "Incorrect password")))
    17. dispatch)

    Then it pass all the test specified in text book.

  • Exercise 3.4

    This problem can be solved conjunction with the two previous exercises: 3.2 with 3.3.

    To implement what we requested, first let we code the test cases, which encodes
    all the specifications it should satisfy:

    1. ;; Test code
    2. ;;; consequetive call case
    3. (define acc (make-account 100 'secret-password))
    4. ((acc 'some-other-password 'deposit) 50)
    5. "Incorrect password"
    6. ((acc 'some-other-password 'deposit) 50)
    7. "Incorrect password"
    8. ((acc 'some-other-password 'deposit) 50)
    9. "Incorrect password"
    10. ((acc 'some-other-password 'deposit) 50)
    11. "Incorrect password"
    12. ((acc 'some-other-password 'deposit) 50)
    13. "Incorrect password"
    14. ((acc 'some-other-password 'deposit) 50)
    15. "Incorrect password"
    16. ((acc 'some-other-password 'deposit) 50)
    17. "call-the-cops"
    18. ;;; reset count
    19. ((acc 'secret-password 'withdraw) 40)
    20. 60
    21. ;;; interposed case
    22. ((acc 'some-other-password 'deposit) 50)
    23. "Incorrect password"
    24. ((acc 'some-other-password 'deposit) 50)
    25. "Incorrect password"
    26. ((acc 'some-other-password 'deposit) 50)
    27. "Incorrect password"
    28. ((acc 'secret-password 'withdraw) 40)
    29. 20
    30. ((acc 'some-other-password 'deposit) 50)
    31. "Incorrect password"
    32. ((acc 'some-other-password 'deposit) 50)
    33. "Incorrect password"
    34. ((acc 'some-other-password 'deposit) 50)
    35. "Incorrect password"
    36. ((acc 'some-other-password 'deposit) 50)
    37. "Incorrect password"
    38. ((acc 'some-other-password 'deposit) 50)
    39. "Incorrect password"
    40. ((acc 'some-other-password 'deposit) 50)
    41. "Incorrect password"
    42. ((acc 'some-other-password 'deposit) 50)
    43. "call-the-cops"

    Then modify the code of exercise 3.3:

    1. (define (make-account balance password)
    2. (define (withdraw amount)
    3. (if (>= balance amount)
    4. (begin (set! balance (- balance amount))
    5. balance)
    6. "Insufficient funds"))
    7. (define (deposit amount)
    8. (set! balance (+ balance amount))
    9. balance)
    10. (let ((n-incorrect 0))
    11. (define (dispatch pd m)
    12. (if (eq? pd password)
    13. (begin
    14. (set! n-incorrect 0) ;reset the counter
    15. (cond ((eq? m 'withdraw) withdraw)
    16. ((eq? m 'deposit) deposit)
    17. (else (error "Unknown request -- MAKE-ACCOUNT"
    18. m))))
    19. (lambda (x)
    20. (set! n-incorrect (1+ n-incorrect))
    21. (if (>= n-incorrect 7)
    22. "call-the-cops"
    23. "Incorrect password"))))
    24. dispatch))

    It passes all the test case.

The Benefits of Introducing Assignment

In the text book, it explane the benefits of introducing assignment expression
in our language taking Monte Carlo test as example. Without using assignment, we
should handle explicitly the state of each experiment that use random number;
it results into leaking the guts of state over the most higher level of
language: Monte Carlo test. No more general Monte Carlo test, only the specific
instance of that kind.

  • Exercise 3.5

    We can implement this exercise directly or we can do using what we learned so
    far, data abstraction:

    1. ;; top level
    2. (define (estimate-integral P rect trials)
    3. (* (rect 'area)
    4. (monte-carlo trials
    5. (lambda () (P (random-in-rect rect))))))
    6. ;; dependency
    7. (define (monte-carlo trials experiment)
    8. (define (iter trials-remaining trials-passed)
    9. (cond ((= trials-remaining 0)
    10. (/ trials-passed trials))
    11. ((experiment)
    12. (iter (- trials-remaining 1) (+ trials-passed 1)))
    13. (else
    14. (iter (- trials-remaining 1) trials-passed))))
    15. (iter trials 0))

    Here we captured the upper and lower bounds as rectangle; also we exploit that
    structure in generating random point in that rectangle. In this way, our program
    start to self-document what we are trying to do. With this, we can test this
    unit by

    1. ;;; test estimate-integral
    2. (define p1 (make-point -1 -1))
    3. (define p2 (make-point 1 1))
    4. (define r (make-rect p1 p2))
    5. (define (P pt) (<= (+ (square (x-coor pt))
    6. (square (y-coor pt)))
    7. 1))
    8. (estimate-integral P r 100)
    9. ;; it should converge to 3.141592...

    It’s true that we can not test this code until implement the lower level
    language but it is important to note that we can write the unit test.

    Then we can implement the other levels of language:

    1. ;; middle level
    2. (define (random-in-rect rect)
    3. (let ((points (list (bottom-left rect) (top-right rect))))
    4. (make-point (random-in-range
    5. (map exact->inexact (map x-coor points)))
    6. (random-in-range
    7. (map exact->inexact (map y-coor points))))))
    8. ;; dependency
    9. (define (random-in-range low high)
    10. (let ((range (- high low)))
    11. (+ low (random range))))
    12. ;;; test random-in-rect
    13. (define p1 (make-point 3 4))
    14. (define p2 (make-point 8 7))
    15. (define r (make-rect p1 p2))
    16. (random-in-rect r)
    17. ;; some float point in rect

    … and so on

    1. ;; low-middle level
    2. (define (make-rect bl tr)
    3. (define area
    4. (* (- (x-coor (tr) (bl)))
    5. (- (y-coor (tr) (bl)))))
    6. (define (dispatch m)
    7. (cond ((eq? m 'top-right) tr)
    8. ((eq? m 'bottom-left) bl)
    9. ((eq? m 'area) area)
    10. (else (error "Undefined request -- MAKE-RECT" m))))
    11. dispatch)
    12. (define (top-right rect) (rect 'top-right))
    13. (define (bottom-left rect) (rect 'bottom-left))
    14. ;;; test rect
    15. (define p1 (make-point 3 4))
    16. (define p2 (make-point 8 7))
    17. (define r (make-rect p1 p2))
    18. (r 'area)
    19. ;; 15
    20. (top-right rect)
    21. ;; (8 . 7)
    22. (bottom-left rect)
    23. ;; (3 . 4)
    24. (rect 'unknown-message)
    25. ;; Undefined request -- MAKE-RECT unknown-message
    26. ;; lowest level
    27. (define (make-point x y)
    28. (cons x y))
    29. (define (x-coor pt) (car pt))
    30. (define (y-coor pt) (cdr pt))
    31. ;;; test point
    32. (define a (make-point 5 3))
    33. (x-coor a)
    34. ;; 5
    35. (y-coor a)
    36. ;; 3

    Then we start the unit tests from the bottom. We failed in the rect level:

    1. (define r (make-rect p1 p2))
    2. ;The object (3 . 4) is not applicable.

    As soon as we inspect the code of make-rect, we realize what the problem was.
    We can fix this easily:

    1. *** in make-rect
    2. (define area
    3. (* (- (x-coor tr) (x-coor bl))
    4. (- (y-coor tr) (y-coor bl))))

    Run our test again: this time, we got by

    1. (top-right rect)
    2. ;Unbound variable: rect

    It was due to the test code itself: we defined r to be rectangle but we called
    rect. Fix and run. Then again we caught by random-in-rect:

    1. (random-in-rect r)
    2. ;The procedure #[compound-procedure 38 random-in-range] has been called with 1 argument; it requires exactly 2 arguments.

    We should have used apply in the random-in-rect procedure to apply
    random-in-range to argument list; after amending, it produce:

    1. (random-in-rect r)
    2. ;Value: (5.225704578484133 . 5.665006074331469)

    Looks fine.

    Then we move up the top level. It produce:

    1. (estimate-integral P r 100)
    2. ;Value: 76/25

    If we convert that value to inexact number:

    1. (exact->inexact 76/25)
    2. ;Value: 3.04

    To produce the floating-point number at first, we need to give the x and y
    coordinates with floating-point number. Let’s do more trials:

    1. (estimate-integral P r 1000)
    2. ;Value: 2.98
    3. (estimate-integral P r 10000)
    4. ;Value: 3.158
    5. (estimate-integral P r 100000)
    6. ;Value: 3.14068

    It really slowly converge to 3.141592…, π.

  • Exercise 3.6

    We can design what we requested using the message-passing programming strategy
    as we did in make-rect. First let we code the test:

    1. ;;; test rend
    2. (rand 'generate)
    3. <some-random-number>
    4. ((rand 'reset) 5)
    5. (rand 'generate)
    6. <specific-random-number>
    7. (rand 'generate)
    8. <some-other-random-number>
    9. ((rand 'reset) 5)
    10. (rand 'generate)
    11. <specific-random-number>

    Then implement:

    1. (define rand
    2. (let ((x random-init))
    3. (define (dispatch m)
    4. (cond ((eq? m 'generate)
    5. (set! x (rand-update x))
    6. x)
    7. ((eq? m 'reset)
    8. (lambda (new-x)
    9. (set! x new-x)))
    10. (else
    11. error "Unknown request -- RAND" m)))))

    As soon as I run the test:

    1. (rand 'generate)
    2. ;Unassigned variable: rand

    I should have to return dispatch as its return value.

    Let we run test again:

    1. (rand 'generate)
    2. ;Value: 88
    3. ((rand 'reset) 5)
    4. ;Value: 88
    5. (rand 'generate)
    6. ;Value: 34
    7. (rand 'generate)
    8. ;Value: 55
    9. ((rand 'reset) 5)
    10. ;Value: 55
    11. (rand 'generate)
    12. ;Value: 34

    It works as expected.

The Costs of Introducing Assignment

Allowing assignment expression in our language, introduce more profound
complication namely, what is the object and what is the sameness. It is more
alike philosophical concept. As noted in the text, to identify the sameness we
experiment by changing one object and observe the other; however in turns, to
define the “change” of one object we should first define “sameness.” As
consequence, we need a priori notion of sameness to identify whether the
objects that we are compare with is same.

  • Exercise 3.7

    Let we first code the test:

    1. ;;; test make-joint
    2. (define peter-acc (make-account 100 'open-sesame))
    3. (define paul-acc (make-joint peter-acc 'open-sesame 'rosebud))
    4. ((paul-acc 'rosebud 'withdraw) 50)
    5. ;; 50
    6. ((peter-acc 'open-sesame 'deposit) 30)
    7. ;; 80
    8. (define opaque (make-joint paul-acc 'open-sesame 'this-should-not-work?))

    The last line is undefined because our specification does not regulate this
    ambiguity. This looseness leads us to choose the implementation detail from
    several possibilities:

    • Let the last line of the test be valid: This would imply to joint account,
      all we need to do is add new password to password list and then make change
      in make-account to check whether the password is correct use password
      list instead single password.
    • Let the last line of the test be invalid: This means that only the balance
      variable should be shared between the joint accounts, which also indicates
      that

      1. ((peter-acc 'rosebug 'deposit) 30)

      should not work.

    Let we explore the two possibilities by implementing both version.

    • First version:

      1. (define (make-account1 balance password)
      2. (define (withdraw amount)
      3. (if (>= balance amount)
      4. (begin (set! balance (- balance amount))
      5. balance)
      6. "Insufficient funds"))
      7. (define (deposit amount)
      8. (set! balance (+ balance amount))
      9. balance)
      10. (let ((n-incorrect 0)
      11. (pw-list (list password)))
      12. (define (dispatch pd m)
      13. (if (mem? pd pw-list)
      14. (begin
      15. (set! n-incorrect 0) ;reset the counter
      16. (cond ((eq? m 'withdraw) withdraw)
      17. ((eq? m 'deposit) deposit)
      18. ((eq? m 'joint) (lambda (new-pw)
      19. (set! pw-list (cons new-pw pw-list))
      20. dispatch))
      21. (else (error "Unknown request -- MAKE-ACCOUNT1"
      22. m))))
      23. (lambda (x)
      24. (set! n-incorrect (1+ n-incorrect))
      25. (if (>= n-incorrect 7)
      26. "call-the-cops"
      27. "Incorrect password"))))
      28. dispatch))
      29. (define (make-joint acc old-pw new-pw)
      30. ((acc old-pw 'joint) new-pw))
      31. ;; general helper function
      32. (define (mem? el S)
      33. (if (null? S) false
      34. (or (eq? el (car S))
      35. (mem? el (cdr S)))))

      Then let’s test:

      1. (define peter-acc (make-account1 100 'open-sesame))
      2. ;Value: peter-acc
      3. (define paul-acc (make-joint peter-acc 'open-sesame 'rosebud))
      4. ;Value: paul-acc
      5. ((paul-acc 'rosebud 'withdraw) 50)
      6. ;Value: 50
      7. ((peter-acc 'open-sesame 'deposit) 30)
      8. ;Value: 80
      9. (define opaque (make-joint paul-acc 'open-sesame 'this-should-not-work?))
      10. ;Value: opaque
      11. ((opaque 'this-should-not-work? 'withdraw) 80)
      12. ;Value: 0
    • For the second version, as it is more tricky than the first one, first we
      should specify what we want. We want that the return account from make-joint
      should not share the password state variable with the account, with which
      make-joint called; yet want to share balance state variable among the
      two account. To accomplish this, we should have make-account with the
      following properties:

      1. We make object with unprotected account.
      2. Given that instance, we can convert that account to protected account with
        password.

      By restructuring make-account like this, we can easily implement the
      make-joint:

      1. (define (make-account balance)
      2. (define (withdraw amount)
      3. (if (>= balance amount)
      4. (begin (set! balance (- balance amount))
      5. balance)
      6. "Insufficient funds"))
      7. (define (deposit amount)
      8. (set! balance (+ balance amount))
      9. balance)
      10. (define (make-protected password)
      11. (let ((n-incorrect 0))
      12. (define (protected-dispatch pd m)
      13. (if (eq? pd password)
      14. (begin
      15. (set! n-incorrect 0) ;reset the counter
      16. (cond ((eq? m 'withdraw) withdraw)
      17. ((eq? m 'deposit) deposit)
      18. ((eq? m 'joint) (lambda (new-pw)
      19. ((dispatch 'make-protected) new-pw)))
      20. (else (error "Unknown request -- MAKE-PROTECTED"
      21. m))))
      22. (lambda (x)
      23. (set! n-incorrect (1+ n-incorrect))
      24. (if (>= n-incorrect 7)
      25. "call-the-cops"
      26. "Incorrect password"))))
      27. protected-dispatch))
      28. (define (dispatch m)
      29. (cond ((eq? m 'withdraw) withdraw)
      30. ((eq? m 'deposit) deposit)
      31. ((eq? m 'make-protected) make-protected)
      32. (else (error "Unknown request -- MAKE-ACCOUNT"
      33. m))))
      34. dispatch)
      35. (define (make-account2 balance password)
      36. (((make-account balance) 'make-protected) password))

      And here is the test:

      1. (define peter-acc (make-account2 100 'open-sesame))
      2. ;Value: peter-acc
      3. (define paul-acc (make-joint peter-acc 'open-sesame 'rosebud))
      4. ;Value: paul-acc
      5. ((paul-acc 'rosebud 'withdraw) 50)
      6. ;Value: 50
      7. ((peter-acc 'open-sesame 'deposit) 30)
      8. ;Value: 80
      9. (define opaque (make-joint paul-acc 'open-sesame 'this-should-not-work?))
      10. ;Value: opaque
      11. opaque
      12. ;Value: "Incorrect password"

    From this exercise, we can think procedure as an object in that notion of modern
    programming language. Note that in second version of make-account, we used
    procedure dispatch to call the parent object or the self in the modern
    programming scheme.

  • Exercise 3.8

    Note that to implement such f

    • We should use assignment expression otherwise it doesn’t depend on the
      evaluation order.
    • As the arguments of + should be number and the procedure + is referential
      transparent, the specification can be divided as pure imperative part and pure
      functional part; that is, we can think the evaluation order left to right as
      imperative part:

      1. evaluate (f 0) => a
      2. evaluate (f 1) => b

      with functional part:

      1. (+ a b) = 0

    Here is the sample implementation such f:

    1. (define f
    2. (let ((x 0))
    3. (lambda (n)
    4. (if (= n 0)
    5. x
    6. (begin (set! x n) 0)))))

    Or more imperative-oriented version:

    1. (define f
    2. (let ((x 0))
    3. (lambda (n)
    4. (let ((temp x))
    5. (begin (set! x n)
    6. temp)))))

    Then we can test the interpreter:

    1. (+ (f 0) (f 1))
    2. ;Value: 1
    3. *** reevaluate f
    4. (+ (f 1) (f 0))
    5. ;Value: 0

    So, our interpreter evaluate augend first.

Modeling with Mutable Data

Mutable List Structure

  • Exercise 3.16

    1. (define (count-pairs x)
    2. (if (not (pair? x))
    3. 0
    4. (+ (count-pairs (car x))
    5. (count-pairs (cdr x)))))
    6. (define list1 (list 1 2 3))
    7. (define list2
    8. (let ((tList (list 1 2 3)))
    9. (let ((tPointer (cdr tList)))
    10. (set-car! tPointer (cdr tPointer))
    11. tList)))
    12. (define list3
    13. (let ((tList (list 1 2 3)))
    14. (let ((tPointer (cdr tList)))
    15. (set-car! tPointer (cdr tPointer))
    16. (set-car! tList (cdr tList))
    17. tList)))
    18. (define list4
    19. (let ((tList (list 1 2 3)))
    20. (set-car! tList tList)))

    These list are all made up of exactly three pairs; but count-pairs returns
    differently:

    1. (count-pairs list1)
    2. ;Value: 3
    3. (count-pairs list2)
    4. ;Value: 4
    5. (count-pairs list3)
    6. ;Value: 7
    7. (count-pairs list4)
    8. ;Aborting!: maximum recursion depth exceeded
  • Exercise 3.17

    Then our version should solve the problem Ben Bitdiddle encountered.
    We can implement either of the paradigm, functional or imperative.
    Functional:

    1. (define (count-pairs x)
    2. (define (without-loop x visited)
    3. (if (or (mem? x visited) (not (pair? x)))
    4. 0
    5. (let ((new-visited (cons x visited)))
    6. (+ (without-loop (car x) new-visited)
    7. (without-loop (cdr x) new-visited)
    8. 1))))
    9. (without-loop x '()))

    Unfortunately, this won’t work:

    1. (count-pairs1 list1)
    2. ;Value: 3
    3. (count-pairs1 list2)
    4. ;Value: 4
    5. (count-pairs1 list3)
    6. ;Value: 7
    7. (count-pairs1 list4)
    8. ;Value: 3

    The problem is that in the recursive branch, each of it doesn’t share the
    visited argument. We can fix it by using local variable with assignment:

    1. (define (count-pairs2 x)
    2. (define recorded
    3. (let ((visited '()))
    4. (lambda (x)
    5. (if (or (mem? x visited)
    6. (not (pair? x)))
    7. 0
    8. (begin (set! visited (cons x visited))
    9. (+ (recorded (car x))
    10. (recorded (cdr x))
    11. 1))))))
    12. (recorded x))

    As expected, it solved the problem:

    1. (count-pairs2 list1)
    2. ;Value: 3
    3. (count-pairs2 list2)
    4. ;Value: 3
    5. (count-pairs2 list3)
    6. ;Value: 3
    7. (count-pairs2 list4)
    8. ;Value: 3

    Let we revise our functional version. To fix that, we should traverse the tree
    structure like depth first search; we one of the branch should wait until the
    other branch terminate its search. We should access the visited argument from
    returned value of one branch to propagate that argument to other branch. By
    returning tuple that contains visited with the counter, we can do the right thing:

    1. (define (count-pairs1 x)
    2. (define (without-loop x visited)
    3. (if (or (mem? x visited) (not (pair? x)))
    4. (list 0 visited)
    5. (let ((result-of-one
    6. (without-loop (cdr x) (cons x visited))))
    7. (let ((result-of-the-other
    8. (without-loop (car x) (cadr result-of-one))))
    9. (list (+ (car result-of-one)
    10. (car result-of-the-other)
    11. 1)
    12. (cadr result-of-the-other))))))
    13. (car (without-loop x '())))
    1. (count-pairs1 list1)
    2. ;Value: 3
    3. (count-pairs1 list2)
    4. ;Value: 3
    5. (count-pairs1 list3)
    6. ;Value: 3
    7. (count-pairs1 list4)
    8. ;Value: 3

    Implicitly, we used the induction on depth of x to construct this algorithm.

  • Exercise 3.18

    This algorithm should look like that of previous exercise. Let we first code the
    test:

    1. (define (make-cycle x)
    2. (set-cdr! (last-pair x) x)
    3. x)
    4. (define (last-pair x)
    5. (if (null? (cdr x))
    6. x
    7. (last-pair (cdr x))))
    8. (define list5 (make-cycle (list 1 2 3 4)))
    9. (define list6 (append '(a b c d) list5))
    10. (cycle? list1)
    11. #f
    12. (cycle? list2)
    13. #f
    14. (cycle? list3)
    15. #f
    16. (cycle? list4)
    17. #f
    18. (cycle? list5)
    19. #t
    20. (cycle? list6)
    21. #t
    22. (cycle? list7)
    23. #t

    Here is the code that exploits local state variable:

    1. (define (cycle? x)
    2. (define iter
    3. (let ((visited '()))
    4. (lambda (x)
    5. (cond ((null? x) false)
    6. ((mem? x visited) true)
    7. (else
    8. (set! visited (cons x visited))
    9. (iter (cdr x)))))))
    10. (iter x))

    … And test:

    1. (cycle? list1)
    2. ;Value: #f
    3. (cycle? list2)
    4. ;Value: #f
    5. (cycle? list3)
    6. ;Value: #f
    7. (cycle? list4)
    8. ;Value: #f
    9. (cycle? list5)
    10. ;Value: #t
    11. (cycle? list6)
    12. ;Value: #t
    13. (cycle? list7)
    14. ;Value: #t

    As we wrote our test to be as exhaustive as possible, we can be quite confidence
    about our program: We tested the extremum case such as list7 with normal case
    list6 and list5.

  • Exercise 3.19

    The keyword is constant amount of space; it implies we should code our code
    iterative. And also since we operate on list, we need to fully understand what
    the properties list structure possesses. If we focus on the convention that we
    traverse list structure one way only– cdr ing down, we could come up with the
    “very clever” idea as noted in text book:

    1. Send one person to stride one step further by cdr in each iteration where
    2. Send the other one until the place where the first person stand checking
      whether the each pointer is same as of first person.
    3. If it is, and if the two pointer located differently in examining list, then
      it is cycled list.
    4. otherwise second person stride one step further.
    5. If the second one reach the first one without trouble then first one stride
      one step further.
    6. Loop from 2 to 5 until the first one reach nil pointer. If it reaches nil
      pointer then return false.

    As this algorithm inherently evolve as iterative process, we got the algorithm
    that we wanted. To implement the check algorithm that whether two pointer is
    same but in different place, we could assign each pointer to number, namely the
    step needed to get reached to that place. However, although it is subtle, this
    strategy doesn’t assure this assigned number doesn’t grow in space: It would be
    consume more space when the step get huge number. To cope with this situation we
    can exploit the observation that, as our program return as soon as it found the
    first cycle point, the different place but same pointer should have different
    previous place where visited just before the current place.

    Here is the code:

    1. (define (cycle1? x)
    2. (define first-man
    3. (let ((prev '()))
    4. (lambda (current)
    5. (define second-man
    6. (let ((prev2 '()))
    7. (lambda (current2)
    8. (if (eq? current current2)
    9. (eq? prev prev2)
    10. (begin (set! prev2 current2)
    11. (second-man (cdr current2)))))))
    12. (cond ((null? current) false)
    13. ((not (second-man x)) true)
    14. (else
    15. (set! prev current)
    16. (first-man (cdr current)))))))
    17. (first-man x))

    And the test:

    1. (cycle1? list1)
    2. ;Value: #f
    3. (cycle1? list2)
    4. ;Value: #f
    5. (cycle1? list3)
    6. ;Value: #f
    7. (cycle1? list4)
    8. ;Value: #f
    9. (cycle1? list5)
    10. ;Value: #t
    11. (cycle1? list6)
    12. ;Value: #t
    13. (cycle1? list7)
    14. ;Value: #t

    Or functional version:

    1. (define (cycle2? x)
    2. (define (first-man prev current)
    3. (define (second-man prev2 current2)
    4. (if (eq? current current2)
    5. (eq? prev prev2)
    6. (second-man current2 (cdr current2))))
    7. (cond ((null? current) false)
    8. ((not (second-man '() x)) true)
    9. (else
    10. (first-man current (cdr current)))))
    11. (first-man '() x))

    We could use less define notation:

    1. (define (cycle3? x)
    2. (let ((first-man
    3. (lambda (prev current)
    4. (let ((second-man
    5. (lambda (prev2 current2)
    6. (if (eq? current current2)
    7. (eq? prev prev2)
    8. (second-man current2 (cdr current2))))))
    9. (cond ((null? current) false)
    10. ((not (second-man '() x)) true)
    11. (else
    12. (first-man current (cdr current))))))))
    13. (first-man '() x)))

    Unfortunately, this won’t work:

    1. (cycle3? list7)
    2. ;Unbound variable: first-man

    To understand why, we can desugar (let ((<var> <val>)) body) expression as
    ((lambda (<var>) body) <val>): The lambda expression in the first let
    notation, there is no way to reference first-man in the body of lambda
    expression of right-hand side; we need define to abstract this.

  • Lecture 5A: Assignment, State, and Side-effects

    I’ve got quite intrigued by that if we can replace all the set! expression
    with define ? (well, we supposed to not to do)

    Let’s experiment:

    1. (define t 1)
    2. ;Value: t
    3. (define t (+ t 1))
    4. ;Value: t
    5. t
    6. ;Value: 2

    Seems like we could replace the use of set!; but:

    1. (define test
    2. (let ((t 1))
    3. (define t (+ t 1))
    4. t))

    This definition won’t work:

    1. (define test
    2. (let ((t 1))
    3. (define t (+ t 1))
    4. t))
    5. ;Unassigned variable: t

    Do more experiment to grasp what’s going on here:

    1. (define test
    2. (let ((t 1))
    3. (define t1 (+ t 1))
    4. (define t1 (+ t1 1))
    5. t))
    6. ;duplicate internal definitions for (#[uninterned-symbol 38 t1]) in |#[let-procedure]|

    From these sequence of experiments, we can deduce that the effect of define,
    which is similar with set!, is only allowed to REPL; in other words, only in
    global environment. Within the evaluation model we learned so far, we can not
    understand fully the behavior of define.

    In lecture, professor start to define what means by the word “bound”:

    We say that a variable, V, is “bound in an expression”, E, if the meaning of E
    is unchanged by the uniform replacement of a variable, W, not occurring in E,
    for every occurrence of V in E.

    And also “free variable”:

    We say that a variable, V, is “free in and expression”, E, if the meaning of E
    is changed by the uniform replacement of a variable, W, not occurring in E, for
    every occurrence of V in E.

    By consequence of this, we have concept named “scope”:

    If x is a bound variable in E then there is a lambda expression where it is
    bound. We call the list of formal parameters of the lambda expression the “bound
    variable list” and we say that the lambda expression “binds” the variables
    “declared” in its bound variable list. In addition, those parts of the
    expression where a variable has a value defined by the lambda expression which
    binds it is called the “scope” of the variable.

    The evaluation rule 1:

    A procedure object is applied to a set of arguments by constructing a frame,
    binding the formal parameters of the procedure to the actual arguments of the
    call, and then evaluating the body of the procedure in the context of the new
    environment constructed. The new fram has as its enclosing environment the
    environment part of the procedure object being applied.

    The evaluation rule 2:

    A lambda-expression is evaluated relative to a given environment as follows: A
    new procedure object is formed, combining the text (code) of the lambda
    expression with a pointer to the environment of evaluation.

    The terminologis about actions and identity:

    We say that an action, A, had an effect on an object, X, (or equivalently, that
    X was changed by A) if some property, P, which was true of X before A became
    false of X after A.

    We say that two objects, X and Y, are the same if any action which has an effect
    on X has the same effect on Y.

    Professor end the lecture with following quote:

    Mr. Gilbert and Sullivan said,

    Things are seldom what they seem,
    Skim milk masquerades as cream…

    He wanted to implies, I guess, the stream concept that would come up with following lecture;
    if we consider the meaning of quote in this specific context but it will convolve a lot more
    profound meaning I think.

Representing Queues

As we do usually, we build our data abstraction for our queue implementation:

  • a constructor: (make-queue) returns an empty queue.
  • two selectors: - (empty-queue? <queue>) tests if the queue is empty.
    • (front-queue <queue>) returns the object at the front of the queue,
      signaling an error if the queue is empty; it does not modify the queue.
  • two mutators: - (insert-queue! <queue> <item>) inserts the item at the front of the
    1. queue and returns the modified queue as its value.
    • (delete-queue! <queue>) removes the item at the front of the queue and
      returns the modified queue as its value, signaling an error if the queue is
      empty before the deletion.

For the implementation restriction, all the operation should require Θ (1) steps.

In text book, they install additional layer, between manipulating queue
representation and implementing queue representation using list structure:

  1. ;;; wrapping around the queue representation
  2. (define (front-ptr queue) (car queue))
  3. (define (rear-ptr queue) (cdr queue))
  4. (define (set-front-ptr! queue item) (set-car! queue item))
  5. (define (set-rear-ptr! queue item) (set-cdr! queue item))
  6. ;;; selector -- predicate
  7. (define (empty-queue? queue) (null? (front-ptr queue)))
  8. ;;; constructor
  9. (define (make-queue) (cons '() '()))
  10. ;;; selector -- first element
  11. (define (front-queue queue)
  12. (if (empty-queue? queue)
  13. (error "FRONT called with an empty queue" queue)
  14. (car (front-ptr queue))))
  15. ;;; mutator -- insert item
  16. (define (insert-queue! queue item)
  17. (let ((new-pair (cons item '())))
  18. (cond ((empty-queue? queue)
  19. (set-front-ptr! queue new-pair)
  20. (set-rear-ptr! queue new-pair)
  21. queue)
  22. (else
  23. (set-cdr! (rear-ptr queue)
  24. new-pair)
  25. (set-rear-ptr! queue new-pair)
  26. queue))))
  27. ;;; mutator -- delete item
  28. (define (delete-queue! queue)
  29. (cond ((empty-queue? queue)
  30. (error "DELETE! called with an empty queue" queue))
  31. (else
  32. (set-front-ptr! queue (cdr (front-ptr queue)))
  33. queue)))
  • Exercise 3.21

    Eva Lu Ator denotes that it is just reachable from two different pointer not
    that insert-queue inserts item twice; recall the exercise 3.16, where all the
    list has exactly three pairs in it but represented differently. (in both Ben
    Bitdiddle’s count-pairs and Lisp printer)

    Here, to understand how the Lisp printer works, we implement the Lisp printer.
    To implement that, we use nested induction – induction on depth of expression
    and within that, we run induction on width of expression. To deduce the
    specification of Lisp printer, we play with Lisp interpreter for a while:

    1. (cons 1 2)
    2. ;Value: (1 . 2)
    3. (list 1 2)
    4. ;Value: (1 2)
    5. (cons 1 (cons 2 3))
    6. ;Value: (1 2 . 3)
    7. (cons (cons 1 2) 3)
    8. ;Value: ((1 . 2) . 3)
    9. (cons 1 (cons 2 '()))
    10. ;Value: (1 2)

    And here is the implementation:

    1. (define (print exp)
    2. (cond ((pair? exp) (print-exp exp))
    3. (else ;not compound
    4. (display exp))))
    5. (define (print-exp exp)
    6. (define (iter exp)
    7. (cond ((null? exp))
    8. ((pair? exp)
    9. (display " ")
    10. (print (car exp))
    11. (iter (cdr exp)))
    12. (else
    13. (display " . ")
    14. (print exp))))
    15. (display "(")
    16. (print (car exp))
    17. (iter (cdr exp))
    18. (display ")"))

    Here is the test:

    1. (print (cons 1 2))
    2. (1 . 2)
    3. ;Unspecified return value
    4. (print (list 1 2))
    5. (1 2)
    6. ;Unspecified return value
    7. (print (cons 1 (cons 2 3)))
    8. (1 2 . 3)
    9. ;Unspecified return value
    10. (print (cons (cons 1 2) 3))
    11. ((1 . 2) . 3)
    12. ;Unspecified return value
    13. (print (cons 1 (cons 2 '())))
    14. (1 2)
    15. ;Unspecified return value

    Then here is the examples for what the Ben Bitdiddle misunderstood:

    1. (print list3)
    2. (((3) 3) (3) 3)
    3. ;Unspecified return value
    4. (print list2)
    5. (1 (3) 3)
    6. ;Unspecified return value

    Consequently we can conclude that the Lisp printer display all the element
    recursively following given pointer.

    Actually, what version I use for now as scheme interpreter is smarter than that:

    1. (define q1 (make-queue))
    2. ;Value: q1
    3. (insert-queue! q1 'a)
    4. ;Value: (#0=(a) . #0#)
    5. (insert-queue! q1 'b)
    6. ;Value: ((a . #0=(b)) . #0#)
    7. (delete-queue! q1)
    8. ;Value: (#0=(b) . #0#)
    9. (delete-queue! q1)
    10. ;Value: (() b)

    It recognize the same pointer using, I guess, memorize all the pointer it
    encountered. But for now, let we stick the old version of printer in text book.

    Return to our task, print-queue is really simple: Just print following the
    first pointer of queue:

    1. (define (print-queue queue)
    2. (display (front-ptr queue)))

    Then test:

    1. (define q1 (make-queue))
    2. ;Value: q1
    3. (print-queue (insert-queue! q1 'a))
    4. (a)
    5. ;Unspecified return value
    6. (print-queue (insert-queue! q1 'b))
    7. (a b)
    8. ;Unspecified return value
    9. (print-queue (delete-queue! q1))
    10. (b)
    11. ;Unspecified return value
    12. (print-queue (delete-queue! q1))
    13. ()
    14. ;Unspecified return value

    Ben Bitdiddle should satisfy what print-queue display.

  • Exercise 3.22

    We can map our previous implementation to message passing style by

    • (front-ptr queue)front-ptr in make-queue
    • (rear-ptr queue)rear-ptr in make-queue

    It get boring to jot down all the relation; let I just show the result:

    1. (define (make-queue2)
    2. (let ((front-ptr '())
    3. (rear-ptr '()))
    4. ;; selector -- predicate
    5. (define (empty-queue?) (null? front-ptr))
    6. ;; selector -- first item
    7. (define (front-queue)
    8. (if (empty-queue?)
    9. (error "FRONT called with an empty queue -- MAKE-QUEUE2" dispatch)
    10. (car front-ptr)))
    11. (define (insert-queue! item)
    12. (let ((new-pair (cons item '())))
    13. (cond ((empty-queue?)
    14. (set! front-ptr new-pair)
    15. (set! rear-ptr new-pair)
    16. dispatch)
    17. (else
    18. (set-cdr! rear-ptr new-pair)
    19. (set! rear-ptr new-pair)
    20. dispatch))))
    21. (define (delete-queue!)
    22. (cond ((empty-queue?)
    23. (error "DELETE! called with an empty queue -- MAKE-QUEUE2" dispatch))
    24. (else
    25. (set! front-ptr (cdr front-ptr))
    26. dispatch)))
    27. (define (dispatch m)
    28. (cond ((eq? m 'empty-queue?) empty-queue?)
    29. ((eq? m 'front-queue) front-queue)
    30. ((eq? m 'insert-queue!) insert-queue!)
    31. ((eq? m 'delete-queue!) delete-queue!)
    32. (else
    33. (error "Unknown request -- MAKE-QUEUE2" m))))
    34. dispatch))

    And here is how we should use:

    1. (define q1 (make-queue2))
    2. ;Value: q1
    3. ((q1 'insert-queue!) 'a)
    4. ;Value: #[compound-procedure 44 dispatch]
    5. ((q1 'front-queue))
    6. ;Value: a
    7. ((q1 'insert-queue!) 'b)
    8. ;Value: #[compound-procedure 44 dispatch]
    9. ((q1 'front-queue))
    10. ;Value: a
    11. ((q1 'delete-queue!))
    12. ;Value: #[compound-procedure 44 dispatch]
    13. ((q1 'front-queue))
    14. ;Value: b
    15. ((q1 'delete-queue!))
    16. ;Value: #[compound-procedure 44 dispatch]
    17. ((q1 'front-queue))
    18. ;FRONT called with an empty queue -- MAKE-QUEUE2 #[compound-procedure 44 dispatch]

    We can integrate print-queue to this procedure object:

    1. (define (make-queue2)
    2. (let ((front-ptr '())
    3. (rear-ptr '()))
    4. ;; selector -- predicate
    5. (define (empty-queue?) (null? front-ptr))
    6. ;; selector -- first item
    7. (define (front-queue)
    8. (if (empty-queue?)
    9. (error "FRONT called with an empty queue -- MAKE-QUEUE2" dispatch)
    10. (car front-ptr)))
    11. (define (insert-queue! item)
    12. (let ((new-pair (cons item '())))
    13. (cond ((empty-queue?)
    14. (set! front-ptr new-pair)
    15. (set! rear-ptr new-pair)
    16. (print-queue)
    17. dispatch)
    18. (else
    19. (set-cdr! rear-ptr new-pair)
    20. (set! rear-ptr new-pair)
    21. (print-queue)
    22. dispatch))))
    23. (define (delete-queue!)
    24. (cond ((empty-queue?)
    25. (error "DELETE! called with an empty queue -- MAKE-QUEUE2" dispatch))
    26. (else
    27. (set! front-ptr (cdr front-ptr))
    28. (print-queue)
    29. dispatch)))
    30. (define (print-queue) (display front-ptr))
    31. (define (dispatch m)
    32. (cond ((eq? m 'empty-queue?) empty-queue?)
    33. ((eq? m 'front-queue) front-queue)
    34. ((eq? m 'insert-queue!) insert-queue!)
    35. ((eq? m 'delete-queue!) delete-queue!)
    36. (else
    37. (error "Unknown request -- MAKE-QUEUE2" m))))
    38. dispatch))
    1. ((q1 'insert-queue!) 'a)
    2. (a)
    3. ;Value: #[compound-procedure 45 dispatch]
    4. ((q1 'insert-queue!) 'b)
    5. (a b)
    6. ;Value: #[compound-procedure 45 dispatch]
    7. ((q1 'delete-queue!))
    8. (b)
    9. ;Value: #[compound-procedure 45 dispatch]
    10. ((q1 'delete-queue!))
    11. ()
    12. ;Value: #[compound-procedure 45 dispatch]

    Note that with this local state paradigm, message passing style is more
    succinctly encode the specification than the representation using concrete data
    structure– here pair.

  • Exercise 3.23

    Not only to traverse one way– cdr, we need means to traverse the opposite
    way to implement deque. As we requested in implementation of queue, we need to
    implement all the operation in Θ (1) steps.

    Here we devise new data structure not only deque, also node with which we
    represent deque. Node has many analogy with primitive data structure, pair;
    it construct one chunk using several pointers together, but node has three
    pointers in one chunk whereas pair has two pointers in it.

    Here is the specification for node:

    • constructor: (make-node prev item next) returns node that has three
      pointers, each of which points prev, item, next, respectively.
    • selectors: - (prev node) returns first pointer.
      • (item node) returns second pointer.
      • (next node) returns last pointer.
    • mutators: - (set-prev! node new-prev) resets its pointer that points prev to new-prev.
      • (set-item! node new-item) resets its pointer that points item to new-item.
      • (set-next! node new-next) resets its pointer that points next to new-next.

    Here is the implementation for node:

    1. ;;; constructor
    2. (define (make-node prev item next)
    3. (define (set-prev! new-prev) (set! prev new-prev))
    4. (define (set-item! new-item) (set! item new-item))
    5. (define (set-next! new-next) (set! next new-next))
    6. (define (dispatch m)
    7. (cond ((eq? m 'prev) prev)
    8. ((eq? m 'item) item)
    9. ((eq? m 'next) next)
    10. ((eq? m 'set-prev!) set-prev!)
    11. ((eq? m 'set-item!) set-item!)
    12. ((eq? m 'set-next!) set-next!)
    13. (else
    14. (error "Unknown request -- MAKE-NODE" m))))
    15. dispatch)
    16. ;;; selectors
    17. (define (prev node) (node 'prev))
    18. (define (item node) (node 'item))
    19. (define (next node) (node 'next))
    20. ;;; mutators
    21. (define (set-prev! node new-prev) ((node 'set-prev!) new-prev))
    22. (define (set-item! node new-item) ((node 'set-item!) new-item))
    23. (define (set-next! node new-next) ((node 'set-next!) new-next))

    Then we can implement deque using the analogy with queue. During
    implementing node, queue, and the other massage passing style code, I got
    intrigued by how am I going to implement the conventional array in scheme using
    massage passing style; it should access its element with Θ (1) steps. So
    far, in message passing style, we dealt with only fixed sized argument list.

    One way to deal with unspecified argument list is use the primitive procedure
    syntax, e.g. (x y . z); but the problem of this approach is that to access the
    contents that stored in z, as it is list, needs Θ (n) steps where n is
    the size of z. To deal with this unfortunate, if we believe that accessing any
    variable in the frame require only Θ(1) steps, we need to register all the
    argument of z (with x and y also) current environment of callee, for which
    we don’t have any method.

    Let alone that problem let we conclude our original task. First let test our
    node code:

    1. (define n (make-node '() 2 '()))
    2. ;Value: n
    3. (define n2 (make-node n 3 '()))
    4. ;Value: n2
    5. (item n)
    6. ;Value: 2
    7. (item n2)
    8. ;Value: 3
    9. (item (prev n2))
    10. ;Value: 2
    11. (define n3 (make-node '() 4 '()))
    12. ;Value: n3
    13. (set-next! n2 n3)
    14. ;Value: ()
    15. (set-prev! n3 n2)
    16. ;Value: ()
    17. (item (prev (prev n3)))
    18. ;Value: 2

    And this is straightforward implementation for deque:

    1. ;; constructor
    2. (define (make-deque)
    3. (let ((front-ptr '())
    4. (rear-ptr '()))
    5. ;; selector -- predicate
    6. (define (empty-deque?) (or (null? front-ptr)
    7. (null? rear-ptr)))
    8. ;; selector -- first item
    9. (define (front-deque)
    10. (if (empty-deque?)
    11. (error "FRONT called with an empty deque -- MAKE-DEQUE" dispatch)
    12. (item front-ptr)))
    13. (define (rear-deque)
    14. (if (empty-deque?)
    15. (error "REAR called with an empty deque -- MAKE-DEQUE" dispatch)
    16. (item rear-ptr)))
    17. (define (rear-insert-deque! item)
    18. (let ((new-node (make-node '() item '())))
    19. (cond ((empty-deque?)
    20. (set! front-ptr new-node)
    21. (set! rear-ptr new-node)
    22. dispatch)
    23. (else
    24. (set-next! rear-ptr new-node)
    25. (set! rear-ptr new-node)
    26. dispatch))))
    27. (define (front-insert-deque! item)
    28. (let ((new-node (make-node '() item '())))
    29. (cond ((empty-deque?)
    30. (set! front-ptr new-node)
    31. (set! rear-ptr new-node)
    32. dispatch)
    33. (else
    34. (set-prev! front-ptr new-node)
    35. (set! front-ptr new-node)
    36. dispatch))))
    37. (define (front-delete-deque!)
    38. (cond ((empty-deque?)
    39. (error "FRONT-DELETE! called with an empty deque -- MAKE-DEQUE" dispatch))
    40. (else
    41. (set! front-ptr (next front-ptr))
    42. dispatch)))
    43. (define (rear-delete-deque!)
    44. (cond ((empty-deque?)
    45. (error "FRONT-DELETE! called with an empty deque -- MAKE-DEQUE" dispatch))
    46. (else
    47. (set! rear-ptr (prev rear-ptr))
    48. dispatch)))
    49. (define (dispatch m)
    50. (cond ((eq? m 'empty-deque?) empty-deque?)
    51. ((eq? m 'front-deque) front-deque)
    52. ((eq? m 'rear-deque) rear-deque)
    53. ((eq? m 'front-insert-deque!) front-insert-deque!)
    54. ((eq? m 'rear-insert-deque!) rear-insert-deque!)
    55. ((eq? m 'front-delete-deque!) front-delete-deque!)
    56. ((eq? m 'rear-delete-deque!) rear-delete-deque!)
    57. (else
    58. (error "Unknown request -- MAKE-DEQUE" m))))
    59. dispatch))
    60. ;; selector -- predicate
    61. (define (empty-deque? deque) ((deque 'empty-deque?)))
    62. ;; selector -- first item
    63. (define (front-deque deque) ((deque 'front-deque)))
    64. ;; selector -- last item
    65. (define (rear-deque deque) ((deque 'rear-deque)))
    66. ;; mutator -- insert front
    67. (define (front-insert-deque! deque item) ((deque 'front-insert-deque!) item))
    68. ;; mutator -- insert rear
    69. (define (rear-insert-deque! deque item) ((deque 'rear-insert-deque!) item))
    70. ;; mutator -- delete first
    71. (define (front-delete-deque! deque) ((deque 'front-delete-deque!)))
    72. ;; mutator -- delete last
    73. (define (rear-delete-deque! deque) ((deque 'rear-delete-deque!)))

    Test for deque:

    1. (define d (make-deque))
    2. ;Value: d
    3. (empty-deque? d)
    4. ;Value: #t
    5. (front-insert-deque! d 'a)
    6. ;Value: #[compound-procedure 38 dispatch]
    7. (rear-deque d)
    8. ;Value: a
    9. (front-deque d)
    10. ;Value: a
    11. (rear-insert-deque! d 'b)
    12. ;Value: #[compound-procedure 38 dispatch]
    13. (rear-deque d)
    14. ;Value: b
    15. (front-deque d)
    16. ;Value: a
    17. (front-delete-deque! d)
    18. ;Value: #[compound-procedure 38 dispatch]
    19. (front-delete-deque! d)
    20. ;Value: #[compound-procedure 38 dispatch]
    21. (empty-deque? d)
    22. ;Value: #t

Representing Tables

In chapter 2, we exploited a lot the data structure named table. Table was
the backbone our data-directed programming scheme. Table is so general data
structure to the extent many of the “practical” programming languages provides
table as one of the primitive data structure. In Lisp, we don’t have any table
like structure for granted. But we can implement that data structure by our own
if we allowed to use assignment.

Then what is table? What data structure we think as table? Here we define what
we think as table informally:

  • We should be able to retrieve the registered value in the table by its key.
  • We should be able to insert new entry, which contains key value pair-like
    structure, to the table.

More formally table should satisfy following axioms:

  • (lookup key (begin (insert! (make-entry key value) table) table))
    returns value.

What value (lookup not-in-the-table table) should returns is implementation
detail. The implementor can choose whatever value we want unless the specifier
doesn’t specify to that extent.

For the implementation detail, we need to use headed list as the backbone of
our table to insert given entry to the given table; without this, we couldn’t
locate the locus of the table’s contents.

Here is the sample implementation in text book:

  1. ;;; constructor
  2. (define (make-table)
  3. (list '*table*))
  4. ;;; selector + predicate
  5. (define (lookup key table)
  6. (let ((record (assoc key (cdr table))))
  7. (if record
  8. (cdr record)
  9. false)))
  10. ;;; dependency
  11. ;;; we represent contents of table as A-list
  12. (define (assoc key records)
  13. (cond ((null? records) false)
  14. ((equal? key (caar records)) (car records))
  15. (else (assoc key (cdr records)))))
  16. ;;; mutator
  17. (define (insert! key value table)
  18. (let ((record (assoc key records)))
  19. (if record
  20. (set-cdr! record value)
  21. (set-cdr! table
  22. (cons (cons key value)
  23. (cdr table)))))
  24. 'ok)
  • Two-dimensional tables

    We can extend one-dimensional table above to two-dimensional table by observing
    that each value of one-dimensional table could be A-list. As the key of the
    subtables performs as header, we don’t need any auxiliary header as we did in
    one-dimensional table.

    As we noted above, all the left is to implement two dimensional lookup procedure
    and insert accordingly:

    1. (define (lookup key-1 key-2 table)
    2. (let ((subtable (assoc key-1 (cdr table))))
    3. (if subtable
    4. (let ((record (assoc key-2 (cdr subtable))))
    5. (if record
    6. (cdr record)
    7. false))
    8. false)))
    9. (define (insert! key-1 key-2 value table)
    10. (let ((subtable (assoc key-1 (cdr table))))
    11. (if subtable
    12. (let ((record (assoc key-2 (cdr subtable))))
    13. (if record
    14. (set-cdr! record value)
    15. (set-cdr! subtable
    16. (cons (cons key-2 value)
    17. (cdr subtable)))))
    18. (set-cdr! table
    19. (cons (list key-1
    20. (cons key-2 value))
    21. (cdr table)))))
    22. 'ok)
  • Creating local tables

    We can convert our concrete representation to message-passing:

    1. (define (make-table2)
    2. (let ((local-table (list '*table*)))
    3. (define (lookup key-1 key-2)
    4. (let ((subtable (assoc key-1 (cdr local-table))))
    5. (if subtable
    6. (let ((record (assoc key-2 (cdr subtable))))
    7. (if record
    8. (cdr record)
    9. false))
    10. false)))
    11. (define (insert! key-1 key-2 value)
    12. (let ((subtable (assoc key-1 (cdr local-table))))
    13. (if subtable
    14. (let ((record (assoc key-2 (cdr subtable))))
    15. (if record
    16. (set-cdr! record value)
    17. (set-cdr! subtable
    18. (cons (cons key-2 value)
    19. (cdr subtable)))))
    20. (set-cdr! local-table
    21. (cons (list key-1
    22. (cons key-2 value))
    23. (cdr local-table)))))
    24. 'ok)
    25. (define (dispatch m)
    26. (cond ((eq? m 'lookup-proc) lookup)
    27. ((eq? m 'insert-proc!) insert!)
    28. (else (error "Unknown operation -- TABLE" m))))
    29. dispatch))
  • Exercise 3.24

    It is way too easy to implement we get what we want by slightly amending above
    implementation:

    1. (define (make-table3 same-key?)
    2. (define (assoc key records)
    3. (cond ((null? records) false)
    4. ((same-key? key (caar records)) (car records))
    5. (else (assoc key (cdr records)))))
    6. (let ((local-table (list '*table*)))
    7. (define (lookup key-1 key-2)
    8. (let ((subtable (assoc key-1 (cdr local-table))))
    9. (if subtable
    10. (let ((record (assoc key-2 (cdr subtable))))
    11. (if record
    12. (cdr record)
    13. false))
    14. false)))
    15. (define (insert! key-1 key-2 value)
    16. (let ((subtable (assoc key-1 (cdr local-table))))
    17. (if subtable
    18. (let ((record (assoc key-2 (cdr subtable))))
    19. (if record
    20. (set-cdr! record value)
    21. (set-cdr! subtable
    22. (cons (cons key-2 value)
    23. (cdr subtable)))))
    24. (set-cdr! local-table
    25. (cons (list key-1
    26. (cons key-2 value))
    27. (cdr local-table)))))
    28. 'ok)
    29. (define (dispatch m)
    30. (cond ((eq? m 'lookup-proc) lookup)
    31. ((eq? m 'insert-proc!) insert!)
    32. (else (error "Unknown operation -- TABLE" m))))
    33. dispatch))
  • Exercise 3.25

    It is equivalent to prove the behavior of the specification: Induction on depth
    of the table. If we persist with the A-list representation as before, and also
    assume that all the generalized table structured appropriately, we can easily
    extend the previous table to generalized one:

    1. (define (lookup key-list table)
    2. (cond ((null? key-list)
    3. (error "LOOKUP called with empty key-list"))
    4. ((null? (cdr key-list))
    5. (let ((record (assoc (car key-list) (cdr table))))
    6. (if record
    7. (cdr record)
    8. false)))
    9. (else
    10. (let ((subtable (assoc (car key-list) (cdr table))))
    11. (if subtable
    12. (lookup (cdr key-list table))
    13. false)))))
    14. (define (insert! key-list value table)
    15. (cond ((null? key-list)
    16. (error "INSERT! called with empty key-list"))
    17. ((null? (cdr key-list))
    18. (let ((record (assoc (car key-list) records)))
    19. (if record
    20. (set-cdr! record value)
    21. (set-cdr! table
    22. (cons (cons (car key-list) value)
    23. (cdr table))))))
    24. (else
    25. (let ((subtable (assoc (car key-list) (cdr table))))
    26. (if subtable
    27. (insert! (cdr key-list) value table)
    28. (set-cdr! table
    29. (cons (make-table-with key-list value)
    30. (cdr table)))))))
    31. 'ok)
    32. (define (make-table-with key-list value)
    33. (if (null? (cdr key-list))
    34. (cons (car key-list) value)
    35. (list (car key-list)
    36. (make-table-with (cdr key-list) value))))

    I didn’t tested it but it would work only if we lookup the table that contains
    entry with key list with which we lookup. Unfortunately, it is huge bug, since
    the user of lookup doesn’t know whether the table has such entry. To fix this,
    we make our table using the strategy learned from lecture, Abstract Data Type.

    In previous exercise, we only dealt with table of fixed dimension; we don’t need
    to check whether the value of given entry is table or not. Here we don’t know
    fore hand the dimension of table, and to deal with such situation, we treat
    table as special value of one dimensional table. By induction on the depth on
    the dimension of table, our result table would handle the unspecified dimension
    of table without any trouble.

    At our disposal, we have two candidates for implementation of this:
    message-passing style with local state or dispatch on data type with concrete
    data structure.

    Here we first try out the message-passing style:

    1. ;; constructor
    2. (define (make-table4)
    3. (let ((local-table (list '*table*)))
    4. (define (lookup-internal key-list)
    5. (let ((record (assoc (car key-list) (cdr local-table))))
    6. (if record
    7. (let ((value (cdr record)))
    8. (cond ((null? (cdr key-list)) value)
    9. ((table? value)
    10. (lookup (cdr key-list) value))
    11. (else false)))
    12. false)))
    13. (define (insert-internal! key-list value)
    14. (let ((record (assoc (car key-list) (cdr local-table))))
    15. (if record
    16. (let ((value (cdr record)))
    17. (cond ((null? (cdr key-list)) (set-cdr! record value))
    18. ((table? value)
    19. (insert! (cdr key-list) value))))
    20. (set-cdr! local-table
    21. (cons (make-table-with key-list value)
    22. (cdr local-table)))))
    23. 'ok)
    24. (define (make-table-with key-list value)
    25. (if (null? (cdr key-list))
    26. (cons (car key-list) value)
    27. (let ((tbl (make-table4)))
    28. (insert! (cdr key-list)
    29. value
    30. tbl)
    31. (cons (car key-list) tbl))))
    32. (define (dispatch m)
    33. (cond ((eq? m 'lookup-proc) lookup-internal)
    34. ((eq? m 'insert-proc!) insert-internal!)
    35. ((eq? m 'table?) true)
    36. (else (error "Unknown operation -- TABLE" m))))
    37. dispatch))
    38. ;; selector -- predicate
    39. (define (table? t) (t 'table?))
    40. (define (lookup ks t) ((t 'lookup-proc) ks))
    41. ;; mutator
    42. (define (insert! ks v t) ((t 'insert-proc!) ks v))

    Then test:

    1. ;; constructor
    2. (define tbl (make-table4))
    3. ;Value: tbl
    4. ;; predicate
    5. (table? tbl)
    6. ;Value: #t
    7. ;; mutator
    8. (insert! '(1 2 3 4) 5 tbl)
    9. ;Value: ok
    10. ;; selector
    11. (lookup '(1 2 3 4) tbl)
    12. ;Value: 5
    13. (lookup '(1) tbl)
    14. ;Value: #[compound-procedure 39 dispatch]
    15. (lookup '(2 3 4) (lookup '(1) tbl))
    16. ;Value: 5

    The another way:

    1. ;; type tag
    2. (define table-tag '*table*)
    3. ;; constructor
    4. (define (make-table5)
    5. (list table-tag))
    6. ;; selector -- predicate
    7. (define (table? t)
    8. (and (pair? t) (eq? (car t) table-tag)))
    9. ;; selector
    10. (define (lookup key-list tbl)
    11. (let ((record (assoc (car key-list) (cdr tbl))))
    12. (if record
    13. (let ((value (cdr record)))
    14. (cond ((null? (cdr key-list)) value)
    15. ((table? value)
    16. (lookup (cdr key-list) value))
    17. (else false)))
    18. false)))
    19. ;; mutator
    20. (define (insert! key-list value tbl)
    21. (let ((record (assoc (car key-list) (cdr tbl))))
    22. (if record
    23. (let ((value (cdr record)))
    24. (cond ((null? (cdr key-list)) (set-cdr! record value))
    25. ((table? value)
    26. (insert! (cdr key-list) value))))
    27. (set-cdr! tbl
    28. (cons (make-table-with key-list value)
    29. (cdr tbl)))))
    30. 'ok)
    31. (define (make-table-with ks v)
    32. (if (null? (cdr ks))
    33. (cons (car ks) v)
    34. ;; (let ((tbl (make-table5)))
    35. ;; (insert! (cdr key-list)
    36. ;; value
    37. ;; tbl)
    38. ;; (cons (car key-list) tbl))))
    39. (cons (car ks) (list table-tag ;more efficiently
    40. (make-table-with (cdr ks) v)))))

    To design make-table-with, I’ve exploit the opaque type. And here is the test:

    1. (define tbl (make-table5))
    2. ;Value: tbl
    3. (table? tbl)
    4. ;Value: #t
    5. (insert! '(1 2 3 4) 5 tbl)
    6. ;Value: ok
    7. (lookup '(1 2 3 4) tbl)
    8. ;Value: 5
    9. (lookup '(2 3 4) (lookup '(1) tbl))
    10. ;Value: 5

    Note that the latter one is more efficient than former one; also seems cleaner.
    Object-oriented paradigm do not always win.

  • Exercise 3.26

    As we already implemented the lookup procedure for binary tree in exercise 2.66.
    All the left is to implement constructor and mutator of table.

    With the same argument of headed list– our first backbone of table, we need to
    tag a type to locate “place” to mutate table.

    Here for simplicity, we don’t consider the “balance” of tree structure; but that
    issue can be dealt with using amortized algorithm.

    Here’s the result:

    1. ;; constructor
    2. (define (make-table6)
    3. (cons table-tag '()))
    4. ;; selector
    5. (define (lookup key table)
    6. (let ((entry (assoc-tree key (cdr table))))
    7. (if entry
    8. (value entry)
    9. false)))
    10. (define (assoc-tree given-key tree)
    11. (if (null? tree)
    12. false
    13. (let ((hd (key (entry tree))))
    14. (cond ((= given-key hd) (entry tree))
    15. ((< given-key hd) (assoc-tree given-key (left-branch tree)))
    16. ((> given-key hd) (assoc-tree given-key (right-branch tree)))))))
    17. ;; mutator
    18. (define (insert! key value table)
    19. (let ((tree (cdr table)))
    20. (if (null? tree)
    21. (set-cdr! table
    22. (make-tree-with-entry (make-entry key value)))
    23. (insert-tree! key value tree))))
    24. (define (insert-tree! aKey aValue tree)
    25. (let ((hd (key (entry tree))))
    26. (cond ((= aKey hd) (set-value! (entry tree) aValue))
    27. ((< aKey hd)
    28. (if (null? (left-branch tree))
    29. (set-left-branch! tree (make-tree-with-entry (make-entry aKey aValue)))
    30. (insert-tree! aKey aValue (left-branch tree))))
    31. ((> aKey hd)
    32. (if (null? (right-branch tree))
    33. (set-right-branch! tree (make-tree-with-entry (make-entry aKey aValue)))
    34. (insert-tree! aKey aValue (right-branch tree)))))))
    35. (define (make-tree-with-entry entry) (make-tree entry '() '()))
    36. ;; backbone of table
    37. ;;; constructor
    38. (define (make-tree entry left right)
    39. (list entry left right))
    40. ;;; selectors
    41. (define (entry tree) (car tree))
    42. (define (left-branch tree) (cadr tree))
    43. (define (right-branch tree) (caddr tree))
    44. ;;; mutators
    45. (define (set-left-branch! tree left-tree) (set-car! (cdr tree) left-tree))
    46. (define (set-right-branch! tree right-tree) (set-car! (cddr tree) right-tree))
    47. ;; lowest layer entry language
    48. (define (make-entry key value)
    49. (cons key value))
    50. (define (key entry) (car entry))
    51. (define (value entry) (cdr entry))
    52. (define (set-value! entry value) (set-cdr! entry value))

    Test:

    1. (define tbl (make-table6))
    2. ;Value: tbl
    3. (insert! 1 'a tbl)
    4. ;Unspecified return value
    5. (lookup 1 tbl)
    6. ;Value: a
    7. (insert! 5 'e tbl)
    8. ;Unspecified return value
    9. (insert! -3 'z tbl)
    10. ;Unspecified return value
    11. tbl
    12. ;Value: (*table* (1 . a) ((-3 . z) () ()) ((5 . e) () ()))
    13. (lookup -3 tbl)
    14. ;Value: z
  • Exercise 3.27

    I’ve drawed environment diagram in my personal digital paper. To analysis the
    step complexity of memo-fib, let we assume that our memo-fib procedure does
    not compute the result of the argument that previously called with – actually
    this is the reason we use memoization for – by wishful thinking. Then note
    that, for instance, if our entry point – the first procedure call – was made
    by (memo-fib 7), depending on how our the interpreter works, it should compute
    after some step (memo-fib 6) (or (memo-fib 5)) followed by (memo-fib 5)
    (or (memo-fib 6)); observe when it comes to the latter point of computation
    the result of that should already be in the local table of memo-fib by the
    assumption. Nevertheless, in any circumstance, if we let (T(n)) denote the step
    complexity of (memo-fib n) then we get [T(n) - T(n - 1) = \Theta(1)] from
    which, we can conclude that [T(n) = \Theta(n)].

    To prove our assumption, we need to argue in our environment model for
    evaluation, which just complicate our discussion; yet we can grasp the taste of
    it by simulating (drawing) the evaluation process using environment model.

A Simulator for Digital Circuits

Here we implement event-driven simulation; usually we encounter event-driven
programming in web developing – JavaScript, ruby on rails, and so on. For
instance, we develop language for digital circuits simulator.

As the name of our language, simulator, indicates that we model real world
object exploiting local state variable.

As a powerful language should consist of primitive elements, means of combination, means of
abstraction, our simulator language also composed by those:

  • primitive elements: Digital circuits constructed by combination of simple
    logical function boxes:

    img

    • Inverter
    • And-gate
    • Or-gate
  • means of combination: We can construct more complex function boxes using
    previous ones: Using wiring up the components.
  • means of abstraction: As we embed our language in scheme, we inherit
    scheme’s means of abstraction – define.

Note that we described above components using real world terminology since we
assumed that we mapped that concept to our language.

For instance, let we construct half-adder circuit:

img

  1. ;;; consturct half-adder
  2. (define a (make-wire))
  3. (define b (make-wire))
  4. (define c (make-wire))
  5. (define d (make-wire))
  6. (define e (make-wire))
  7. (define s (make-wire))
  8. (or-gate a b d)
  9. (and-gate a b c)
  10. (inverter c e)
  11. (and-gate d e s)

As this is general function box, it would be more appropriate to abstract this
as box:

  1. (define (half-adder a b s c)
  2. (let ((d (make-wire)) (e (make-wire)))
  3. (or-gate a b d)
  4. (and-gate a b c)
  5. (inverter c e)
  6. (and-gate d e s)
  7. ))

Using this function box, we can construct more useful, complex function box:

img

  1. (define (full-adder a b c-in sum c-out)
  2. (let ((s (make-wire))
  3. (c1 (make-wire))
  4. (c2 (make-wire)))
  5. (half-adder b c-in s c1)
  6. (half-adder a s sum c2)
  7. (or-gate c1 c2 c-out)
  8. 'ok))
  • Primitive function boxes

    The primitive function boxes works as force the wire get its signal changed. So
    wire should provides appropriate operations for that:

    • (get-signal <wire>)
      returns the current value of the signal on wire.
    • (set-signal <wire> <new value>)
      changes the value of the signal on the wire to the new value.
    • (add-action! <wire> <procedure of no arguments>)
      asserts that the designated procedure should be run whenever the signal on the
      wire changes value. Such procedures are the vehicles by which changes in the
      signal value on the wire are communicated to other wires.

    Note that the first two operations should be familiar with us – selector and
    mutator; but the last one is specific for the event-driven programming. By
    this effect, the “force” propagates along with the wires.

    In addition, to reflect the delay of logical function boxes, we need to make use
    of a procedure after-delay that takes a time delay and a procedure to be run
    and executes the given procedure after the given delay.

    As we gathered all the components we need, let’s define the primitive function
    boxes:

    • inverter

      1. (define (inverter input output)
      2. (define (invert-input)
      3. (let ((new-value (logical-not (get-signal input))))
      4. (after-delay inverter-delay
      5. (lambda ()
      6. (set-signal! output new-value)))))
      7. (add-action! input invert-input)
      8. 'ok)
      9. (define (logical-not s)
      10. (cond ((= s 0) 1)
      11. ((= s 1) 0)
      12. (else (error "Invalid signal" s))))

      Note that whenever the input signal changes invert-input executed by the
      add-action!

    • and-gate

      1. (define (and-gate a1 a2 output)
      2. (define (and-action-procedure)
      3. (let ((new-value
      4. (logical-and (get-signal a1) (get-signal a2))))
      5. (after-delay and-gate-delay
      6. (lambda ()
      7. (set-signal! output new-value)))))
      8. (add-action! a1 and-action-procedure)
      9. (add-action! a2 and-action-procedure)
      10. 'ok)
      11. (define (logical-and s1 s2)
      12. (cond ((and (= s1 0) (= s2 0)) 0)
      13. ((and (= s1 0) (= s2 1)) 0)
      14. ((and (= s1 1) (= s2 0)) 0)
      15. ((and (= s1 1) (= s2 1)) 1)
      16. (else (error "Invalid signals" (cons s1 s2)))))
    • or-gate

      1. (define (or-gate a1 a2 output)
      2. (define (or-action-procedure)
      3. (let ((new-value
      4. (logical-or (get-signal a1) (get-signal a2))))
      5. (after-delay or-gate-delay
      6. (lambda ()
      7. (set-signal! output new-value)))))
      8. (add-action! a1 or-action-procedure)
      9. (add-action! a2 or-action-procedure)
      10. 'ok)
      11. (define (logical-or s1 s2)
      12. (cond ((and (= s1 0) (= s2 0)) 0)
      13. ((and (= s1 0) (= s2 1)) 1)
      14. ((and (= s1 1) (= s2 0)) 1)
      15. ((and (= s1 1) (= s2 1)) 1)
      16. (else (error "Invalid signals" (cons s1 s2)))))
  • Exercise 3.28

    See the above or-gate part.

  • Exercise 3.29

    We can use either De Morgan’s law or truth table to deduce following logic:

    1. (define (or-gate2 a1 a2 output)
    2. (let ((not-a1 (make-wire))
    3. (not-a2 (make-wire))
    4. (not-or (make-wire)))
    5. (inverter a1 not-a1)
    6. (inverter a2 not-a2)
    7. (and-gate not-a1 not-a2 not-or)
    8. (inverter not-or output)
    9. 'ok))

    or-gate-delay becomes 2 inverter-delay + and-gate-delay.

  • Exercise 3.30

    Using induction on n, we can construct ripple-carry-adder. More
    specifically, to connect each full-adder boxes, we should hand over the carry
    wire to next full-adder box:

    img

    1. (define (ripple-carry-adder aAs aBs aSs C)
    2. (define (connect-and-return-carry Ak Bk Ck Sk)
    3. (let ((C_k-1 (make-wire)))
    4. (full-adder Ak Bk Ck Sk C_k-1)
    5. C_k-1))
    6. (define (connect-recursive As Bs Ss)
    7. (cond ((and (null? As) (null? Bs) (null? Ss))
    8. (make-wire)) ;C_n
    9. ((or (null? As) (null? Bs) (null? Ss)) ;defensive programming
    10. (error
    11. "arguments do not agree in the number of elements --RIPPLE-CARRY-ADDER"
    12. (list aAs aBs aSs)))
    13. (else
    14. (connect-and-return-carry
    15. (car As) (car Bs)
    16. (connect-recursive (cdr As) (cdr Bs) (cdr Ss))
    17. (car Ss)))))
    18. (cond ((or (null? aAs) (null? aBs) (null? aSs))
    19. (error "RIPPLE-CARRAY-ADDER cannot do connect with given arguemens"
    20. (list aAs aBs aSs)))
    21. (else (full-adder (car aAs)
    22. (car aBs)
    23. (connect-recursive ;C_1
    24. (cdr aAs)
    25. (cdr aBs)
    26. (cdr aSs))
    27. (car aSs)
    28. C)
    29. 'ok)))

    To estimate the whole delay of ripple carry adder, we exploit abstraction over
    the delays of other simpler function boxes that constitute ripple carry adder.
    Observe that the delay from Ck to Ck-1 accumulated; it would be the
    bottle neck of propagation of signal in this function box. So we can conclude
    that the whole delay of ripple carry adder would be approximately n ×
    each of the delay that going to be accumulated.

    In turns, to estimate the each delay of that part of full-adder, we need to do the same
    process as above or we could “bottom up” approach to it; calculate from the
    very lower level of function boxes from which full-adder constructed:

    • From half-adder, we have or-gate-delay + and-gate-delay or 2
      and-gate-delay + inverter-delay to propagate the
      signal from inputs to the s.
    • From full-adder, we have 2 (or-gate-delay + and-gate-delay) or 3
      and-gate-delay + inverter-delay + or-gate-delay to propagate from c-in
      to c-out.

    So, if we stick with or-gate and and-gate combination only, we got n
    × 2 (or-gate-delay + and-gate-delay) as whole. We will verify this when
    we implemented all the parts of our language.

  • Representing wires

    By wishful thinking, we just used wires to connect the components of our
    primitive boxes until now; this time, we need to represent wires according to
    our use of that – the specifications deduced from our use.

    Our wire should have signal as its state variable from the use of that; i.e.
    (get-signal <wire>) and (set-signal! <wire> <new value>). Also it should
    have some way to save all the actions it should executes when it change its
    signal according to (add-action! <wire> <procedure of no arguments>); the
    name’s bang of usage implies wire should have “action list” as its state
    variable.

    Here, in text book, we implement wire using the message passing style:

    1. (define (make-wire)
    2. (let ((signal-value 0) (action-procedures '()))
    3. (define (set-my-signal! new-value)
    4. (if (not (= signal-value new-value))
    5. (begin (set! signal-value new-value)
    6. (call-each action-procedures))
    7. 'done))
    8. (define (accept-action-procedure! proc)
    9. (set! action-procedures (cons proc action-procedures))
    10. (proc))
    11. (define (dispatch m)
    12. (cond ((eq? m 'get-signal) signal-value)
    13. ((eq? m 'set-signal!) set-my-signal!)
    14. ((eq? m 'add-action!) accept-action-procedure!)
    15. (else (error "Unknown operation -- WIRE" m))))
    16. dispatch))
    17. (define (call-each procedures)
    18. (if (null? procedures)
    19. 'done
    20. (begin
    21. ((car procedures))
    22. (call-each (cdr procedures)))))

    Then set up the interface for procedure calling style:

    1. (define (get-signal wire)
    2. (wire 'get-signal))
    3. (define (set-signal! wire new-value)
    4. ((wire 'set-signal!) new-value))
    5. (define (add-action! wire action-procedure)
    6. ((wire 'add-action!) action-procedure))
  • The agenda

    Only the after-delay left to complete our language. We need some data
    structure that contains the schedule of things to do. For that structure we make
    data structure called agenda. Here is the interface of agenda:

    • (make-agenda)
      returns a new empty agenda
    • (empty-agenda? <agenda>)
      is true if the specified agenda is empty.
    • (first-agenda-item <agenda>)
      returns the first item on the agenda.
    • (remove-first-agenda-item! <agenda>)
      modifies the agenda by removing the first item.
    • (add-to-agenda! <time> <action> <agenda>)
      modifies the agenda by adding the given action procedure to be run at the
      specified time.
    • (current-time <agenda>)
      returns the current simulation time.

    We also used the wishful thinking to construct this interface. The particular
    agenda that we use along the simulation is denoted as the-agenda. Using this
    structure we can implement after-delay procedure:

    1. (define (after-delay delay action)
    2. (add-to-agenda! (+ delay (current-time the-agenda))
    3. action
    4. the-agenda))

    With this data structure, we are ready to implement the initiation of simulator:

    1. (define (propagate)
    2. (if (empty-agenda? the-agenda)
    3. 'done
    4. (let ((first-item (first-agenda-item the-agenda)))
    5. (first-item)
    6. (remove-first-agenda-item! the-agenda)
    7. (propagate))))
  • A sample simulation

    Cool! Since we can start the simulation, and we got the all the components of
    simulation, we are done only if we represent the agenda data structure? No! We
    can’t inspect the simulation: We only can inspect whether the simulation done by
    propagate. To cope with this, we implement probe procedure that print out
    all the events at given wire:

    1. (define (probe name wire)
    2. (add-action! wire
    3. (lambda ()
    4. (newline)
    5. (display name)
    6. (display " ")
    7. (display (current-time the-agenda))
    8. (display " New-value = ")
    9. (display (get-signal wire)))))

    We exploited the underlying operation for wire.

    We are really ready to start the simulation. Here is the sample simulation:

    1. ;;; setup
    2. (define the-agenda (make-agenda))
    3. (define inverter-delay 2)
    4. (define and-gate-delay 3)
    5. (define or-gate-delay 5)
    6. (define input-1 (make-wire))
    7. (define input-2 (make-wire))
    8. (define sum (make-wire))
    9. (define carry (make-wire))
    10. (probe 'sum sum)
    11. (probe 'carry carry)
    12. ;;; set situation and start
    13. (half-adder input-1 input-2 sum carry)
    14. (set-signal! input-1 1)
    15. (propagate)
    16. ;;; second situation and start
    17. (set-signal! input-2 1)
    18. (propagate)
  • Exercise 3.31

    We can analyze this problem in two different ways (but not mutually
    independent):

    • Focus the mapping between real world situation, from which we modeled, with
      our resulting model.
    • Experiment with the alternative and see what happen then evaluate its result
      (actually to estimate the result, we would use the first way).

    Let we start with the latter method and then evaluate that with the first
    method. As we did not complete our simulation language, we should simulate with
    their definitions; in other word, we should evaluate each statement as if we
    become a interpreter.

    With the normal definition of accept-action-procedure!, already the result of
    simulation texted in text book; so here we need only to simulate with the
    following definition on our own:

    1. *** in make-wire
    2. (define (accept-action-procedure! proc)
    3. (set! action-procedures (cons proc action-procedures)))

    Let’s run our interpreter:

    1. evaluate (half-adder input-1 input-2 sum carry); at this point
      (empty-agenda? the-agenda) returns true as we don’t run the action since
      the input signals doesn’t changed at all.
    2. evaluate (set-signal! input-1 1); as referencing the half-adder diagram,
      the agenda should contains the schedule that looks like

      1. (
      2. (3 (lambda ()
      3. (set-signal!
      4. carry
      5. (logical-and
      6. (get-signal input-1) (get-signal input-2)))))
      7. (5 (lambda ()
      8. (set-signal!
      9. d
      10. (logical-or (get-signal input-1) (get-signal input-2)))))
      11. )

      The first item of each element represent the scheduled time and the next is
      the action to be executed at that time.

      Here I’ve used the substitution model for evaluation; but it is not strictly
      correct – the value of evaluating logical-and or logical-or should be
      captured when the connection being made for instance.

    3. evaluate (propagate);

      1. (set-signal!
      2. carry
      3. (logical-and
      4. (get-signal input-1) (get-signal input-2)))

      evaluated to

      1. (set-signal! carry 0)

      which cause no change at all.

      Next evaluate

      1. (lambda ()
      2. (set-signal!
      3. d
      4. (logical-or (get-signal input-1) (get-signal input-2))))

      to

      1. (set-signal! d 1)

      ; as d changed its signal, it causes further events:

      1. (
      2. (8 (lambda ()
      3. (set-signal!
      4. sum
      5. (logical-and
      6. (get-signal d) (get-signal e)))))
      7. )

      As

      1. (set-signal! sum 0)

      doesn’t change the signal of sum, it finalize the simulation: 'done.

    The evaluation of this simulation is different from previous: sum and carry
    doesn’t change its signal at all.

    Now we evaluate the result of our simulation using the criterion of first
    method:
    Our real world situation indicates the previous one is correct: the modified one
    is different from what “really” happens.

    Let we conclude with the first method, in this time, it is irrelevant with the
    second method. We observe in real world that as soon as we connect each
    components of digital circuits, the signals of wires, which connect each
    component, affected by its action – connecting. Therefore, the initialization
    in the accept-action-procedure! is necessary when we really want to model that
    situation.

  • Implementing the agenda

    The agenda is made up of time segments; to reflect this structure, we provide
    the backbone of agenda from which agenda constructed up:

    1. (define (make-time-segment time queue)
    2. (cons time queue))
    3. (define (seqment-time s) (car s))
    4. (define (segment-queue s) (cdr s))

    It doesn’t do any special things except it indicates our strategy that we going
    to use queue for accumulating action procedures to be executed at that time. In
    fact, this is due to limitation of our programming language, as it does not
    allow us to use concurrent process, we need to mimic the situation all the
    procedures executed at once in our sequential language – using queue.

    As we noted before, the agenda itself is a one-dimensional table of time
    segments. It is ordered by increasing time, it has current time as its head:

    1. ;; constructor
    2. (define (make-agenda) (list 0))
    3. ;; selector -- time
    4. (define (current-time agenda) (car agneda))
    5. ;; mutator -- time
    6. (define (set-current-time! agenda time)
    7. (set-car! agenda time))
    8. ;; selector -- segments
    9. (define (segments agenda) (cdr agenda))
    10. ;; mutator -- segments
    11. (define (set-segments! agenda segments)
    12. (set-cdr! agenda segments))
    13. ;; selector -- operating on segments
    14. (define (first-segment agenda) (car (segments agenda)))
    15. ;; selector -- operating on segments
    16. (define (rest-segments agenda) (cdr (segments agenda)))
    17. ;; predicate agenda
    18. (define (empty-agenda? agenda)
    19. (null? (segments agenda)))

    To add a new action to agenda, we choose to mutate our agenda structure. It
    make us confront with some tricky situation, which would not occur in functional
    programming. We need to maintain the structure to preserve its identity after
    mutating the structure – as we did in table implementation. So we need to
    follow somewhat more tricky algorithm specified in text book:

    1. (define (add-to-agenda! time action agenda)
    2. (define (belongs-before? segments)
    3. (or (null? segments)
    4. (< time (segment-time (car segments)))))
    5. (define (make-new-time-segment time action)
    6. (let ((q (make-queue)))
    7. (insert-queue! q action)
    8. (make-new-time-segment time q)))
    9. (define (add-to-segments! segments)
    10. (if (= (segment-time (car segments)) time)
    11. (insert-queue! (segment-queue (car segments))
    12. action)
    13. (let ((rest (cdr segments)))
    14. (if (belongs-before? rest)
    15. (set-cdr!
    16. segments
    17. (cons (make-new-time-segment time action)
    18. rest))
    19. (add-to-segments! rest)))))
    20. (let ((segments (segments agenda))) ;handle the entry point
    21. (if (belongs-before? segments)
    22. (set-segments!
    23. agenda
    24. (cons (make-new-tiem-segment time action)
    25. segments))
    26. (add-to-segments! segments))))

    The remover:

    1. (define (remove-first-agenda-item! agenda)
    2. (let ((q (segment-queue (first-segment agenda))))
    3. (delete-queue! q)
    4. (if (empty-queue? q)
    5. (set-segments! agenda (rest-segments agenda)))))

    Finally the access to the agenda from the higher level user:

    1. (define (first-agenda-item agenda)
    2. (if (empty-agenda? agenda)
    3. (error "Agenda is empty -- FIRST-AGENDA-ITEM")
    4. (let ((first-seg (first-segment agenda)))
    5. (set-current-time! agenda (segment-time first-seg)) ;first item access renew the time
    6. (front-queue (segment-queue first-seg))))) ;from the contract, queue is not empty

    Then let’s test with previous exercise, Exercise 3.31:

    1. (half-adder input-1 input-2 sum carry)
    2. ;Value: ok
    3. (set-signal! input-1 1)
    4. ;Value: done
    5. (propagate)
    6. ;Value: done
    7. (set-signal! input-2 1)
    8. ;Value: done
    9. (propagate)
    10. carry 11 New-value = 1
    11. ;Value: done

    It is exactly what we expected.

  • Exercise 3.32

    We reason as follows:

    1. By the observation of previous exercise, each action stored in one wire
      executed when the signal of that wire has changed.
    2. As queue is FIFO, even if it is same time at which the actions to be
      executed, it is important which one has added to agenda.
    3. There are two possibilities in this particular situation:
      • 0,1 → 1,1 → 1,0 – abbreviated form of that first transit from 0,1 to
        1,1 (the first argument change its signal) and then transit from 1,1 to 1,0
        (the second argument change its signal).
      • 0,1 → 0,0 → 1,0
    4. Case 1: output → 1 → 0 – output of and-gate transit from its initial
      signal (0) to 1 and then 0 at the same time point.
      Case 2: output is unchanged.
    5. Case 1 would cause further event propagation; Case 2 would not. But with the
      current primitive function boxes, after done with propagation, we can’t
      notice the difference between Case 1 from Case 2.
    6. Now let we assume list structure not queue: LIFO; with the same reasoning as
      with queue, the order of action is important also.
    7. Case 1 would be stacked as (1,1 → 1,0) (0,1 → 1,1) as and-gate-action
      capture the signals of wires when it called not the execution time of that action.
    8. That cause output → unchanged → 1
    9. Whereas, Case 2 stacked as follows: (0,0 → 1,0) (0,1 → 0,0), which cause
      the output unchanged at all.
    10. Conclusion: If we use list instead of queue, new the order in which actions
      to be executed start to get mattered; also in the view point of modeling, it
      doesn’t reflect well what behavior the real world takes.

Lecture 5B: Computational Objects

In this lecture, we are going to learn how we use computational object – the
object of message passing paradigm – to model the real world object; that is,
mapping the objects from the real world to our computational world as well as
all the relations between the objects. In lecture, it brings digital circuit
simulation as example of instantiation of outlined scheme.

The steps of the conversion, from model of real world to our computational
model, can be summarized as follows:

  1. Capture the behavior of real world object that we want to map to our minds.
    It requires more subtle steps for itself; the main concept of that steps is
    to abstract the behavior as formally as possible.

    In the digital circuit example, the primitive function boxes would be that –
    inverter, and-gate, or-gate in addition to wire.

  2. Then formalize captured behavior using the state and mutation languages (more
    specifically using the wishful thinking!).

    In the example of lecture, each the cloudy wire object has to have signal
    “state” with action procedures list “state” which stores things to inform
    when it has changed its own signal value.

  3. Turn the language of state and mutation to the language of environment model.
    Environment model can deal with the evaluation involving mutation and
    assignment as well as the other expressions, we can safely map all the stuff
    of the wishful thinking involving computational object as instantiation of
    environment model.

    In the example of digital circuit, wire is the environment which possess
    signal and action procedures as variable in its first frame; the references
    between the objects resolved as bindings in frame.

  4. Convert the environment model to code which produce such environments. We
    have learned the instance of environment model that given code produces; if
    we approach backwards, we can get what we want as there is one to one mapping
    between the environment diagram and that code.

    In the example of the digital circuit, there are more than what we outlined
    here: as we are going to code digital circuit “simulation”, we need to code
    the fragments to achieve simulation feature such as delay of propagation and
    so on. The agenda data structure is one of the auxiliary parts we mentioned
    above.

For the latter part of the example, it involves another key concept: Designing
ADT – in lecture, the agenda structure. To design and implement ADT,

  1. By wishful thinking, come up with behaviors that we want to have.
  2. Formalize the captured behavior as ADT.
  3. Modeling that behavior using our environment model (or box and pointer
    diagram if it can be done that way).
  4. Convert that into code as there is direct mapping between code and model as
    we specified above.

The only difference between this and above modeling of real world object is
that for designing ADT, there is no need to exist in the real world what we want
to capture; if we can imagine what we want, we can capture that in computational
object.

Propagation of Conlstraints

Here we implement the constraint-based programming – non-directional
computations. There are some mechanisms works in this way in the real world –
mechanics of material and so on. This constraint-based programming also known as
logical programming combined with pattern matching. In that paradigm, all the
constraints works as fact. Using such facts, deduce unknown things if
appropriate amounts of fact provided.

To implement such programming style, we apply what we learned in Lecture 5B: Computational Objects:

  • Capture the behavior of real world scheme.

    We could start this using the mechanical example we described; but to begin, the
    simpler example we use, the easier to capture the common behavior of those
    scheme, which leads capturing the abstracted concept of those behavior.

    So here, we use the conversion of temperature between of Fahrenheit and of
    Celsius. The relationship can be formalize as
    [9C = 5(F - 32)]

  • Then we have to capture that as frame of components of ADT or a programming language.

    Here as we develop the programming style, we need to fit our
    components into

    • primitive elements:
      As we develop constraint-based programming our primitive elements would be
      primitive constraints – primitive adder, multiplier, and constant
      constraint
    • means of combination:
      Analogous to the digital circuit simulation, we combine constraints
      connecting by connectors. Connectors work similarly with wires.
    • means of abstraction:
      As we embedding this language into Scheme, it inherit the abstraction means
      of Scheme, namely lambda expression with the define.

    Here we denote the result of connecting several constraints as network. As
    example, the network of the conversion of temperatures given as Figure 3.28. To
    deduce the cloudy objects in our computational world in terms of state and
    references, we need to extract more information from the informal behavior of
    simple examples. Let we inspect the above figure a little more specifically. The
    conversion constraint network consists of primitive adder, multiplier, and
    constant constraints. For that, please see the relevant discussion in the text
    book.

    The important concept is not the description of the behavior but how they
    deduced that description. Our task is to trace back that process to apply our
    own application in later. They nicely captured the common behavior of two
    examples – mechanic law of material and conversion of temperatures – into
    networks of primitive elements – constant, multiplier, and adder boxes. Then
    as they simulation in their mind, they filled the details of the
    requirement. All of these made by wishful thinking: They believe that the
    real world system, such as mechanical system, can be mapped into our
    computational world even though we only know rough behavior of real world
    behavior.

    With all the specification in the text book, we can deduce the cloudy objects
    as connector should have state variable that holds the value of connector and
    gconstraint list of all constraints to which the connector linked; in
    addition, it should have informant as its state variable to achieve the
    requirement. For the primitive constraint boxes, it should have references to
    terminals to poll and set; it should possess identity to signature informant
    part of the connector. It forms the cloudy objects.

  • To deduce the concrete environment diagram,

    here we use test usages to make things more concretely.

    So far, we developed the skeletons of our language, here we will develop more
    concrete contents by specifying how we want to use the constraint system. The
    temperature constraint network converted to

    1. (define C (make-connector))
    2. (define F (make-connector))
    3. (celsius-fahrenheiit-converter C F)

    The real works defined as

    1. (define (celsius-farenheit-converter c f)
    2. (let ((u (make-connector))
    3. (v (make-connector))
    4. (w (make-connector))
    5. (x (make-connector))
    6. (y (make-connector)))
    7. (multiplier c w u)
    8. (multiplier v x u)
    9. (adder v y f)
    10. (constant 9 w)
    11. (constant 5 x)
    12. (constant 32 y)
    13. 'ok))

    As we did in digital circuit simulation, to see what is going on, we probe on
    the connector that we want to know the value when it changed. It should work
    as follows

    1. (probe "Celsius temp" C)
    2. (probe "Fahrenheit temp" F)
    3. (set-value! C 25 'user)
    4. Probe : Celsius temp = 25
    5. Probe : Fahrenheit temp = 77
    6. done

    If we try to set F to a new value, say 212 it should behave as

    1. (set-value! F 212 'user)
    2. Error! Contradiction (77 212)

    That is, the connector complains that it has sensed a contradiction: Its
    value is 77, and someone is trying to set it to 212. If we really want to
    reuse the network with new values, we should tell C to forget its old value
    before setting to new value:

    1. (forget-value! C 'user)
    2. Probe: Celsius temp = ?
    3. Probe: Fahrenheit temp = ?
    4. done

    C finds that the user, who set its value originally, is now retracting
    that value, so C agrees to lose its value, as shown by the probe, and
    informs the rest of the network of this fact. This information eventually
    propagates to F, which now finds that it has no reason for continuing to
    believe that its own value is 77. Thus, F also gives up its value, as shown
    by the probe.

    Now that F has no value, we can safely set it to 212:

    1. (set-value! F 212 'user)
    2. Probe: Fahrenheit temp = 212
    3. Probe: Celsius temp = 100
    4. done
  • Unfortunately, what we deduced from the lecture 5B was wrong!

    You should notice something wrong is happening around the previous section or
    process. What we tried to deduce is not just the environment diagram.
    Environment diagram is not dynamic – it is static as its nature; but what we
    tried to capture is dynamic thing – it evolves as time passes.

    We can only convert the general pattern of the moment of object not the whole
    behavior of particular object, that is the evolution of process, as we capture
    the computation pattern using the substitution model.

    To design the behavior of objects we need to consult our old method, wishful
    thinking in terms of ADT or the core features that a powerful language should
    have with the method of designing algorithm.

    In fact it is not the unfortunate thing, as we have practiced enough to design
    our own ADT (and algorithm) again and again so far. The only difference is that
    now we have to consider the moment of evolution and capture that moment using
    environment model; in the substitution model, we does not considered the moment
    of evolution as there is no time in it. To summarize our abstract process of
    designing what we want is unchanged but the tools have changed.

  • Implementing the constraint system

    So far, we converted the behavior of sample objects into that of our
    computational world. Now we turn our topic into implementing the primitive
    constraints using the ADT of connector by wishful thinking as we did again and
    again, that is we haven’t got the resulting code of connector, that is, we
    just “assume” we have one.

    Here is the specifications for the operations on connectors:

    • (has-value? <connector>)
      tells whether the connector has a value.
    • (get-value <connector>)
      returns the connector’s current value.
    • (set-value! <connector> <new-value> <informant>)
      indicates that the informant is requesting the connector to set its value to
      the new value.
    • (forget-value! <connector> <retractor>)
      tells the connector that the retractor is requesting it to forget its value.
    • (connect <connector> <new-constraint>)
      tells the connector to participate in the new constraint.

    The connectors communicate with the constraints by means of the procedures
    inform-about-value, which tells the given constraint that the connector has a
    value, and inform-about-no-value, which tells the constraint that the
    connector has lost its value.

    Note that by using the wishful thinking we could break down the massive
    structure into smaller ones as we did in the Exercise 2.92. From this process,
    we can learn how to expand our previous designing skill embracing mutation. The
    only difference is that in previous we used to substitution model in conversion
    from common computation to code, here we use environment model to convert the
    picture of object at some moment to code.

    Back to our discourse, adder constructs an adder constraint among summand
    connectors a1 and a2 and a sum connector. To implement this object, we
    think each part composing the whole object separately:

    • To capture the state mutation, and references relations using the environment model,
    • to capture the evolutions using procedure as we did before in the functional programming.

    The behavior of our newly defined primitive constraints are similar of the
    primitive function boxes of digital circuit but this constraint has to have
    identity. That means it should be the procedure with the local state – object
    in message passing style.

    Using those observations we can produce code that do the right things:

    1. (define (adder a1 a2 sum)
    2. (define (process-new-value)
    3. (cond ((and (has-value? a1) (has-value? a2))
    4. (set-value! sum
    5. (+ (get-value a1) (get-value a2))
    6. me))
    7. ((and (has-value? a1) (has-value? sum))
    8. (set-value! a2
    9. (- (get-value sum) (get-value a1))
    10. me))
    11. ((and (has-value? a2) (has-value? sum))
    12. (set-value! a1
    13. (- (get-value sum) (get-value a2))
    14. me))))
    15. (define (process-forget-value)
    16. (forget-value! sum me)
    17. (forget-value! a1 me)
    18. (forget-value! a2 me)
    19. (process-new-value))
    20. (define (me request)
    21. (cond ((eq? request 'I-have-a-value)
    22. (process-new-value))
    23. ((eq? request 'I-lost-my-value)
    24. (process-forget-value))
    25. (else
    26. (error "Unknown request -- ADDER" request))))
    27. (connect a1 me)
    28. (connect a2 me)
    29. (connect sum me)
    30. me)

    The following “syntax interfaces” are used in conjunction with the dispatch:

    1. (define (inform-about-value constraint)
    2. (constraint 'I-have-a-value))
    3. (define (inform-about-no-value constraint)
    4. (constraint 'I-lost-my-value))

    The adder’s local procedure processs-new-value is called when the adder is
    informed that one of its connectors has a value. It evolves as what the behavior
    does. The only non-trivial process is the process-forget-value it works if the
    adder is told that one of its connectors has lost a value, it requests that all
    of its connectors now lose their value as desired but now it runs
    process-new-value. In adder, we could not grasp the essence why it needs to
    be done like that but it becomes somewhat more apparently when we consider the
    multiplier constraint:

    1. (define (multiplier m1 m2 product)
    2. (define (process-new-value)
    3. (cond ((or (and (has-value? m1) (= (get-value m1) 0))
    4. (and (has-value? m2) (= (get-value m2) 0)))
    5. (set-value! product 0 me))
    6. ((and (has-value? m1) (has-value? m2))
    7. (set-value! product
    8. (* (get-value m1) (get-value m2))
    9. me))
    10. ((and (has-value? product) (has-value? m1))
    11. (set-value m2
    12. (/ (get-value product) (get-value m1))
    13. me))
    14. ((and (has-value? product) (has-value? m2))
    15. (set-value m1
    16. (/ (get-value product) (get-value m2))
    17. me))))
    18. (define (process-forget-value)
    19. (forget-value! product me)
    20. (forget-value! m1 me)
    21. (forget-value! m2 me)
    22. (process-new-value))
    23. (define (me request)
    24. (cond ((eq? request 'I-have-a-value)
    25. (process-new-value))
    26. ((eq? request 'I-lost-my-value)
    27. (process-forget-value))
    28. (else
    29. (error "Unknown request -- MULTIPLIER" request))))
    30. (connect m1 me)
    31. (connect m2 me)
    32. (connect product me)
    33. me)

    Think about the situation where m1 equals to 0 and also m2 equals to 0
    changed to m2 got changed its value to not 0; sum still have to hold 0 as
    one of the multiplicand is 0. This is why we need to run process-new-value at
    the end of process-forget-value. So, for the adder case, we think this
    additional complexity is for consistency through whole system.

    For the other primitives, please refer the contents of text book.

  • Representing connector

    As we outlined above, connector is represented in our computational world as a
    procedural object with local state variables value, the current value of the
    connector; informant, the object that set the connector’s value; and
    constraints, a list of the constraints in which the connector participates.

    1. (define (make-connector)
    2. (let ((value false) (informant false) (constraints '()))
    3. (define (set-my-value newval setter)
    4. (cond ((not (has-value? me))
    5. (set! value newval)
    6. (set! informant setter)
    7. (for-each-except setter
    8. inform-about-value
    9. constraints))
    10. ((not (= value newval))
    11. (error "Contradiction" (list value newval)))
    12. (else 'ignored)))
    13. (define (forget-my-value retractor)
    14. (if (eq? retractor informant)
    15. (begin (set! informant false)
    16. (for-each-except retractor
    17. inform-about-no-value
    18. constraints))
    19. 'ignored))
    20. (define (connect new-constraint)
    21. (if (not (memq new-constraint constraints))
    22. (set! constraints
    23. (cons new-constraint constraints)))
    24. (if (has-value? me)
    25. (inform-about-value new-constraint))
    26. 'done)
    27. (define (me request)
    28. (case request
    29. ((has-value?) (if informant true false))
    30. ((value) value)
    31. ((set-value!) set-my-value)
    32. ((forget) forget-my-value)
    33. ((connect) connect)
    34. (else (error "Unknown operation -- CONNECTOR"
    35. request))))
    36. me))
    37. (define (for-each-except exception procedure list)
    38. (let loop ((items list))
    39. (cond ((null? items)
    40. 'done)
    41. ((eq? (car items) exception) (loop (cdr items)))
    42. (else (procedure (car items))
    43. (loop (cdr items))))))

    Then syntax interfaces:

    1. (define (has-value? connector)
    2. (connector 'has-value?))
    3. (define (get-value connector)
    4. (connector 'value))
    5. (define (set-value! connector new-value informant)
    6. ((connector 'set-value!) new-value informant))
    7. (define (connect connector new-constraint)
    8. ((connector 'connect) new-constraint))
  • Exercise 3.33

    We can easily implement requested that is analogous to temperature conversion
    system:

    1. (define (averager a b c)
    2. (let ((x (make-connector))
    3. (y (make-connector)))
    4. (adder a b x)
    5. (multiplier c y x)
    6. (constant 2 y)
    7. 'ok))

    Test:

    1. (define a (make-connector))
    2. ;Value: a
    3. (define b (make-connector))
    4. ;Value: b
    5. (define c (make-connector))
    6. ;Value: c
    7. (averager a b c)
    8. ;Value: ok
    9. (probe 'average c)
    10. ;Value: #[compound-procedure 43 me]
    11. (set-value! a 2 'user)
    12. ;Value: done
    13. (set-value! b 3 'user)
    14. Probe: average = 5/2
    15. ;Value: done
  • Exercise 3.34

    We want to implement the squarer which is the constraint version of square
    procedure. Louis Reasoner builds this constraint using the multipliter:

    1. (define (squarer a b)
    2. (multiplier a a b))

    Unfortunately, it does not work as desired: It only works from a to b not the
    other way around:

    1. (define a (make-connector))
    2. ;Value: a
    3. (define b (make-connector))
    4. ;Value: b
    5. (probe 'a a)
    6. ;Value: #[compound-procedure 42 me]
    7. (probe 'b b)
    8. ;Value: #[compound-procedure 43 me]
    9. (squarer a b)
    10. ;Value: #[compound-procedure 44 me]
    11. (set-value! a 4 'user)
    12. Probe: b = 16
    13. Probe: a = 4
    14. ;Value: done
    15. (forget-value! a 'user)
    16. Probe: b = ?
    17. Probe: a = ?
    18. ;Value: done
    19. (set-value! b 16 'user)
    20. Probe: b = 16
    21. ;Value: done
  • Exercise 3.35

    1. (define (squarer a b)
    2. (define (process-new-value)
    3. (cond ((has-value? b)
    4. (if (< (get-value b) 0)
    5. (error "sqaure less than 0 -- SQUARER" (get-value b))
    6. (set-value! a (sqrt b) me)))
    7. ((has-value? a)
    8. (set-value! b (square a) me))))
    9. (define (process-forget-value)
    10. (forget-value! a me)
    11. (forget-value! b me)
    12. (process-new-value))
    13. (define (me request)
    14. (case request
    15. ((I-have-a-value) (process-new-value))
    16. ((I-lost-my-value) (process-forget-value))
    17. (else
    18. (error "Unknown request -- SQUARER" request))))
    19. (connect a me)
    20. (connect b me)
    21. me)

    Test:

    1. (define a (make-connector))
    2. ;Value: a
    3. (define b (make-connector))
    4. ;Value: b
    5. (probe 'a a)
    6. ;Value: #[compound-procedure 39 me]
    7. (probe 'b b)
    8. ;Value: #[compound-procedure 40 me]
    9. (squarer a b)
    10. ;Value: #[compound-procedure 41 me]
    11. (set-value! a 4 'user)
    12. Probe: b = 16
    13. Probe: a = 4
    14. ;Value: done
    15. (forget-value! a 'user)
    16. Probe: b = ?
    17. Probe: a = ?
    18. ;Value: done
    19. (set-value! b 4 'user)
    20. Probe: a = 2
    21. Probe: b = 4
    22. ;Value: done
  • Exercise 3.36

    I’ve done this with my digital paper.

  • Exercise 3.37

    Using the expression-oriented style of definition, we can rewrite the
    celsius-fahrenheit-converter procedure as follows:

    1. (define (celsius-fahrenheit-converter x)
    2. (c+ (c* (c/ (cv 9) (cv 5))
    3. x)
    4. (cv 32)))
    5. (define C (make-connector))
    6. (define F (celsius-fahrenheit-converter C))

    Here c+, c*, etc. are the “constraint” versions of the arithmetic
    operations. For example, c+ takes two connectors as arguments and returns a
    connector that is related to these by an adder constraint:

    1. (define (c+ x y)
    2. (let ((z (make-connector)))
    3. (adder x y z)
    4. z))

    Then, we can define c-, c*, c/, and cv (constant value) analogous to
    c+:

    1. (define (c- s x)
    2. (let ((y (make-connector)))
    3. (adder x y s)
    4. y))
    5. (define (c* x y)
    6. (let ((z (make-connector)))
    7. (multiplier x y z)
    8. z))
    9. (define (c/ z x)
    10. (let ((y (make-connector)))
    11. (multiplier x y z)
    12. y))
    13. (define (cv num)
    14. (let ((x (make-connector)))
    15. (constant num x)
    16. x))

    Test:

    1. (define (celsius-fahrenheit-converter x)
    2. (c+ (c* (c/ (cv 9) (cv 5))
    3. x)
    4. (cv 32)))
    5. (define C (make-connector))
    6. (define F (celsius-fahrenheit-converter C))
    7. (set-value! f 212 'user)
    8. ;Value: done
    9. (probe 'f f)
    10. Probe: f = 212
    11. ;Value: #[compound-procedure 42 me]
    12. (probe 'c c)
    13. Probe: c = 100
    14. ;Value: #[compound-procedure 43 me]

Concurrency: Time Is of the Essence

The Nature of Time in Concurrent Systems

  • Exercise 3.38 ** a.

    Note that as Peter → Paul results in same as Paul → Peter, There are only
    four of cases that differs each other:

    • M → P → P – $40;
    • Peter → M → Paul – $35;
    • Paul → M → Peter – $50;
    • P → P → M – $45;

    Here we abbreviated Marry as M, Peter or Paul as P when it doesn’t matter
    whether Peter or Paul.

    • b.

      Note that the timing of the accessing the shared variable & mutating that only
      matters here. For simplicity (and for the discussion), we only consider the
      situation where mutation can not occur at the very same time – if we allow
      this, then the discussion would be bogged down to bit mutation level. Then our
      concurrent process can be grouped as

      • the three people accessing the same value (at the same timing) of the shared
        variable (the three people try to mutate the shared variable concurrently);
      • two of them accessing the same value (at the same timing) of the shared
        variable (two of them try to mutate concurrently);
      • each of them accessing the shared variable without interleaving with others
        (the a. cases).

      If we draw diagram relating these concurrent process, the first case of outlined
      above equivalent to only one person of them participate to the mutation, the
      second is two of them participate, and the last one participate all of them. As
      consequence the number of different situation here is 3 + 5 + 4 = 12. That is,

      • first case: - Peter: $110
        • Paul: $80
        • Marry: $50
      • second case: - P → P: $90
        • M → Peter: $60
        • M → Paul: $30
        • Paul → M: $40
        • Peter → M: $55
      • last case (a.): - M → P → P – $40;
        • Peter → M → Paul – $35;
        • Paul → M → Peter – $50;
        • P → P → M – $45;

      Here we stroke out the duplicated result; overall number of different result is 10.

Mechanisms for Controlling Concurrency

There are several strategy to control the concurrency. In the text book, they
implemented serializer, which works as station where issues permission to
operate. Then each of procedure who can mutate shared variable should be
registered by the same serializer to prevent the interleaving process. We can
implement this behavior following the steps of previous section.

There are several implementation details that we can choose for our own:

  • Serializer should have state variable that stores all the procedures that
    registered to self? Actually the way of text book’s have chosen reverse way –
    the procedures have to reference the serializer to be controlled.
  • How the serializer permit specific procedure to run? We could have done this
    using the massage-passing paradigm:

    img

    However it is not the way of the book’s; they make the serializer
    “busy-waiting”. If we implement this asynchronous way, we can make our
    serializing scheme more efficient. But, here we only consider what way the
    book driving for the simplicity.

  • Exercise 3.39

    Since s, now control only one of two execution, not the all of them, and the
    controlled procedure never executed twice that are concurrently; thus the
    serializer does not work in this situation – make no difference at all from the
    previous unserialized version. Consequently remain all of the previous cases.

  • Exercise 3.40

    1. (define x 10)
    2. (parallel-execute (lambda () (set! x (* x x)))
    3. (lambda () (set! x (* x x x))))

    We can capture above situation abstractly:

    • access x1 → access x2 → set x3 to (* x1 x2); let we
      abbreviate this sequence of events as (x, y, z).
    • access x1 → access x2 → access x3 → set x4 to (* x1 x2
      x3); let we abbreviate this sequence of events as (a, b, c, d)

    We used subscriptions or superscriptions to capture the times to access the
    variable x. Then the resulting sequence of execution should be an sequence
    that contains all of the elements of above sequences – x, y, z, a, b, c, d in
    some order with constraints that elements of each sequence should be preserve
    the relative order. And note that only mutation (assignment) make the context
    different; that is, the number of cases that possibly produce different result
    are the number of ways of organizing (0, 0, 0) with 1 plus (1, 1, 1, 1) with 0
    minus 2 (duplicated cases among those – (1, 0) and (0, 1)) – 7 cases:

    • (0, 0, 0, 1): 106
    • (0, 0, 1, 0): 102
    • (0, 1, 0, 0): 104
    • (1, 0, 0, 0): 106
    • (1, 1, 1, 0, 1): 103
    • (1, 1, 0, 1, 1): 104
    • (1, 0, 1, 1, 1): 105

    We represented the (x, y, z) sequence as (0, 0, 0) similarly (1, 1, 1, 1) for
    (a, b, c, d); so the last element of each sequence represent the assignment
    statement. Also in the combined sequences, we only noted one element of other
    sequence such as (0, 0, 0, 1) – here 1 represent the assignment of latter
    sequence. – as only assignment make context different.

    If we use the serializer here, the only two case survives – (0, 1) and (1, 0)
    both results to 106.

  • Exercise 3.41

    The Ben Bitdiddle’s observation is not correct: As the balance method only
    access to the shared state variable not mutating it, this method calling could
    not make any different context (can not alter the environment) at all; recall
    that the anomalous behavior we concerned here is the interleaving processes
    which do access the shared variable and based on that, mutate that variable.

  • Exercise 3.42

    To answer this question, we need to inspect the behavior of serializer more
    formally. If we interpret the informal specification described in the text book
    as only one of evaluation can be processed among the set of procedures
    associated with that serializer, then the modification made by Ben Bitdiddle
    results to the same behavior as previous one.

    On the other hand, if it means only one of procedure, whose instance gets
    permission from the serializer, allowed to be executed, then it results in the
    instances (processes) of that procedure allowed to be processed concurrently.
    That causes the chaos in the banking system cause it will allow interleaving
    processes.

    So it is good convention to define the banking account procedure as original one
    since it does not depend on the interpretation of the specification, which is
    the implementation details.

  • Exercise 3.43

    As it is evaluated sequentially, all we need to show is that if before
    sequential execution specific condition holds, then after which it still holds.
    And this assertion can be proved as choose arbitrary one of the execution of
    element of sequence; then prove if the a specific condition holds before that
    execution, it still holds after that.

    By setting the specific condition as the account balances should be $10, $20,
    and $30 after the sequential execution, we got what we want.

    This condition can be violated allowing the interleaving exchange. I’ve drawn
    the figure describing this situation in the digital paper.

    Now we try to prove even this situation, the total balance of accounts are
    conserved. Basically, the structure of our proof is same as outlined above. We
    decompose the goal into about the each exchange execution, and then into the
    individual execution of withdraw or deposit. About the decomposed goal, we
    have the lemma-like specification in the book, so if we formalize that
    observation, we are done – completion of proof. To prove the final decomposed
    goal, we need to incorporate the user – the caller of exchange – to interact
    or possess the balance – difference.

    For the last request, I’ve drawn diagram in the digital paper.

  • Exercise 3.44

    As we noted in exercise 3.42, the anomalous behavior can be introduced only by
    consequent access to value & mutating that variable. In the previous exchange
    procedure, we accessed each account to calculate the difference of those
    balances then mutated each account appropriately. That is, the anomaly involved each
    account as shared variable. Whereas in our situation, we do not access the
    values to mutate the account; in other word our mutation does not depend on the
    shared variable. So our execution sequence does not satisfy the condition of
    anomalous behavior.

  • Exercise 3.45

    Ben Bitdiddle’s introduces not halting process – due to the so called
    “deadlock”: Our serialized exchange process locks the serializer until it
    terminates all of the statement of its body; however the withdrawal (or deposit)
    process also require the permission of the same serializer otherwise it can not
    proceed, which would be free after that withdrawal (or deposit) process
    terminate. This situation end up with interlocked process, does not terminate at all.

  • Exercise 3.46

    I’ve drawn required in my digital paper.

  • Exercise 3.47

    • a.

      To implement the semaphore using the mutex, here we modify the definition of
      mutex not to make the caller of acquire method of that to busy wait.

      1. ;; modifed mutex -- to fit in the semaphore
      2. (define (make-mutex)
      3. (let ((cell (list false)))
      4. (define (the-mutex m)
      5. (case m
      6. ((acquire) (test-and-set! cell))
      7. ((release) (clear! cell))))
      8. the-mutex))
      9. (define (clear! cell)
      10. (set-car! cell false))
      11. (define (test-and-set! cell)
      12. (without-interrupts
      13. (lambda ()
      14. (if (car cell)
      15. true
      16. (begin (set-car! cell true)
      17. false)))))

      Then the previous make-serializer can be implemented as

      1. (define (make-serializer)
      2. (let ((mutex (make-mutex)))
      3. (lambda (p)
      4. (define (serialized-p . args)
      5. (if (mutex 'acquire)
      6. (mutex 'acquire)) ;retry
      7. (let ((val (apply p args)))
      8. (mutex 'release)
      9. val))
      10. serialized-p)))

      Armed with this modified mutex, we can implement semaphore:

      1. (define (make-semaphore n)
      2. (define (make-mutex-chain n)
      3. (if (zero? n)
      4. '()
      5. (cons (make-mutex)
      6. (make-mutex-chain (-1+ n)))))
      7. (let ((mutexes (make-cycle (make-mutex-chain n))))
      8. (define (the-semaphore request)
      9. (case request
      10. ((acquire)
      11. (let loop ((current-cycle mutexes))
      12. (let ((mutex (car current-cycle)))
      13. (if (mutex 'acquire)
      14. (loop (cdr current-cycle))
      15. mutex))))
      16. (else (error "Unknown request -- MAKE-SEMAPHORE" request))))
      17. the-semaphore))

      Then the serializer with n concurrent procedure:

      1. (define (make-semaphore n)
      2. (define (make-mutex-chain n)
      3. (if (zero? n)
      4. '()
      5. (cons (make-mutex)
      6. (make-mutex-chain (-1+ n)))))
      7. (let ((mutexes (make-cycle (make-mutex-chain n))))
      8. (define (the-semaphore request)
      9. (case request
      10. ((acquire)
      11. (let loop ((current-cycle mutexes))
      12. (let ((mutex (car current-cycle)))
      13. (if (mutex 'acquire)
      14. (loop (cdr current-cycle))
      15. mutex))))
      16. (else (error "Unknown request -- MAKE-SEMAPHORE" request))))
      17. the-semaphore))
    • b.

      With atomic test-and-set! it becomes more concise – the semaphore only need
      to keep track the number of current concurrent procedures:

      1. (define (make-semaphore2 n)
      2. (let ((cell (list 0)))
      3. (define (test-and-set!)
      4. (without-interrupts
      5. (lambda ()
      6. (if (< (car cell) n)
      7. (begin (set-car! cell (1+ (car cell)))
      8. false)
      9. true))))
      10. (define (clear!)
      11. (without-interrupts
      12. (lambda ()
      13. (set-car! cell (-1+ (car cell))))))
      14. (define (the-semaphore request)
      15. (case request
      16. ((acquire)
      17. (test-and-set!))
      18. ((release)
      19. (clear!))
      20. (else
      21. (error "Unknown reuqest -- MAKE-SEMAPHORE2" request))))
      22. the-semaphore))
      23. (define (make-serializer-with2 n)
      24. (let ((the-semaphore (make-semaphore2 n)))
      25. (lambda (p)
      26. (define (serialized-p . args)
      27. (if (the-semaphore 'acquire)
      28. (the-semaphore 'acquire))
      29. (let ((val (apply p args)))
      30. (the-semaphore 'release)
      31. val))
      32. serialized-p)))
  • Exercise 3.48

    As noted in the text, deadlock occur in the situation where each process needs
    to get permission of every permission from specified serializer set to execute,
    but each permission doesn’t have to be acquired in specific order. Thus several
    process can get some number of permissions concurrently, which leads interlocked
    processes.

    The given deadlock-avoidance method constrain each process needs to get
    permission in specific order; that means the interlocked situation can not occur
    at all since to occur such situation, one should break the ordering constraint.

    To implement this idea, first we need to modify make-account-and-serializer to
    issue identification number to each account:

    1. (define make-account-and-serializer
    2. (let ((id 0))
    3. (lambda (balance)
    4. (let ((id (begin (set! id (1+ id))
    5. id)))
    6. (define (withdraw amount)
    7. (if (>= balance amount)
    8. (begin (set! balance (- balance amount))
    9. balance)
    10. "Insufficient funds"))
    11. (define (deposit amount)
    12. (set! balance (+ balance amount))
    13. balance)
    14. (let ((balance-serializer (make-serializer)))
    15. (define (dispatch m)
    16. (case m
    17. ((withdraw) withdraw)
    18. ((deposit) deposit)
    19. ((balance) balance)
    20. ((serializer) balance-serializer)
    21. ((id) id)
    22. (else (error "Unknown request -- MAKE-ACCOUNT" m))))
    23. dispatch)))))

    Then test the id feature:

    1. (define a (make-account-and-serializer 100))
    2. (define b (make-account-and-serializer 100))
    3. (a 'id)
    4. ;Value: 1
    5. (b 'id)
    6. ;Value: 2
    1. (define (serialized-exchange account1 account2)
    2. (let ((id1 (account1 'id))
    3. (id2 (account2 'id))
    4. (serializer1 (account1 'serializer))
    5. (serializer2 (account2 'serializer)))
    6. (if (< id1 id2)
    7. ((serializer1 (serializer2 exchange))
    8. account1
    9. account2)
    10. ((serializer2 (serializer1 exchange))
    11. account2
    12. account1))))
  • Exercise 3.49

    Such scenario should satisfy the selection of the other account depends on the
    value of the shared resource of first account and also it should mutate the
    depending resource in that process.

    We can think of the joint account situation a bit screwed from what discussed in
    the text book – jointing accounts results to making each account reference the
    other account in their state variable. Then now think of the situation where we
    want to exchange the account’s balance of ones with the joint account’s. It
    cannot be avoided by outlined procedure in exercise 3.48.

  • Concurrency, time, and communication

    Let we conclude this section quoting the last paragraph of the text’s this
    section:

    The basic phenomenon here is that synchronizing different processes,
    establishing shared state, or imposing an order on events requires communication
    among the processes. In essence, any notion of time in concurrency control must
    be intimately tied to communication. It is intriguing that a similar connection
    between time and communication also arises in the Theory of Relativity, where
    the speed of light (the fastest signal that can be used to synchronize events)
    is a fundamental constant relating time and space. The complexities we encounter
    in dealing with time and state in our computational models may in fact mirror a
    fundamental complexity of the physical universe.

Streams

Here we approach the modeling of real world in different angle: We try to
capture time as sequence of instants; that is analogous to the physical
interpretation of world as 4 dimension – time axis with 3D space.

With this view point, we can use our sequence processing interface to process
the streams. In fact, this stream processing is powerful model which enables us
to deal the state without ever using assignment or mutable data.

On the other hand, the stream framework raises difficulties of its own, and the
question of which modeling technique leads to more modular and more easily
maintained systems remains open.

Streams Are Delayed Lists

  • Exercise 3.50

    Use type contract:

    1. ;; (A,...,A -> B), List<Stream<A>> -> Stream<B>
    2. (define (stream-map proc . argstreams)
    3. (if (empty-stream? (car argstreams))
    4. the-empty-stream
    5. (cons-stream
    6. (apply proc (map stream-car argstreams))
    7. (apply stream-map
    8. (cons proc (map stream-cdr argstreams))))))
  • Exercise 3.51

    It should

    1. (define x (stream-map show (stream-enumerate-interval 0 10)))
    2. ;display 0
    3. (stream-ref x 5)
    4. ;display 1, 2, 3, 4, 5
    5. (stream-ref x 7)
    6. ;display 6, 7

    And it did:

    1. (define x (stream-map show (stream-enumerate-interval 0 10)))
    2. 0
    3. ;Value: x
    4. (stream-ref x 5)
    5. 1
    6. 2
    7. 3
    8. 4
    9. 5
    10. ;Value: 5
    11. (stream-ref x 7)
    12. 6
    13. 7
    14. ;Value: 7
  • Exercise 3.52

    Mentally calculated:

    1. (define seq (stream-map accum (stream-enumerate-interval 1 20)))
    2. ;sum = 1
    3. (define y (stream-filter even? seq))
    4. ;sum = 6
    5. (define z (stream-filter (lambda (x) (= (remainder x 5) 0))
    6. seq))
    7. ;sum = 10
    8. (stream-ref y 7)
    9. ;136
    10. ;sum = 136
    11. (display-stream z)
    12. ;10, 15, 45, 55, 105, 120, 190, 210
    13. ;sum = 210

    Actual response:

    1. (stream-ref y 7)
    2. ;Value: 136
    3. (display-stream z)
    4. 10
    5. 15
    6. 45
    7. 55
    8. 105
    9. 120
    10. 190
    11. 210
    12. ;Unspecified return value

    For the latter question: Yes it’ll produce different result, since it now the
    duplicated evaluation aggregate the value of sum by the computation result of
    that additional evaluation.

Infinite Streams

The important aspect of streams that differs from previous processes –
iterative, recursive – is that streams unfolds the specified process while the
others folding the given argument; that is, streams make more complex things as
it evolves, whereas the others simplifying those.

Observe that procedure dealing with streams usually capture the differential
equation as we’ll learn from 6.01.

  • Exercise 3.53

    This stream captures the differential equation given by

    \begin{matrix}
    s[n] =& s[n-1] + s[n-1] =& 2s[n-1]\
    s[0] =& 0
    \end{matrix}

    So, by solving the deduced equation, we got (s[n] = 2^{n}).

  • Exercise 3.54

    The differential equation we want:

    \begin{matrix}
    f[n] =& f[n-1] \times int[n + 1]\
    f[0] =& 1
    \end{matrix}

    The converted code:

    1. (define (mul-streams s1 s2)
    2. (stream-map * s1 s2))
    3. (define factorials (cons-stream 1 (mul-streams (integers-starting-from 2)
    4. factorials)))
  • Exercise 3.55

    The differential equation we want:

    \begin{matrix}
    f[n] =& f[n-1] + s[n]\
    f[0] =& s[0]
    \end{matrix}

    Then the code:

    1. (define (partial-sums s)
    2. (define partials
    3. (cons-stream (stream-car s)
    4. (add-streams (stream-cdr s) partials)))
    5. partials)

    Then test:

    1. (define test-partial-sums (partial-sums integers))
    2. (display-stream test-partial-sums)
    3. 1
    4. 3
    5. 6
    6. 10
    7. 15
    8. 21
    9. 28
    10. 36
    11. 45
    12. 55
    13. ...
  • Exercise 3.56

    We need to merge all of (scale-stream s 2), (scale-stream s 3),
    (scale-stream s 5):

    1. (define S (cons-stream 1
    2. (merge (scale-stream s 2)
    3. (merge (scale-stream s 3)
    4. (scale-stream s 5)))))

    Then test:

    1. (stream-ref s 100)
    2. ;Value: 1600
    3. s
    4. ;Value: {1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 ...
  • Exercise 3.57

    We answers to the questions in order:

    1. The number of additions results to (n-2) since
      • base case: 3th fibs needs one addition.
      • inductive case: nth fibs needs # of additions of n-1th fibs + 1.
    2. The number of the latter would be greater than 2n/2 - 1 since

      • [T(3) = 1]
      • [T(n) = T(n-1) + T(n-2) + 1]

      These equation can be deduced as it does not involve any assignment
      explicitly & implicitly; so the evaluation does not depends on context, that
      is to the result of (T(n-2)) after evaluating (T(n-1)) same as (T(n-2))
      before evaluating (T(n-1)).

  • Exercise 3.58

    1. (define (expand num den radix)
    2. (cons-stream
    3. (quotient (* num radix) den)
    4. (expand (remainder (* num radix) den) den radix)))

    It produce the sequence representing radix point number expansion of rational
    number (\frac{num}{den}).

    Tests:

    1. (define 1over7 (expand 1 7 10))
    2. ;Value: |1over7|
    3. 1over7
    4. ;Value: {1 ...}
    5. (stream-ref 1over7 10)
    6. ;Value: 5
    7. 1over7
    8. ;Value: {1 4 2 8 5 7 1 4 2 8 5 ...}
    9. (define 3over8 (expand 3 8 10))
    10. ;Value: |3over8|
    11. (stream-ref 3over8 10)
    12. ;Value: 0
    13. 3over8
    14. ;Value: {3 7 5 0 0 0 0 0 0 0 0 ...}
  • Exercise 3.59

    • a.

      The code do the right things:

      1. (define (integrate-series s)
      2. (stream-map (lambda (s i) (/ s i)) s integers))

      And test:

      1. (define test-integrate-series
      2. (integrate-series ones))
      3. test-integrate-series
      4. ;Value: {1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9 1/10 1/11 ...}
    • b.

      Here we using following observation:

      1. (cons-stream (stream-car series), (integrate-series (deriv series))) = series

      So, the answers can be implemented as

      1. (define cosine-series
      2. (cons-stream 1 (integrate-series (stream-map - sine-series))))
      3. (define sine-series
      4. (cons-stream 0 (integrate-series cosine-series)))
  • Exercise 3.60

    Let S2 be ({a{0}, a{1}, \ldots}) then (mul-series S1 S2) should equals
    to (a{0} S{1} + x S{1} \times {a{1}, a_{2}, \ldots}).

    From above observation, we can deduce

    1. (mul-series s1 s2)
    2. = (add-series (scale-stream s1 (stream-car s2))
    3. (shift (mul-series s1 (stream-cdr s2))))
    4. = (let ((a0s1 (scale-stream s1 (stream-car s2))))
    5. (cons-stream
    6. (stream-car a0s1)
    7. (add-series (stream-cdr a0s1)
    8. (mul-series s1 (stream-cdr s2)))))

    Then the result:

    1. (define (mul-series s1 s2)
    2. (let ((a0s1 (scale-stream s1 (stream-car s2))))
    3. (cons-stream
    4. (stream-car a0s1)
    5. (add-series (stream-cdr a0s1)
    6. (mul-series s1 (stream-cdr s2))))))

    Then test:

    1. ;; test for sine, cosine series with mul-series
    2. (define (square-series s)
    3. (mul-series s s))
    4. (define test-trigonometric-stream
    5. (add-series (square-series sine-series)
    6. (square-series cosine-series)))
    7. test-trigonometric-stream
    8. ;Value: {1 0 0 0 0 0 ...}
  • Exercise 3.61

    The straight forward implementation:

    1. (define (invert-unit-series s)
    2. (define X (cons-stream
    3. (stream-car s)
    4. (mul-series
    5. (stream-cdr s)
    6. (stream-map - X))))
    7. X)

    Then test:

    1. (define test-invert-series
    2. (invert-unit-series test-integrate-series))
    3. test-invert-series
    4. ;Value: {1 -1/2 -1/12 -1/24 -19/720 -3/160 -863/60480 -275/24192 -33953/3628800 -8183/1036800 -3250433/479001600 ...}

    The problem is I don’t know the result of the test case is right. Let this test
    delegate to next exercise.

  • Exercise 3.62

    Here we use following observation:

    \begin{align}
    &{n{0}, n{1}, \ldots} / {d{0}, d{1}, \ldots}\
    =&\frac{{n{0}, n{1}, \ldots}/d{0}}{{1, d{1}/d{0}, d{2}/d_{0}, \ldots}}
    \end{align
    }

    It results into

    1. (define (div-series ns ds)
    2. (let ((d0 (stream-car ds)))
    3. (cond ((zero? d0)
    4. (error "Zero division -- DIV-SERIES" ds))
    5. (else
    6. (let ((ns/d0 (stream-map (lambda (n) (/ n d0)) ns))
    7. (ds/d0 (stream-map (lambda (d) (/ d d0)) ds)))
    8. (mul-series
    9. ns/d0
    10. (invert-unit-series ds/d0)))))))

    Then test:

    1. (define test-div-series
    2. (div-series sine-series sine-series))
    3. ;Zero division -- DIV-SERIES {0 1 0 -1/6 0 ...}
    4. (define test-div-series
    5. (div-series cosine-series cosine-series))
    6. test-div-series
    7. ;Value: {1 0 0 0 0 0 0 0 0 0 0 ...}
    8. ;; tangent-series
    9. (define tangent-series
    10. (div-series sine-series cosine-series))
    11. tangent-series
    12. ;Value: {0 1 0 1/3 0 2/15 0 17/315 0 62/2835 0 ...}

Exploiting the Stream Paradigm

  • Exercise 3.63

    The modified version need to recalculate the subsequent call of (sqrt-stream x) within the body of sqrt-stream whereas the original one just refer the
    precalculated result.

    If we just use call-by-name not call-by-need, the two different version of these
    procedure would hardly differ each other since both should recalculate recursive
    call of expression.

    I’ve deduced this result using environment diagram. You should also draw by
    yourself.

  • Exercise 3.64

    This procedure should involve conditional branch:

    1. (define (stream-limit s tolerance)
    2. (let ((x (stream-car s))
    3. (y (stream-car (stream-cdr s))))
    4. (if (< (abs (- x y)) tolerance)
    5. y
    6. (stream-limit (stream-cdr s) tolerance))))
  • Exercise 3.65

    It is analogous to pi-stream in text book:

    1. (define (ln2-summands n)
    2. (cons-stream (/ 1.0 n)
    3. (stream-map -
    4. (ln2-summands (1+ n)))))
    5. (define ln2-stream
    6. (accelerated-sequence
    7. euler-transform (partial-sums (ln2-summands 1))))

    Then let’s test it together with previous exercise:

    1. (define ln2 (stream-limit ln2-stream 1e-8))
    2. ln2
    3. ;Value: .6931471805604039

    It seems reasonable.

  • Exercise 3.66

    To answer the question, we need to formulate the mapping rule of pairs, that
    is, the relation between the pair of index of s with t and that the index of
    (pairs s t). That, in turns, involves the mapping rule of interleave.

    Let we start with the easy one: The mapping rule of interleave. Let we denote
    i as the i-th element of S1 and similarly j for j-th element of
    S2. Then we deduce the relation between i or j and the index of that
    element in the (interleave s1 s2) as

    \begin{equation}
    \left{
    \begin{matrix}
    i \to& 2i - 1\
    j \to& 2j
    \end{matrix}
    \right.
    \end{equation
    }

    Using this, we can construct the mapping rule of pairs:

    \begin{equation}
    f(x,y) =
    \left{
    \begin{matrix}
    1,1 &\to& 1\
    1,i+1 &\to& (2i - 1) + 1\
    j+1, k+1 &\to& 2 f(j,k) +1
    \end{matrix}
    \right.
    \end{equation
    }

    By solving this equation, we can deduce the closed form of that:

    \begin{align}
    f(n, n+m) &= 2^{n-1}f(1, 1 + m) + 2^{n-1} - 1\
    &= 2^{n-1} (2m + 1) -1
    \end{align
    }

    Consequently, we got the precise mathematical statements about the order mapping
    rule of pairs. Using this, we can calculate the given test cases as

    \begin{align}
    f(1,100) &= 2^{0} (2\times 99 + 1) - 1\
    &= 198\
    f(99,100) &= 2^{98} (2\times 1 + 1) - 1\
    &= 2^{98}\times 3 - 1\
    f(100, 100) &= 2^{100} -1
    \end{align
    }

    Let’s verify above results:

    1. (define test-order-of-pairs (pairs integers integers))
    2. (stream-ref test-order-of-pairs (-1+ 198))
    3. ;Value: (1 100)
    4. (stream-ref test-order-of-pairs (-1+ (-1+ (* 3 (expt 2 8)))))
    5. ;Value: (9 10)
    6. (stream-ref test-order-of-pairs (-1+ (-1+ (expt 2 10))))
    7. ;Value: (10 10)

    Here we replaced last two cases as (f(9, 10)) and (f(10, 10)) respectively
    since, as we computed the order of given pair order, the input order number
    turns out so enormous that we can not wait the calculation.

  • Exercise 3.67

    It is analogous to pairs:

    1. (define (all-pairs s t)
    2. (cons-stream
    3. (list (stream-car s) (stream-car t))
    4. (interleave
    5. (stream-map (lambda (x) (list (stream-car s) x))
    6. (stream-cdr t))
    7. (interleave
    8. (stream-map (lambda (x) (list x (stream-car t)))
    9. (stream-cdr s))
    10. (all-pairs (stream-cdr s) (stream-cdr t))))))

    Then test:

    1. (define test-all-pairs (all-pairs integers integers))
    2. test-all-pairs
    3. ;Value: {(1 1) (1 2) (2 1) (1 3) (2 2) (1 4) (3 1) (1 5) (2 3) (1 6) (4 1) (1 7) (3 2) (1 8) (5 1) (1 9) (2 4) (1 10) (6 1) (1 11) (3 3) ...}
  • Exercise 3.68

    The implementation of Reasoner would fall into infinite loop since evaluating the
    pairs, in turns, execute another pairs with different arguments but without
    delay, which execute another pairs, and so on.

    Why this version execute pairs successively whereas the original one didn’t?
    It is due to the evaluation order: Applicative order is default in Scheme.

  • Exercise 3.69

    Let we simply the problem as we did in designing pairs; let we think integer
    triples. If we could implement the integer triples generally enough then that
    process automatically cope with the stream of triples of S, T, U.

    Observe the all the elements after ((i,j)) of int-pairs satisfy that each part of
    pair is greater than or equals to i. Using this observation we can implement
    int-triples as follows

    1. (define int-triples
    2. (define (make-int-triples integers int-pairs)
    3. (cons-stream
    4. (cons (stream-car integers) (stream-car int-pairs))
    5. (interleave
    6. (stream-map (lambda (x) (cons (stream-car integers) x))
    7. (stream-cdr int-pairs))
    8. (make-int-triples (stream-cdr integers) (stream-cdr int-pairs)))))
    9. (make-int-triples integers int-pairs))

    You should notice the similarity with pairs since I’ve used that structure
    here. Given that we can generalize as

    1. (define (triples s t u)
    2. (define pair-s (pairs t u))
    3. (cons-stream
    4. (cons (stream-car s) (stream-car pair-s))
    5. (interleave
    6. (stream-map (lambda (x) (cons (stream-car s) x))
    7. (stream-cdr pair-s))
    8. (triples (stream-cdr s) (stream-cdr t) (stream-cdr u)))))

    Then the final request can be achieved as

    1. (define pythagorean-triples
    2. (stream-filter
    3. (lambda (t)
    4. (let ((squared-triple (map square t)))
    5. (= (+ (car squared-triple)
    6. (cadr squared-triple))
    7. (caddr squared-triple))))
    8. (triples integers integers integers)))

    Then test:

    1. (display-stream pythagorean-triples)
    2. (3 4 5)
    3. (6 8 10)
    4. (5 12 13)
    5. (9 12 15)
    6. (8 15 17)
    7. (12 16 20)
  • Exercise 3.70

    Merge-weighted is like merge except the weight:

    1. (define (merge-weighted weight s1 s2)
    2. (cond ((stream-null? s1) s2)
    3. ((stream-null? s2) s1)
    4. (else
    5. (let ((s1car (stream-car s1))
    6. (s2car (stream-car s2)))
    7. (let ((s1w (apply weight s1car))
    8. (s2w (apply weight s2car)))
    9. (cond ((< s1w s2w)
    10. (cons-stream s1car (merge-weighted weight (stream-cdr s1) s2)))
    11. (else
    12. (cons-stream s2car (merge-weighted weight s1 (stream-cdr s2))))))))))

    Then weighted-pairs is just pairs using different combiner:

    1. (define (weighted-pairs weight s t)
    2. (cons-stream
    3. (list (stream-car s) (stream-car t))
    4. (merge-weighted
    5. weight
    6. (stream-map (lambda (x) (list (stream-car s) x))
    7. (stream-cdr t))
    8. (weighted-pairs weight (stream-cdr s) (stream-cdr t)))))

    Then

    • a.

      1. (define weighted-as-i+j
      2. (weighted-pairs + integers integers))
      1. (display-stream weighted-as-i+j)
      2. (1 1)
      3. (1 2)
      4. (2 2)
      5. (1 3)
      6. (2 3)
      7. (1 4)
      8. (3 3)
      9. (2 4)
      10. (1 5)
      11. (3 4)
      12. (2 5)
      13. (1 6)
      14. (4 4)
      15. (3 5)
      16. (2 6)
      17. (1 7)
    • b.

      1. (define weighted-filtered-2-3-5
      2. (let ((2-3-5-filtered-integers
      3. (stream-filter
      4. (lambda (i)
      5. (not (or (divisible-by? 2 i)
      6. (divisible-by? 3 i)
      7. (divisible-by? 5 i))))
      8. integers)))
      9. (weighted-pairs
      10. (lambda (i j)
      11. (+ (* 2 i)
      12. (* 3 j)
      13. (* 5 i j)))
      14. 2-3-5-filtered-integers
      15. 2-3-5-filtered-integers)))
      1. (display-stream weighted-filtered-2-3-5)
      2. (1 1)
      3. (1 7)
      4. (1 11)
      5. (1 13)
      6. (1 17)
      7. (1 19)
      8. (1 23)
      9. (1 29)
      10. (1 31)
      11. (7 7)
      12. (1 37)
      13. (1 41)
      14. (1 43)
      15. (1 47)
      16. (1 49)
      17. (1 53)
      18. (7 11)
      19. (1 59)
  • Exercise 3.71

    Here is the procedures that do the right things:

    1. (define (merge-consecutive-weight s weight merger)
    2. (let ((x (stream-car s))
    3. (y (stream-car (stream-cdr s))))
    4. (let ((a (apply weight x))
    5. (b (apply weight y)))
    6. (if (= a b)
    7. (cons-stream
    8. (merger x y)
    9. (merge-consecutive-weight
    10. (stream-cdr s)
    11. weight
    12. merger))
    13. (merge-consecutive-weight
    14. (stream-cdr s)
    15. weight
    16. merger)))))
    17. (define ramanujans
    18. (let* ((weight
    19. (lambda (i j)
    20. (+ (cube i)
    21. (cube j))))
    22. (merger
    23. (lambda (x y) (apply weight x))))
    24. (merge-consecutive-weight
    25. (weighted-pairs weight integers integers)
    26. weight
    27. merger)))

    I’ve implemented merge-consecutive-weight general enough to cover next exercise.

    Here is the first 5 numbers:

    1. ramanujans
    2. ;Value: {1729 4104 13832 20683 32832 ...}

    You should draw Henderson diagram of merge-consecutive-weight to understand
    the process of design.

  • Exercise 3.72

    Here is the a little convolved coding:

    1. (define ramanujan-likes
    2. (let ((weight
    3. (lambda (i j)
    4. (+ (square i) (square j)))))
    5. (merge-consecutive-weight
    6. (merge-consecutive-weight
    7. (weighted-pairs weight integers integers)
    8. weight
    9. list)
    10. (lambda (p1 p2) (apply weight p1))
    11. (lambda (x y)
    12. (append x (cdr y))))))

    Then the results:

    1. (display-stream ramanujan-likes)
    2. ((10 15) (6 17) (1 18))
    3. ((13 16) (8 19) (5 20))
    4. ((17 19) (11 23) (5 25))
    5. ((14 23) (10 25) (7 26))
    6. ((19 22) (13 26) (2 29))
    7. ((15 25) (11 27) (3 29))
    8. ((21 22) (14 27) (5 30))
    9. ((20 25) (8 31) (1 32))
    10. ((23 24) (12 31) (9 32))
    11. ((12 31) (9 32) (4 33))
    12. ((25 25) (17 31) (5 35))
    13. ((20 30) (12 34) (2 36))
    14. ((22 29) (13 34) (10 35))
    15. ((22 31) (17 34) (1 38))
    16. ((19 33) (15 35) (9 37))
    17. ((25 30) (9 38) (2 39))
    18. ((28 29) (20 35) (16 37))
    19. ((20 35) (16 37) (5 40))
    20. ...
  • Exercise 3.73

    1. (define (RC R C dt)
    2. (lambda (I v0)
    3. (add-streams
    4. (scale-stream I R)
    5. (integral (scale-stream I (/ 1.0 C))
    6. v0
    7. dt))))

    Then test using the example required:

    1. (define RC1 (RC 5 1 0.5))
    2. (define zeros (cons-stream 0 zeros))
    3. (define emulate-currents
    4. (stream-append
    5. (list->stream '(0 0.5 1 1 1 1 1 0.5 0))
    6. zeros))
    7. (display-stream (RC1 emulate-currents 1))
    8. 1
    9. 3.5
    10. 6.25
    11. 6.75
    12. 7.25
    13. 7.75
    14. 8.25
    15. 6.25
    16. 4.
    17. 4.
    18. 4.
    19. 4.
    20. 4.
    21. 4.
    22. 4.
    23. 4.
    24. 4.
  • Exercise 3.74

    First we implement sign-change-detector, which was supposed to be implemented
    by Alyssa.

    1. (define (sign-change-detector current last-value)
    2. (cond ((negative? last-value)
    3. (if (negative? current)
    4. 0
    5. ;; 0 is also treated as positive
    6. 1))
    7. (else
    8. ;; 0 is also treated as positive
    9. (if (negative? current)
    10. -1
    11. 0))))

    Then test:

    1. (define test-data
    2. (stream-append
    3. (list->stream '(1 2 1.5 1 0.5 -0.1 -2 -3 -2 -0.5 0.2 3 4))
    4. zeros))
    5. (define test-crossings
    6. (make-zero-crossings test-data 0))
    7. test-crossings
    8. ;Value: {0 0 0 0 0 -1 0 0 0 0 1 0 0 0 ...}

    It correspond to what jotted in the text.

    Then the main task:

    1. (define zero-crossings
    2. (stream-map sign-change-detector
    3. test-data
    4. (cons-stream 0 test-data)))
    5. zero-crossings
    6. ;Value: {0 0 0 0 0 -1 0 0 0 0 1 0 0 0 ...}
  • Exercise 3.75

    In the given implementation, it use last-value in mixed sense: In one place it
    works as last averaged value – previous avpt –; in the others it works as
    last element of sense-data.

    Thus, we can fix the problem by specifying what value the place expect:

    1. (define (make-zero-crossings input-stream last-value last-avpt)
    2. (let ((avpt (/ (+ (stream-car input-stream) last-value) 2)))
    3. (cons-stream (sign-change-detector avpt last-avpt)
    4. (make-zero-crossings (stream-cdr input-stream)
    5. (stream-car input-stream)
    6. avpt))))

    Test:

    1. (define test-crossings2
    2. (make-zero-crossings2 test-data 0 0))
    3. test-crossings2
    4. ;Value: {0 0 0 0 0 0 -1 0 0 0 0 1 0 0 ...}
  • Exercise 3.76

    1. (define (smooth s)
    2. (stream-map average s (stream-cdr s)))

    Then

    1. (define (make-smoothed-zero-crossings input-stream last-value)
    2. (let ((smooted (smooth input-stream)))
    3. (stream-map sign-change-detector
    4. smooted
    5. (cons-stream
    6. last-value
    7. smooted))))
  • Exercise 3.77

    1. (define (integral delayed-integrand initial-value dt)
    2. (cons-stream initial-value
    3. (let ((integrand (force delayed-integrand)))
    4. (if (stream-null? integrand)
    5. the-empty-stream
    6. (integral (delay (stream-cdr integrand))
    7. (+ (* dt (stream-car integrand))
    8. initial-value)
    9. dt)))))
  • Exercise 3.78

    1. (define (solved-2nd a b dt y0 dy0)
    2. (define y (integral (delay dy) y0 dt))
    3. (define dy (integral (delay ddy) dy0 dt))
    4. (define ddy
    5. (add-streams
    6. (scale-stream dy a)
    7. (scale-stream y b)))
    8. y)
  • Exercise 3.79

    1. (define (solved-2nd f dt y0 dy0)
    2. (define y (integral (delay dy) y0 dt))
    3. (define dy (integral (delay ddy) dy0 dt))
    4. (define ddy
    5. (stream-map f dy y))
    6. y)
  • Exercise 3.80

    1. (define (RLC R L C dt)
    2. (lambda (vC0 iL0)
    3. (define vC
    4. (integral (delay dvC) vC0 dt))
    5. (define iL
    6. (integral (delay diL) iL0 dt))
    7. (define dvC
    8. (scale-stream iL (/ -1.0 C)))
    9. (define diL
    10. (add-streams
    11. (scale-stream vC (/ 1.0 L))
    12. (scale-stream iL (/ (- R) L))))
    13. (cons vC iL)))

    Then the test accomplishing the example:

    1. (define RLC1s ((RLC 1 1 0.2 0.1) 10 0))
    2. (display-stream (car RLC1s))
    3. 10
    4. 10
    5. 9.5
    6. 8.55
    7. 7.220000000000001
    8. 5.5955
    9. 3.77245
    10. 1.8519299999999999
    11. -.0651605000000004
    12. -1.8831384500000004
    13. -3.5160605800000004
    14. -4.8915335745
    15. -5.95365624055
    16. -6.66498996127
    17. -7.0075074978905
    18. -6.982523782785449
    19. -6.609663064296379
    20. -5.924962228516943
    21. -4.978248323100632
    22. -3.829957696800105
    23. ...
    24. (display-stream (cdr RLC1s))
    25. 0
    26. 1.
    27. 1.9
    28. 2.66
    29. 3.249
    30. 3.6461
    31. 3.84104
    32. 3.834181
    33. 3.6359559
    34. 3.2658442599999997
    35. 2.750945989
    36. 2.1242453320999997
    37. 1.4226674414399998
    38. .6850350732409998
    39. -4.9967430210100305e-2
    40. -.7457214369781403
    41. -1.3694016715588713
    42. -1.893427810832622
    43. -2.296581252601054
    44. -2.5647479596510117
    45. -2.691268933365921
    46. -2.676900411726789
    47. -2.529405295583274
    48. ...

Modularity of Functional Programs and Modularity of Objects

  • Exercise 3.81

    This is the message passing paradigm in stream:

    1. (define (rand-generator messages rand-seed)
    2. (if (empty-stream? messages)
    3. the-empty-stream
    4. (let* ((message (stream-car messages))
    5. (msg (car message))
    6. (args (cdr message)))
    7. (case msg
    8. ((generate)
    9. (let ((new-seed (rand-update rand-seed)))
    10. (cons-stream new-seed
    11. (rand-generator (stream-cdr messages)
    12. new-seed))))
    13. ((reset)
    14. (cons-stream 'done
    15. (rand-generator (stream-cdr messages)
    16. (first args))))
    17. (else
    18. (error "Unknown request -- RAND-GENERATOR" message))))))

    Then let’s test it:

    1. (define test-rand-generator
    2. (rand-generator
    3. (list->stream '((generate) (reset 0) (generate) (reset 0)))
    4. 2))
    5. (display-stream test-rand-generator)
    6. 80
    7. done
    8. 26
    9. done
    10. 26
    11. ;Unspecified return value
  • Exercise 3.82

    We could have implemented the monte-carlo-stream using stream processing
    convention:

    1. (define (monte-carlo-stream2 experiments)
    2. (stream-map
    3. (lambda (p) (/ (car p) (+ (car p) (cdr p))))
    4. (stream-cdr
    5. (stream-fold-left
    6. (lambda (acc successed?)
    7. (if successed?
    8. (cons (1+ (car acc))
    9. (cdr acc))
    10. (cons (car acc)
    11. (1+ (cdr acc)))))
    12. (cons 0 0)
    13. experiments))))
    14. (define (stream-fold-left procedure initial stream)
    15. (cons-stream initial
    16. (stream-fold-left procedure
    17. (procedure initial (stream-car stream))
    18. (stream-cdr stream))))

    Anyway, we can implement estimate-integral in stream version as follows

    1. (define (estimate-integral P rect)
    2. (scale-stream
    3. (stream-map P (randoms-in-rect rect))
    4. (area rect)))
    5. (define (randoms-in-rect rect)
    6. (cons-stream
    7. (random-in-rect rect)
    8. (randoms-in-rect rect)))

    Here we used random-in-rect, which implicitly use the assignment; so
    canonically it is not the stream version we expected. To implement
    randoms-in-rect in stream, we need randoms in real number version between
    0 and 1. It is George’s problem; here we assume that we have that one. Then we
    can make randoms-in-rect that is analogous to cesaro-stream:

    1. (define (randoms-in-rect rect)
    2. (let ((points (list (bottom-left rect) (top-right rect))))
    3. (map-successive-pairs
    4. (lambda (r1 r2)
    5. (make-point
    6. ((apply map-in-range
    7. (map x-coor points))
    8. r1)
    9. ((apply map-in-range
    10. (map y-coor points))
    11. r2)))
    12. randoms-in-unit-range)))
    13. (define (map-in-range low high)
    14. (lambda (number-over-unit-range)
    15. (let ((range (- high low)))
    16. (+ low (* range number-over-unit-range)))))

    It is unfortunate that we should explicitly manipulate random number stream to
    get updated random number by the help of map-successive-pairs.

  • A functional-programming view of time

    In text, they noted that to formulate or deal with the concurrency problem of
    Paul and Peter’s that we encountered with in section 3.1.3 we need to
    reintroduce the notion of time into our language – which functional style was
    meant to eliminate. I thought to deal with this specific problem with making
    that affection regional so that it does not leak over all the other components,
    we should stamp the “time” when the user typed the input implicitly so that the
    merge procedure can fairly merger the joint input stream by using the time as
    weight.

Chapter 4: Metalinguistic Abstraction

Metalinguistic Abstraction – establishing new languages – plays an important
role in all branches of engineering design. It is particularly important to
computer programming, because in programming not only can we formulate new
languages but we can also implement these languages by constructing evaluators
.

The Metacircular Evaluator

The Core of the Evaluator

In this section we use the syntax operations (the representation for the syntax
of language being implemented) to implement the eval and apply process. Our
evaluator uses several ADTs each for syntax for language being implemented and the
internal representations for internal evaluation model – environment.

  • Exercise 4.1

    We can force the order by using let statement – since it is just syntactic
    sugar for lambda application, the value of each bindings of let expression
    evaluated before evaluating the body of it.

    1. ;;; Left to Right
    2. (define (list-of-values exps env)
    3. (if (no-operands? exps)
    4. '()
    5. (let ((first (eval (first-operand exps) env)))
    6. (cons first
    7. (list-of-values (rest-operands exps) env)))))
    8. ;;; Right to Left
    9. (define (list-of-values exps env)
    10. (if (no-operands? exps)
    11. '()
    12. (let ((rest (list-of-values (rest-operands exps) env)))
    13. (cons (eval (first-operand exps) env)
    14. rest))))

Representing Expressions

It is reminiscent of the symbolic differentiation program discussed in section
2.3.2; each expression has its own type tag in it, so our evaluator dispatch on
this type and recursively evaluate the nested expression.

  • Exercise 4.2

    • a.

      It will deal the assignment expression as if it is procedure application since
      procedure application expression doesn’t have any type for specifying it so any
      the pairs not sifted by the cond clauses before that clause treated as
      procedure application. So (define x 3) are treated procedure application and,
      in turn, evaluator lookup the define variable; since it couldn’t find any it
      will signal error – undefined variable.

    • b.

      Here is the fix:

      1. (define (application? exp) (tagged-list? exp 'call))
  • Exercise 4.3

    We can rely on the data-directed style only for the tagged expressions:

    1. (define (eval exp env)
    2. (cond ((self-evaluating? exp) exp)
    3. ((variable? exp) (lookup-variable-value exp env))
    4. (else
    5. (let ((op (and (tagged-exp? exp)
    6. (get 'eval (type-tag exp)))))
    7. (cond (op (op exp env))
    8. ((application? exp)
    9. (apply (eval (operator exp) env)
    10. (list-of-values (operands exp) env)))
    11. (else
    12. (error "Unknown expression type -- EVAL" exp)))))))

    The procedures of tagged-exp ADT:

    1. (define (type-tag exp)
    2. (car exp))
    3. (define (tagged-exp? exp)
    4. (pair? exp))

    Then install the contents:

    1. (define (install-eval-clauses)
    2. (put 'eval 'quote (lambda (exp env) (text-of-quotation exp)))
    3. (put 'eval 'set! eval-assignment)
    4. (put 'eval 'define eval-definition)
    5. (put 'eval 'if eval-if)
    6. (put 'eval 'lambda (lambda (exp env)
    7. (make-procedure (lambda-parameters exp)
    8. (lambda-body exp)
    9. env)))
    10. (put 'eval 'begin (lambda (exp env)
    11. (eval-sequence (begin-actions exp) env)))
    12. (put 'eval 'cond (lambda (exp env)
    13. (eval (cond->if exp) env)))
    14. 'done)

    I’ve implemented the type tags directly for simplicity.

  • Exercise 4.4

    • As special expression

      1. (define (install-eval-and-or-direct)
      2. ;; and eval
      3. (define (eval-and exp env)
      4. (eval-and-subs (subexps exp) env))
      5. (define (eval-and-subs subs env)
      6. (cond ((null? subs) (eval 'true env))
      7. ((last-sub? subs) (eval (first subs) env))
      8. (else
      9. (if (true? (eval (first-sub subs) env))
      10. (eval-and-subs (rest-subs subs) env)
      11. (eval 'false env)))))
      12. ;; or eval
      13. (define (eval-or exp env)
      14. (eval-or-subs (subexps exp) env))
      15. (define (eval-or-subs subs env)
      16. (cond ((null? subs) (eval 'false env))
      17. ((last-sub? subs) (eval (first subs) env))
      18. (else
      19. (let ((current
      20. (eval (first-sub subs) env)))
      21. (if (false? current)
      22. (eval-or-subs (rest-subs subs) env)
      23. current)))))
      24. ;; selector on And or Or expression
      25. (define (subexps exp) (cdr exp))
      26. ;; sub ADT
      27. (define (last-sub? subs)
      28. (and (pair? subs) (null? (cdr subs))))
      29. (define first-sub car)
      30. (define rest-subs cdr)
      31. ;; interface with eval procedure
      32. (put 'eval 'and eval-and)
      33. (put 'eval 'or eval-or)
      34. 'done)

      Here we used data-directed style eval from ex 4.3.

      Then setup for test:

      1. (define eval-table (make-hash-table))
      2. (define (put op type item)
      3. (if (eq? op 'eval)
      4. (hash-table-set! eval-table type item)
      5. (error "Unallowed operation -- PUT" op)))
      6. (define (get op type)
      7. (if (eq? op 'eval)
      8. (hash-table-ref eval-table type (lambda () #f))
      9. (error "Unknown operation -- GET" op)))

      Let’s run!

      1. 1 ]=> (install-eval-clauses)
      2. ;Value: done
      3. 1 ]=> (install-eval-and-or-direct)
      4. ;Value: done
      5. 1 ]=> (driver-loop)
      6. ;;; M-Eval input:
      7. (and)
      8. ;;; M-Eval value:
      9. #t
      10. ;;; M-Eval input:
      11. (and 5)
      12. ;;; M-Eval value:
      13. 5
      14. ;;; M-Eval input:
      15. (and false)
      16. ;;; M-Eval value:
      17. #f
      18. ;;; M-Eval input:
      19. (and false 5 2)
      20. ;;; M-Eval value:
      21. #f
      22. ;;; M-Eval input:
      23. (and 5 2)
      24. ;;; M-Eval value:
      25. 2
      26. ;;; M-Eval input:
      27. (and 5 false)
      28. ;;; M-Eval value:
      29. #f
      30. ;;; M-Eval input:
      31. (or)
      32. ;;; M-Eval value:
      33. #f
      34. ;;; M-Eval input:
      35. (or 5)
      36. ;;; M-Eval value:
      37. 5
      38. ;;; M-Eval input:
      39. (or false 5 false flase)
      40. ;;; M-Eval value:
      41. 5
      42. ;;; M-Eval input:
      43. (or false 5 false)
      44. ;;; M-Eval value:
      45. 5
      46. ;;; M-Eval input:
      47. (or false false 5)
      48. ;;; M-Eval value:
      49. 5

      Or we could test this in unit:

      1. (install-eval-clauses)
      2. ;Value: done
      3. (install-eval-and-or-direct)
      4. ;Value: done
      5. (eval '(and 5) the-global-environment)
      6. ;Value: 5
      7. (eval '(and false 5) the-global-environment)
      8. ;Value: #f
      9. (eval '(or 5 false) the-global-environment)
      10. ;Value: 5
    • As derived expression

      This is analogous to cond:

      1. define (install-eval-and-or-derived)
      2. (define (expand-or-subs subs)
      3. (cond ((null? subs) 'false)
      4. ((null? (cdr subs)) (car subs))
      5. (else
      6. (let ((first (car subs)))
      7. (make-let
      8. (list (list 'first first))
      9. (list (make-if 'first
      10. 'first
      11. (expand-or-subs (cdr subs)))))))))
      12. (define (expand-and-subs subs)
      13. (cond ((null? subs) 'true)
      14. ((null? (cdr subs)) (car subs))
      15. (else
      16. (make-if (car subs)
      17. (expand-and-subs (cdr subs))
      18. 'false))))
      19. (define (and->if exp)
      20. (expand-and-subs (cdr exp)))
      21. (define (or->if exp)
      22. (expand-or-subs (cdr exp)))
      23. (put 'eval 'and (lambda (exp env)
      24. (eval (and->if exp) env)))
      25. (put 'eval 'or (lambda (exp env)
      26. (eval (or->if exp) env)))
      27. 'done)

      For or, we used make-let to reuse the result of evaluation of first
      subexpression in case where it turns out true.

      Here is make-let syntax procedure that is analogous to make-lambda

      1. ;; List<binding>, List<expression> -> Let
      2. (define (make-let bindings body)
      3. (cons 'let (cons bindings body)))

      Then test!

      1. (install-eval-and-or-derived)
      2. ;Value: done
      3. (eval '(and 5) the-global-environment)
      4. ;Value: 5
      5. (eval '(and false 5) the-global-environment)
      6. ;Value: #f
      7. (eval '(and true 2 true) the-global-environment)
      8. ;Value: #t

      For the or part, we delegate the test until we implement the let expression
      in our evaluator (ex 4.6)

  • Exercise 4.5

    We can support this new feature through either special form or derived form. The
    key issue is that is it okay re-evaluate the <test> expression twice?

    • If it is okay, the only change that we should make is the sequence->exp part
      of expand-clauses into

      1. (define (expand-clauses clauses)
      2. (if (null? clauses)
      3. 'false ; no else clause
      4. (let ((first (car clauses))
      5. (rest (cdr clauses)))
      6. (if (cond-else-clause? first)
      7. (if (null? rest)
      8. (sequence->exp (cond-actions first))
      9. (error "ELSE clause isn't last -- COND->IF"
      10. clauses))
      11. (make-if (cond-predicate first)
      12. (cond-actions->exp first)
      13. (expand-clauses rest))))))
      14. (define (cond-actions->exp clause)
      15. (let ((actions (cond-actions clause)))
      16. (if (eq? (car actions) '=>)
      17. (if (and (pair? (cdr actions))
      18. (null? (cddr actions)))
      19. (list (cadr actions) (cond-predicate clause))
      20. (error "Illformed expression -- COND-MAP" clause))
      21. (sequence->exp actions))))

      Then test:

      1. (eval '(cond ((cons 3 4) => cdr)
      2. (else false)) the-global-environment)
      3. ;Value: 4
      4. (eval '(cond ((cons 3 4) => cdr 3)
      5. (else false)) the-global-environment)
      6. ;Illformed expression -- COND-MAP ((cons 3 4) => cdr 3)
      7. ;To continue, call RESTART with an option number:
      8. ; (RESTART 1) => Return to read-eval-print level 1.
    • If it is not, in other word, we are constrained to evaluate the cond-predicate
      only once, we do use make-let as we did in the previous exercise.

      1. (define (expand-clauses clauses)
      2. (if (null? clauses)
      3. 'false ; no else clause
      4. (let ((first (car clauses))
      5. (rest (cdr clauses)))
      6. (if (cond-else-clause? first)
      7. (if (null? rest)
      8. (sequence->exp (cond-actions first))
      9. (error "ELSE clause isn't last -- COND->IF"
      10. clauses))
      11. (let ((pred (cond-predicate first)))
      12. (make-let
      13. (list (list 'pred pred))
      14. (list (make-if 'pred
      15. (map-or-sequence->exp
      16. (cond-actions first) 'pred)
      17. (expand-clauses rest)))))))))
      18. (define (map-or-sequence->exp actions pred)
      19. (if (eq? (car actions) '=>)
      20. (if (and (pair? (cdr actions))
      21. (null? (cddr actions)))
      22. (list (cadr actions) pred)
      23. (error "Illformed expression -- COND-MAP" clause))
      24. (sequence->exp actions)))
  • Exercise 4.6

    1. (define (install-eval-let)
    2. (define (let->combination exp)
    3. (let* ((bindings (bindings exp))
    4. (unziped-b
    5. (fold-right
    6. (lambda (var-exp vars-exps)
    7. (cons (cons (car var-exp) (car vars-exps))
    8. (cons (cadr var-exp) (cdr vars-exps))))
    9. (cons '() '())
    10. bindings))
    11. (params (car unziped-b))
    12. (exps (cdr unziped-b)))
    13. (cons (make-lambda params (body exp))
    14. exps)))
    15. (define bindings cadr)
    16. (define body cddr)
    17. (put 'eval 'let (lambda (exp env)
    18. (eval (let->combination exp) env)))
    19. 'done)

    Then test:

    1. (eval '(let ((x 1)) (cons x 2)) the-global-environment)
    2. ;Value: (1 . 2)
    3. (eval '(let ((x 1)) (cons x 2) (cons 2 x)) the-global-environment)
    4. ;Value: (2 . 1)

    Then now we can test the or of derived version:

    1. (install-eval-and-or-derived)
    2. ;Value: done
    3. (eval '(or 5 false) the-global-environment)
    4. ;Value: 5
    5. (eval '(or false 5 false) the-global-environment)
    6. ;Value: 5

    And derived cond‘s new feature:

    1. (eval '(cond ((cons 3 4) => cdr)
    2. (else false)) the-global-environment)
    3. ;Value: 4
    4. (eval '(cond ((cons 3 4) => cdr 3)
    5. (else false)) the-global-environment)
    6. ;Unbound variable: clause

    By test it we found the bug in our previous error handling. It is easy to fix.
    After fixing,

    1. (eval '(cond ((cons 3 4) => cdr 3)
    2. (else false)) the-global-environment)
    3. ;Illformed expression -- COND-MAP (=> cdr 3)

    It works as expected.

  • Exercise 4.7

    1. (define (install-eval-let*)
    2. (define (let*->let exp)
    3. (define (expand-let* bindings)
    4. (if (null? bindings)
    5. (body exp)
    6. (let ((first (car bindings))
    7. (rest (cdr bindings)))
    8. (make-let
    9. (list first)
    10. ((if (null? rest)
    11. identity-procedure
    12. list) ;for the type contraction of make-let
    13. (expand-let* rest))))))
    14. (expand-let* (bindings exp)))
    15. (define bindings cadr)
    16. (define body cddr)
    17. (put 'eval 'let* (lambda (exp env)
    18. (eval (let*->let exp) env)))
    19. 'done)

    It suffices to define let* as derived expression since subsequent derived
    expression reduced to non-derived expression by eval and apply loop, which
    in turn reduced to primitive expressions.

    Then test:

    1. (install-eval-let*)
    2. ;Value: done
    3. (eval
    4. '(let* ((x 3)
    5. (y (+ x 2))
    6. (z (+ x y 5)))
    7. (* x z))
    8. the-global-environment)
    9. ;Value: 39
  • Exercise 4.8

    First let we code the test:

    1. (eval
    2. '(define (fib n)
    3. (let fib-iter ((a 1)
    4. (b 0)
    5. (count n))
    6. (if (= count 0)
    7. b
    8. (fib-iter (+ a b) a (- count 1)))))
    9. the-global-environment)
    10. ;; ok
    11. (eval
    12. '(fib 3)
    13. the-global-environment)
    14. ;; 1

    And note that by using the environment model, we can deduce named let expression
    (let <var> <bindings> <body>) is equivalent to

    1. (let ((<var> undef))
    2. (set! <var>
    3. (lambda <params of bindings> body))
    4. (apply <var> <exps of bindings>))

    So we convert named let expression above equivalent expression.

    Here is the result:

    1. (define (install-eval-let-with-named)
    2. ;; ADT for named
    3. (define (named? exp)
    4. (and (pair? exp)
    5. (pair? (cdr exp))
    6. (symbol? (cadr exp))))
    7. (define (named exp)
    8. (cadr exp))
    9. (define (except-name-let exp)
    10. (cdr exp))
    11. (define (let->combination exp)
    12. (let* ((bindings (bindings exp))
    13. (unziped-b
    14. (fold-right
    15. (lambda (var-exp vars-exps)
    16. (cons (cons (car var-exp) (car vars-exps))
    17. (cons (cadr var-exp) (cdr vars-exps))))
    18. (cons '() '())
    19. bindings))
    20. (params (car unziped-b))
    21. (exps (cdr unziped-b)))
    22. (cons (make-lambda params (body exp))
    23. exps)))
    24. (define (named->let-combination exp)
    25. (let ((var (named exp))
    26. (comb (let->combination
    27. (except-name-let exp))))
    28. (let ((lambda-part (car comb))
    29. (exps (cdr comb)))
    30. (make-let
    31. (list (list var (list 'quote undef)))
    32. (list
    33. (make-assignment var
    34. lambda-part)
    35. (cons var
    36. exps))))))
    37. (define bindings cadr)
    38. (define body cddr)
    39. (put 'eval 'let (lambda (exp env)
    40. (eval (if (named? exp)
    41. (named->let-combination exp)
    42. (let->combination exp)) env)))
    43. 'done)

    And the auxiliary parts:

    1. (define undef '*unassigned*)
    2. (define (make-assignment var val)
    3. (list 'set! var val))

    Then let’s test:

    1. (install-eval-let-with-named)
    2. ;Value: done
    3. (eval
    4. '(define (fib n)
    5. (let fib-iter ((a 1)
    6. (b 0)
    7. (count n))
    8. (if (= count 0)
    9. b
    10. (fib-iter (+ a b) a (- count 1)))))
    11. the-global-environment)
    12. ;Value: ok
    13. (eval
    14. '(fib 3)
    15. the-global-environment)
    16. ;Unbound variable =

    Huh, we should set = (and also -) as primitive procedure in our environment.
    Do that and re-run:

    1. (install-eval-clauses)
    2. ;Value: done
    3. (install-eval-let-with-named)
    4. ;Value: done
    5. (eval
    6. '(define (fib n)
    7. (let fib-iter ((a 1)
    8. (b 0)
    9. (count n))
    10. (if (= count 0)
    11. b
    12. (fib-iter (+ a b) a (- count 1)))))
    13. the-global-environment)
    14. ;Value: ok
    15. (eval
    16. '(fib 3)
    17. the-global-environment)
    18. ;Value: 2
    19. (eval
    20. '(fib 2)
    21. the-global-environment)
    22. ;Value: 1

    Oh-huh there are good news and bad news:

    • Our test code was wrong! (fib 3) should return 2 not 1 as it count from 0!
    • Our code works right!
  • Exercise 4.9

    For example, here we try to implement typical imperative control structure,
    while. Let us think about the what syntax should it be? Analogous to any other
    imperative programming language, let we make our syntax as follows:

    1. (while <pred>
    2. <exp1>
    3. ...
    4. <expN>)

    The semantic of this control structure are followings:

    1. Evaluate <pred> first.
    2. If it true, execute from <exp1> until <expN> sequentially; Then go to 1.
    3. If it was false, then return the control to consequent structure (return
      undefined value).

    All of the above expressions should be evaluated in a same environment; we don’t
    need to extend the given environment.

    Then here is the code:

    1. (define (install-eval-while)
    2. (define pred cadr)
    3. (define body cddr)
    4. (define (eval-while exp env)
    5. (let ((bexp (sequence->exp (body exp))))
    6. (let loop ()
    7. (if (true? (eval (pred exp) env))
    8. (begin (eval bexp env)
    9. (loop))
    10. 'done))))
    11. (put 'eval 'while eval-while)
    12. 'done)

    Then here is the test code:

    1. (eval '(begin
    2. (define x 5)
    3. (define sum 0)
    4. (while (> x 0)
    5. (set! sum (+ x sum))
    6. (set! x (-1+ x)))
    7. sum)
    8. the-global-environment)
    9. ;Should return 15

    And run:

    1. (eval '(begin
    2. (define x 5)
    3. (define sum 0)
    4. (while (> x 0)
    5. (set! sum (+ x sum))
    6. (set! x (-1+ x)))
    7. sum)
    8. the-global-environment)
    9. ;Value: 15

    Sweet!

  • Exercise 4.10

    Here we will use dispatch on type rather than data-directed style since we are
    going to mix the infix expressions with prefix expressions. Specifically we will
    transform the assignment notation and that of definition into infix notation:

    • From (set! <var> <exp>) to (<var> <- <exp>);
    • From (define <var> <exp>) to (<var> = <exp>).

    All we need to change is the detectors and selectors:

    • From

      1. ;; detector
      2. (define (assignment? exp)
      3. (tagged-list? exp 'set!))
      4. ;; selectors
      5. (define (assignment-variable exp) (cadr exp))
      6. (define (assignment-value exp) (caddr exp))

      to

      1. ;; detector
      2. (define (assignment? exp)
      3. (infix-tag-list? exp '<-))
      4. ;; selectors
      5. (define (assignment-variable exp) (car exp))
      6. (define (assignment-value exp) (caddr exp))
    • From

      1. ;; detector
      2. (define (definition? exp)
      3. (tagged-list? exp 'define))
      4. ;; selectors
      5. (define (definition-variable exp)
      6. (if (symbol? (cadr exp))
      7. (cadr exp)
      8. (caadr exp)))
      9. (define (definition-value exp)
      10. (if (symbol? (cadr exp))
      11. (caddr exp)
      12. (make-lambda (cdadr exp)
      13. (cddr exp))))

      to

      1. ;; detector
      2. (define (definition? exp)
      3. (infix-tag-list? exp '=))
      4. ;; selectors
      5. (define (definition-variable exp)
      6. (if (symbol? (car exp))
      7. (car exp)
      8. (caar exp)))
      9. (define (definition-value exp)
      10. (if (symbol? (car exp))
      11. (caddr exp)
      12. (make-lambda (cdar exp)
      13. (cddr exp))))

    Then the test code:

    ```scheme
    ;; Test infix defintion
    (eval ‘(begin (x = 2)

    1. x)
    2. the-global-environment)

    ;; 2
    ;; procedure definition
    (eval
    ‘((factorial n) = (if (= n 1)

    1. 1
    2. (* n (factorial (-1+ n)))))

    the-global-environment)
    ;; ok
    (eval ‘(factorial 3) the-global-environment)
    ;; 6

  1. ;; test infix assignment
  2. (eval '(begin (x <- 5)
  3. x)
  4. the-global-environment)
  5. ;; 5
  6. ```
  7. Then let's run
  8. ```scheme
  9. (eval '(begin (x = 2)
  10. x)
  11. the-global-environment)
  12. ;Value: 2
  13. (eval
  14. '((factorial n) = (if (= n 1)
  15. 1
  16. (* n (factorial (-1+ n)))))
  17. the-global-environment)
  18. ;Value: ok
  19. (eval '(factorial 3) the-global-environment)
  20. ;Value: 6
  21. (eval '(begin (x <- 5)
  22. x)
  23. the-global-environment)
  24. ;Value: 5
  25. ```

Evaluator Data Structures

  • Exercise 4.11

    This is same as replace our frame data structure with table as we did in section
    3.3. Then the changes are trivial:

    1. ;; For extend-environment
    2. (define (make-frame vars vals)
    3. (let ((bindings
    4. ;; provided that the length of both arguments match
    5. (fold-right
    6. (lambda (var val bindings)
    7. (cons (list var val) bindings))
    8. '()
    9. vars
    10. vals))
    11. (tbl (make-table)))
    12. (set-bindings! tbl bindings)
    13. tbl))
    14. ;; Table ADT
    15. ;; constructor
    16. (define (make-table)
    17. (list '*table*))
    18. ;; mutator
    19. (define set-bindings! set-cdr!)
    20. ;; selector
    21. (define bindings cdr)
    22. ;; For lookup-variable-value
    23. (define (lookup-variable-value var env)
    24. (let env-loop ((env env))
    25. (if (eq? env the-empty-environment)
    26. (error "Unbound variable" var)
    27. (let ((frame (first-frame env)))
    28. (cond ((assoc var (bindings frame)) => cadr)
    29. (else (env-loop
    30. (enclosing-environment env))))))))
    31. ;; For set-variable-value!
    32. (define (set-variable-value! var val env)
    33. (let env-loop ((env env))
    34. (if (eq? env the-empty-environment)
    35. (error "Unbound variable -- SET!" var)
    36. (let ((frame (first-frame env)))
    37. (cond ((assoc var (bindings frame))
    38. => (lambda (b) (set-car! (cdr b) val)))
    39. (else (env-loop
    40. (enclosing-environment env))))))))
    41. ;; For define-variable!
    42. (define (define-variable! var val env)
    43. (let ((frame (first-frame env)))
    44. (cond ((assoc var (bindings frame))
    45. => (lambda (b) (set-car! (cdr b) val)))
    46. (else (set-bindings! frame
    47. (cons (list var val)
    48. (bindings frame)))))))

    By using the built in procedures about the alist structure, we could write our
    procedures more succinctly; this leads that we recognize the common patterns in
    above procedures more obviously, which we revisit in the next exercise.

    Return to our discourse, let we code the test:

    1. ;; test definition
    2. (eval '(define test-def 0) the-global-environment)
    3. ;; ok
    4. ;; test assignment in nested frame
    5. (eval '(define (test-assignment) (set! test-def 5))
    6. the-global-environment)
    7. ;; ok
    8. (eval 'test-def the-global-environment)
    9. ;; 0
    10. (eval '(test-assignment) the-global-environment)
    11. ;; ok
    12. ;; test lookup-variable-value
    13. (eval 'test-def the-global-environment)
    14. ;; 5
    15. ;; verify that we using the new data structure for frame
    16. (caar the-global-environment)
    17. ;; *table*

    Then run:

    1. (eval '(define test-def 0) the-global-environment)
    2. ;Value: ok
    3. (eval '(define (test-assignment) (set! test-def 5))
    4. the-global-environment)
    5. ;Value: ok
    6. (eval 'test-def the-global-environment)
    7. ;Value: 0
    8. (eval '(test-assignment) the-global-environment)
    9. ;Value: ok
    10. (eval 'test-def the-global-environment)
    11. ;Value: 5
    12. (caar the-global-environment)
    13. ;Value: *table*
  • Exercise 4.12

    We can capture the common pattern first by recognizing the same code in those
    code and then refine them with their role of behavior, namely traversing given
    frame and traversing given environment:

    1. ;; Var, (Vals -> Any) -> FrameOp
    2. (define (find-var-and-apply-in-frame var vals-op)
    3. (lambda (null-op frame)
    4. (let scan ((vars (frame-variables frame))
    5. (vals (frame-values frame)))
    6. (cond ((null? vars)
    7. (null-op))
    8. ((eq? var (car vars))
    9. (vals-op vals))
    10. (else (scan (cdr vars) (cdr vals)))))))
    11. ;; FrameOp, void -> Any, Env
    12. ;; -> Any
    13. (define (traverse-env-using frame-op empty-env-op env)
    14. (if (eq? env the-empty-environment)
    15. (empty-env-op)
    16. (frame-op (first-frame env)
    17. (lambda () (traverse-env-using frame-op empty-env-op
    18. (enclosing-environment env))))))

    Here we, for interfacing the traverser on environment with traverser on frame,
    defined new data type, namely FrameOpFrameOp := Frame, NullOp -> Any,
    NullOp := void -> Any.

    Then our operations on environments get

    1. (define (lookup-variable-value var env)
    2. (traverse-env-using
    3. (find-var-and-apply-in-frame var car)
    4. (lambda () (error "Unbound variable" var))
    5. env))
    6. (define (set-variable-value! var val env)
    7. (traverse-env-using
    8. (find-var-and-apply-in-frame
    9. var
    10. (lambda (vals) (set-car! vals val)))
    11. (lambda () (error "Unbound variable -- SET!" var))
    12. env))
    13. (define (define-variable! var val env)
    14. (let ((frame (first-frame env)))
    15. ((find-var-and-apply-in-frame
    16. var
    17. (lambda (vals) (set-car! vals val)))
    18. (lambda ()
    19. (add-binding-to-frame! var val frame))
    20. frame)))

    Then test using the test code of previous exercise:

    1. ...
    2. (eval 'test-def the-global-environment)
    3. ;The object #[compound-procedure 38], passed as the first argument to cdr, is not the correct type.
    4. ;To continue, call RESTART with an option number:
    5. ; (RESTART 2) => Specify an argument to use in its place.
    6. ; (RESTART 1) => Return to read-eval-print level 1.
    7. 2 error> (pp #@38)
    8. (lambda ()
    9. (traverse-env-using frame-op empty-env-op (enclosing-environment env)))
    10. ;Unspecified return value

    Got error! It is due to the ill-ordered argument in traverse-env-using. After
    fixing that we got:

    1. (eval '(define test-def 0) the-global-environment)
    2. ;Value: ok
    3. (eval '(define (test-assignment) (set! test-def 5))
    4. the-global-environment)
    5. ;Value: ok
    6. (eval 'test-def the-global-environment)
    7. ;Value: 0
    8. (eval '(test-assignment) the-global-environment)
    9. ;Value: ok
    10. (eval 'test-def the-global-environment)
    11. ;Value: 5

    Our code got more cleaner and easier for us to read.

  • Exercise 4.13

    As we can think of this new special form as counter expression against define,
    let we make make-unbound! remove only the binding in the first frame of the
    environment. To implement this feature, that is to remove specific binding in the
    frame, we need to do nearly what we did in remove-first-item in agenda data
    structure. So, we would not reuse the abstract procedure of previous exercise,
    find-var-and-apply-in-frame; but the resulting procedure would be nearly same
    as that.

    To encompass this pattern, we extend find-var-and-apply-in-frame with:

    1. (define (find-var-and-apply-to-bindings var find-op bindings-op)
    2. (lambda (null-op frame)
    3. (let scan ((vars (frame-variables frame))
    4. (vals (frame-values frame)))
    5. (cond ((null? vars)
    6. (null-op))
    7. ((eq? var (find-op vars))
    8. (bindings-op vars vals))
    9. (else (scan (cdr vars) (cdr vals)))))))

    Then we can define

    1. (define (find-var-and-apply-in-frame var vals-op)
    2. (find-var-and-apply-to-bindings
    3. var car (lambda (vars vals) (vals-op vals))))

    Or it would better to refine our frame data structure by making the
    frame-variables and frame-values to be headed list respectively to encompass
    make-unbound! in our scheme uniformly.

    As we defined our frame data structure abstractly, all we need to change is the
    representation of frame:

    1. ;; Change the representation of frame
    2. (define (make-frame variables values)
    3. (cons (cons '*variables* variables)
    4. (cons '*values* values)))
    5. (define (frame-variables frame) (cdar frame))
    6. (define (frame-values (cddr frame)))

    Then we exploit this idea by amending the find-var-and-apply-to-bindings
    allowing even the selectors and detector for frame:

    1. (define (find-var-and-apply-to-bindings
    2. var find-op bindings-op frame-vars frame-vals empty-vars?)
    3. (lambda (null-op frame)
    4. (let scan ((vars (frame-vars frame))
    5. (vals (frame-vals frame)))
    6. (cond ((empty-vars? vars)
    7. (null-op))
    8. ((eq? var (find-op vars))
    9. (bindings-op vars vals))
    10. (else (scan (cdr vars) (cdr vals)))))))
    11. (define (find-var-and-apply-in-frame var vals-op)
    12. (find-var-and-apply-to-bindings
    13. var car (lambda (vars vals) (vals-op vals))
    14. frame-variables frame-values null?))

    Then we can define what we wanted:

    1. (define (unbound-variable! var val env)
    2. (let ((frame (first-frame env)))
    3. ((find-var-and-apply-to-bindings
    4. var cadr
    5. (lambda (h-vars h-vals)
    6. (set-cdr! h-vars (cddr h-vars))
    7. (set-cdr! h-vals (cddr h-vals)))
    8. car cdr (lambda (h-vars) (null? (cdr h-vars))))
    9. (lambda ()
    10. (error "Unbound variable in the given frame -- MAKE-UNBOUND!" var))
    11. frame)))
    12. (define (install-eval-make-unbound)
    13. (define var cadr)
    14. (define val caddr)
    15. (put 'eval 'make-unbound!
    16. (lambda (exp env)
    17. (unbound-variable! (var exp)
    18. (val exp)
    19. env))))

    Then let we first test the new frame data structure:

    1. (eval '(test-assignment) the-global-environment)
    2. ;Unbound variable test-assignment

    Error! This is due to the add-binding-to-frame! as we did not updated that
    procedure, which is mutator of frame structure!

    Then refine the data structure for frame:

    1. ;; Change the representation of frame
    2. (define (make-frame variables values)
    3. (cons (cons '*variables* variables)
    4. (cons '*values* values)))
    5. ;; selectors for frame
    6. (define (frame-variables frame) (cdar frame))
    7. (define (frame-values frame) (cddr frame))
    8. ;; mutators of frame
    9. (define (add-binding-to-frame! var val frame)
    10. (set-cdr! (car frame) (cons var (frame-variables frame)))
    11. (set-cdr! (cdr frame) (cons val (frame-values frame))))
    12. (define (remove-binding-from-frame! var frame exception)
    13. ((find-var-and-apply-to-bindings
    14. var cadr
    15. (lambda (h-vars h-vals)
    16. (set-cdr! h-vars (cddr h-vars))
    17. (set-cdr! h-vals (cddr h-vals)))
    18. car cdr (lambda (h-vars) (null? (cdr h-vars))))
    19. exception
    20. frame))

    Then our procedure get clarified as

    1. (define (unbound-variable! var env)
    2. (remove-binding-from-frame!
    3. var
    4. (first-frame env)
    5. (lambda ()
    6. (error "Unbound variable in the given frame -- MAKE-UNBOUND!" var))))

    Re-run our test code:

    1. (eval '(define test-def 0) the-global-environment)
    2. ;Value: ok
    3. (eval '(define (test-assignment) (set! test-def 5))
    4. the-global-environment)
    5. ;Value: ok
    6. (eval 'test-def the-global-environment)
    7. ;Value: 0
    8. (eval '(test-assignment) the-global-environment)
    9. ;Value: ok
    10. (eval 'test-def the-global-environment)
    11. ;Value: 5
    12. (eval '(make-unbound! test-def) the-global-environment)
    13. ;ok
    14. (eval '(make-unbound! test-def) the-global-environment)
    15. ;Unbound variable in the given frame -- MAKE-UNBOUND! test-def

    Works sweetly!

Running the Evaluator as a Program

  • Exercise 4.14

    1. It is because our evaluator uses data abstraction for decouple the semantics
      from syntaxes of language.
    2. Combined with the 1., it is due to the difference of how evaluator treats
      application of compound procedure from that of primitive procedure.
    3. Map needs to apply evaluated procedure to the each element of given list;
      but the value of that procedure is wrapped around by type-tag and it handled
      underlying Scheme’s evaluator which don’t know how to handle our type-tagged data.
    4. So namely, it is not the procedure, which handed over to map, in
      implementation language but in language being implemented.

Data as Programs

  • Exercise 4.15

    We can prove this by case analysis with reductio ad absurdum:

    • (try try)'halted:
      By definition of try, it implies that (halts? try try) evaluated as
      false. Which in turn means (try try) raises error or run forever by the
      definition of halts?; this is contradiction with our assumption – (try try) returns 'halted, which means it halted.
    • (try try)run forever or raise error:
      With the same argument from the previous one, it leads to contradiction with
      assumption in any case – whether it evaluated as run forever or raise error.

Internal Definitions

  • Exercise 4.16

    • a.

      1. (define (lookup-variable-value var env)
      2. (traverse-env-using
      3. (find-var-and-apply-in-frame
      4. var
      5. (lambda (vals)
      6. (let ((val (car vals)))
      7. (if (eq? val undef)
      8. (error "Unassigned variable" var)
      9. val))))
      10. (lambda () (error "Unbound variable" var))
      11. env))
    • b.

      I’ve designed in the text book with my digital paper:

      1. (define (scan-out-defines proc-body)
      2. (let ((extrated-exps
      3. ;; List<Exp> (proc-body) -> List<Exp> x List<Exp>
      4. ;; We implement this a little bit complicate procedure since
      5. ;; we want to examine possible user error --
      6. ;; defenstive programming.
      7. (fold-right
      8. (lambda (exp extracted)
      9. (if (definition? exp)
      10. (cons (cons exp (car extracted)) (cdr extracted))
      11. (if (null? (car extracted))
      12. (cons (car extracted) (cons exp (cdr extracted)))
      13. ;; intertwined internal definitions with others
      14. (error "Internal defintions intertwins with others" proc-body))))
      15. (cons '() '())
      16. proc-body)))
      17. (let ((internal-defs (car extrated-exps))
      18. (rest-body (cdr extrated-exps)))
      19. (let ((vars (map definition-variable internal-defs))
      20. (exps (map definition-value internal-defs)))
      21. (let ((bindings
      22. (map (lambda (var) (list var (list 'quote undef)))
      23. vars))
      24. (set-exps
      25. (map (lambda (var val)
      26. (make-assignment var val))
      27. vars
      28. exps)))
      29. (make-let bindings (append set-exps rest-body)))))))

      Then let’s test:

      1. ;; test for scan-out-defines
      2. (scan-out-defines
      3. '((define u <e1>)
      4. (define v <e2>)
      5. <e3>))
      6. ;; should return
      7. (let ((u '*unassigned*)
      8. (v '*unassigned*))
      9. (set! u <e1>)
      10. (set! v <e2>)
      11. <e3>)

      Then the results:

      1. (pretty-print (scan-out-defines
      2. '((define u <e1>)
      3. (define v <e2>)
      4. <e3>)))
      5. (let ((u '*unassigned*) (v '*unassigned*))
      6. (set! u <e1>)
      7. (set! v <e2>)
      8. <e3>)
      9. ;Unspecified return value

      as expected.

    • c.

      The construction time is better. Note that it is analogous to the choice we made
      when we comes with rational number arithmetic package. Since procedure is
      “captured common computational process,” it is supposed to be applied more than
      defining ones; so by make scan-out-defines run in the construction time, we’ll
      get more efficient implementation dynamically.

      Then here is the code:

      1. (define (make-procedure parameters body env)
      2. (list 'procedure parameters
      3. (scan-out-defines body)
      4. env))
  • Exercise 4.17

    I’ve drawn the required diagram with my digital paper.

    For the second question, first we need to recognize what the correct program is;
    as jotted in the text book, correct program means what program obeyed the
    described restriction – the defined variables’ values can be evaluated without
    using any of variables’ values. Then we can answer given question: Variables
    that can be looked up in evaluating <e3> is same in both environment
    structure; as an evaluation of any statement can not inspect the environment
    directly, the behavior of both should equals among both. More formally, we can
    prove the argument using the structure induction on eval/apply; that is,
    evaluation of statement achieved by eval/apply, so we need to prove if all the
    behavior of the program before the transformation are same, then after that
    transformation the evaluation result of each clause should results in same as
    before the transformation.

    Lastly, we can fix this different environment structure by transforming the
    internal definitions into

    • Define the variables of them with *unassigned* then set! the value by
      corresponding expressions afterward, which would do not make any extra frame
      and also satisfy all the specifications described above.
    • Note that this fixation does not involve any ill-defined procedure, that is
      the infinite loop where calls itself again and again.

    The code:

    1. (define (scan-out-defines2 proc-body)
    2. (let ((extrated-exps
    3. ;; List<Exp> (proc-body) -> List<Exp> x List<Exp>
    4. ;; We implement this a little bit complicate procedure since
    5. ;; we want to examine possible user error --
    6. ;; defenstive programming.
    7. (fold-right
    8. (lambda (exp extracted)
    9. (if (definition? exp)
    10. (cons (cons exp (car extracted)) (cdr extracted))
    11. (if (null? (car extracted))
    12. (cons (car extracted) (cons exp (cdr extracted)))
    13. ;; intertwined internal definitions with others
    14. (error "Internal defintions intertwines with others" proc-body))))
    15. (cons '() '())
    16. proc-body)))
    17. (let ((internal-defs (car extrated-exps))
    18. (rest-body (cdr extrated-exps)))
    19. (let ((vars (map definition-variable internal-defs))
    20. (exps (map definition-value internal-defs)))
    21. (let ((def-vars
    22. (map (lambda (var) (make-definition var (list 'quote undef)))
    23. vars))
    24. (set-exps
    25. (map (lambda (var val)
    26. (make-assignment var val))
    27. vars
    28. exps)))
    29. (append def-vars (append set-exps rest-body)))))))
    30. (define (make-definition var val)
    31. (list 'define var val))

    Then test again:

    1. (pretty-print (scan-out-defines2
    2. '((define u <e1>)
    3. (define v <e2>)
    4. <e3>)))
    5. ((define u '*unassigned*) (define v '*unassigned*) (set! u <e1>) (set! v <e2>) <e3>)
  • Exercise 4.18

    This new version would not work; whereas the version in the text works. We can
    reason this as follows:

    1. (define (solve f y0 dt)
    2. (define y (integral (delay dy) y0 dt)) ;does not evaluate dy until it need
    3. ;; in this block structure it never needed.
    4. (define dy (stream-map f y)) ;does evaluate y
    5. y)
    1. As noted above annotation, the value expression of dy involves y and need
      to evaluate that to return the value.
    2. Since in this transformation strategy does not change all the occurrence of
      u and v into a and b respectively in the <e1> and <e2> as it
      assume the restriction is obeyed, when it comes to evaluate y it lookup and
      signal error as it found *unassigned*.
    3. This malfunction is due to the modification of the name of definition without
      changing all the expressions that depend on the name of internal definitions
      as we noted above.
  • Exercise 4.19

    1. I myself couldn’t yet be convinced by why we need to treat internal
      definitions especially; so I support Ben’s view since it make me (and
      possibly other people too) easy to understand the behavior of internal
      definition in consistent manner.

      However it is trickier than it seems since the difference between procedure
      and other data. And because of the limitation of text editor environment: We
      are forced to write code in sequential manner; so it forces us to think as if
      we are dealing with sequential events. But it isn’t as we discuss below.

      The above discussion is not consistent with that of mutual recursive
      procedures – it works as if the defines evaluated simultaneously. So in
      theory Eva’s view point is the most consistent one.

    2. For the second question; yes, we can but it should be implemented in a error
      handling manner: If we got into unassigned value error, then try to delegate
      the execution of that assignment statement (to stack another place and try
      each of them when we reach the end of assignment statement; if there are no
      reduction in stack after one-loop finally it should raise an error – give
      up!). This algorithm is expensive since it recursively reduces the stacked
      statements until there is no change.

  • Exercise 4.20

    • a.

      Here we implement the strategy written in text book. That is, to transform from

      1. (letrec ((u <e1>)
      2. (v <e2>))
      3. <e3>)

      to

      1. (let ((u '*unassigned*)
      2. (v '*unassigned*))
      3. (set! u <e1>)
      4. (set! v <e2>)
      5. <e3>)

      So we can code the test code using above example:

      1. (pretty-print (letrec->let-assignment
      2. (letrec ((u <e1>)
      3. (v <e2>))
      4. <e3>)))
      5. (let ((u '*unassigned*)
      6. (v '*unassigned*))
      7. (set! u <e1>)
      8. (set! v <e2>)
      9. <e3>)
      10. ;Unspecified return value

      Then code:

      1. (define (install-eval-letrec)
      2. (define bindings cadr) ;((u <e1>) (v <e2>))
      3. (define body cddr) ;<e3>
      4. (define (letrec->let-assignment exp)
      5. (let ((binds (bindings exp))
      6. (rest-body (body exp)))
      7. (let ((vars (map car binds))
      8. (exps (map cadr binds)))
      9. (make-let
      10. (map (lambda (var) (list var (list 'quote undef)))
      11. vars)
      12. (append
      13. (map (lambda (var exp) (make-assignment var exp))
      14. vars exps)
      15. rest-body)))))
      16. (put 'eval 'letrec
      17. (lambda (exp env)
      18. (eval (letrec->let-assignment exp) env)))
      19. ;; test
      20. (letrec->let-assignment
      21. '(letrec ((u <e1>)
      22. (v <e2>))
      23. <e3>)))

      And test!

      1. (pretty-print (install-eval-letrec))
      2. (let ((u '*unassigned*) (v '*unassigned*))
      3. (set! u <e1>)
      4. (set! v <e2>)
      5. <e3>)
      6. ;Unspecified return value
    • b.

      Solely let, we cannot express recursive process within the body of procedure.
      I’ve drawn the environment diagram reasoning about this assertion.

  • Exercise 4.21

    • a.

      1. ((lambda (n)
      2. ((lambda (fact)
      3. (fact fact n))
      4. (lambda (ft k)
      5. (if (= k 1)
      6. 1
      7. (* k (ft ft (- k 1)))))))
      8. 10)
      9. ;Value: 3628800

      It works well. By using this technique – specifically the lambda calculus by
      Alonzo Church – we can reason our program’s behavior using, back to our old
      friend, substitution model.

      Fibonacci number can be calculated by using this strategy:

      1. ((lambda (n)
      2. ((lambda (fibo)
      3. (fibo fibo n))
      4. (lambda (fib k)
      5. (if (< k 2)
      6. k
      7. (+ (fib fib (- k 1))
      8. (fib fib (- k 2)))))))
      9. 4)
      10. ;Value: 3

      The common pattern in above procedures is that the inner procedure, which do all
      the serious process, take additional parameter. We can think of this parameter
      as self reference; as soon as we calculate the type contract of this self
      parameter, we realize this is same as that type of the procedure of discourse.

    • b.

      1. (define (f x)
      2. ((lambda (even? odd?)
      3. (even? even? odd? x))
      4. (lambda (ev? od? n)
      5. (if (= n 0) true (od? ev? od? (-1+ n))))
      6. (lambda (ev? od? n)
      7. (if (= n 0) false (ev? ev? od? (-1+ n))))))

      then test:

      1. (f 5)
      2. ;Value: #f
      3. (f 4)
      4. ;Value: #t

Separating Syntactic Analysis from Execution

  • Exercise 4.22

    We can get what we want by mapping the expression part to let->combination to
    analyze-application. First let we make our analyzer to be data-directed
    style, which is analogous to ex 4.3:

    1. (define (analyze exp)
    2. (cond ((self-evaluating? exp)
    3. (analyze-self-evaluating exp))
    4. ((variable? exp) (analyze-variable exp))
    5. (else
    6. (let ((op (and (tagged-exp? exp)
    7. (get 'analyze (type-tag exp)))))
    8. (cond (op (op exp))
    9. ((application? exp)
    10. (analyze-application exp))
    11. (else
    12. (error "Unknown expression type -- ANALYZE" exp)))))))

    Then extend our eval ad-hoc table into more general 2 dimensional
    hash-table:

    1. (define operation-table (make-hash-table))
    2. (define (put op type item)
    3. (let ((type-table (hash-table-ref operation-table op (lambda () #f))))
    4. (if type-table
    5. (hash-table-set! type-table type item)
    6. (let ((type-table (make-hash-table)))
    7. (hash-table-set! type-table type item)
    8. (hash-table-set! operation-table op type-table)))))
    9. (define (get op type)
    10. (let ((type-table
    11. (hash-table-ref operation-table op (lambda () #f))))
    12. (and type-table
    13. (hash-table-ref type-table type (lambda () #f)))))

    And then

    1. (define (install-analyze-clauses)
    2. (put 'analyze 'quote analyze-quoted)
    3. (put 'analyze 'set! analyze-assignment)
    4. (put 'analyze 'define analyze-definition)
    5. (put 'analyze 'if analyze-if)
    6. (put 'analyze 'lambda analyze-lambda)
    7. (put 'analyze 'begin (lambda (exp) (analyze-sequence (begin-actions exp))))
    8. (put 'analyze 'cond (lambda (exp) (analyze (cond->if exp))))
    9. 'done)

    Finally we can return our original task:

    1. (define (install-analyze-let)
    2. (define let->combination
    3. (cadr (assq 'let->combination (install-eval-let))))
    4. (put 'analyze 'let (lambda (exp) (analyze-application (let->combination exp)))))

    To make available the let->combination procedure, which is defined in
    install-eval-let, I’ve made following change to the end of install-eval-let:

    1. *** the last line of install-eval-let
    2. `((let->combination ,let->combination))

    That is, the alist of procedures, which we want to export. And you may find
    Scheme manual about quasi-quote useful.

  • Exercise 4.23

    The procedure of Alyssa’s version start to link all the sequence expressions
    into one analyzed expression in execution time; whereas the version of text do
    link the sequence in the analysis time. As consequence, the number of steps it
    needs when it executed are quite different among both. I’ve wrote the sample
    execute in my digital paper using the substitution model since the execution
    does not involve any side effect (the side effect of executed statement does not
    affect the behavior of this level of abstraction).

  • Exercise 4.24

    • Compare Execution Time

      Here we compare the time it needs to execute application of test procedure. For
      the test procedure we use procedure which calculate Fibonacci number using multi
      recursive process (since it is expensive, we can measure the time more
      apparently).

      The definition of test procedure:

      1. (define (fib n)
      2. (cond ((= n 0) 0)
      3. ((= n 1) 1)
      4. (else (+ (fib (- n 1))
      5. (fib (- n 2))))))

      And we can extract only the execution of application not the definition by
      using eval, namely

      1. (eval '(fib 33) the-global-environment)

      Then we can time it needs to execute whole of this process the variation from
      project 3:

      1. (define (timed proc)
      2. (let ((start (runtime)))
      3. (let ((val (proc)))
      4. (newline)
      5. (display "time expended: ")
      6. (display (- (runtime) start))
      7. val)))

      Then we can use this as

      1. (timed (lambda () (eval '(fib 33) the-global-environment)))

      As we designed the experiments, now let’s run those:

      1. ;;; Analysis
      2. (install-analyze-clauses)
      3. (eval
      4. '(define (fib n)
      5. (cond ((= n 0) 0)
      6. ((= n 1) 1)
      7. (else (+ (fib (- n 1))
      8. (fib (- n 2))))))
      9. the-global-environment)
      10. (timed (lambda () (eval '(fib 20) the-global-environment)))
      11. time expended: 1.6300000000000026
      12. ;Value: 6765

      Turns out (fib 33) way more expensive to wait.

      Then without analysis version:

      1. (install-eval-clauses)
      2. (eval
      3. '(define (fib n)
      4. (cond ((= n 0) 0)
      5. ((= n 1) 1)
      6. (else (+ (fib (- n 1))
      7. (fib (- n 2))))))
      8. the-global-environment)
      9. (timed (lambda () (eval '(fib 20) the-global-environment)))
      10. time expended: 3.21
      11. ;Value: 6765

      With analysis, we halved the time it takes. Quite impressive isn’t it?

      For curiosity, let we run also the primitive evaluator of our Scheme!

      1. (define (fib n)
      2. (cond ((= n 0) 0)
      3. ((= n 1) 1)
      4. (else (+ (fib (- n 1))
      5. (fib (- n 2))))))
      6. ;Value: fib
      7. (timed (lambda () (fib 20)))
      8. time expended: .02999999999999936
      9. ;Value: 6765

      Huge difference!

    • Compare the time spent by analysis versus by execution

      Here is the previous fib procedure’s:

      1. (timed
      2. (lambda ()
      3. (repeat 1000 (lambda ()
      4. (eval '(fib 2)
      5. the-global-environment)))))
      6. time expended: .20000000000000018
      7. ;Value: done

      And that of simple execution:

      1. (timed
      2. (lambda ()
      3. (repeat 1000 (lambda ()
      4. (eval '(fib 1)
      5. the-global-environment)))))
      6. time expended: .07000000000000028
      7. ;Value: done

      Or simple recursive execution:

      1. (timed
      2. (lambda ()
      3. (repeat 1000 (lambda ()
      4. (eval '(fib 2)
      5. the-global-environment)))))
      6. time expended: .20000000000000018
      7. ;Value: done

      Here we used repeat to measure the time otherwise unable to estimate:

      1. (define (repeat n proc)
      2. (let loop ((k n))
      3. (if (> k 0)
      4. (begin (proc)
      5. (loop (-1+ k)))
      6. 'done)))

      Or let us test the sequential statements:

      1. ;; Or those of sequential statements
      2. (define test-sequential
      3. '(define (test-sequential)
      4. (define x 0)
      5. (set! x (1+ x))
      6. (set! x (1+ x))
      7. (set! x (1+ x))
      8. (set! x (1+ x))
      9. (set! x (1+ x))
      10. x))
      11. ;; analysis time
      12. (timed (lambda () (repeat 1000 (lambda () (analyze test-sequential)))))
      13. time expended: .08999999999999986
      14. ;Value: done
      15. ;; execution time
      16. (eval test-sequential the-global-environment)
      17. (timed (lambda ()
      18. (repeat 1000 (lambda ()
      19. (eval '(test-sequential) the-global-environment)))))
      20. time expended: .17000000000000015
      21. ;Value: done

Variations on a Scheme – Lazy Evaluation

Normal Order and Applicative Order

  • Exercise 4.25

    It will falls into infinite loop since what ever argument we hand over, it
    should call itself with different argument to pass the unless procedure as
    parameter – as our underlying Scheme evaluate in applicative order, evaluator
    first evaluate the operands of application of compound procedure and then apply.

    And yes, our procedure will work in normal-order language since in normal-order
    language, expressions only got evaluated when the expression is operation in
    application or the operation part unwound into primitive procedure.

    If we apply above discussion into our factorial application, it unwound into
    application of unless, which in turn got into if expression that is special
    form; by the rule of if evaluation, now the predicate part evaluated and
    depending on that value, in turn, whether consequent part of alternative part
    got evaluated and so on.

  • Exercise 4.26

    Here is the unless as derived form:

    1. (define (unless->if exp)
    2. (make-if (unless-pred exp)
    3. (unless-alter exp)
    4. (unless-conseq exp)))
    5. (define unless-pred cadr)
    6. (define unless-conseq caddr)
    7. (define unless-alter cadddr)

    Let us think the discussion using more simple example – not unless itself –
    the and or or special form. It has the ability to be used as “short circuit” the
    code. I couldn’t count the number of times if I had or as non-special form so
    that I can code up with higher order function like fold-right.

    Back to the original question, we can think about the possibilities if we
    allowed to use unless with combination of stream processing frame work. By
    using that combination, we could code event driven system in functional
    programming style as we use the unless to control the input stream.

    Well, in any case, if we allowed to use unless not as special form, we can
    pass that as argument of another higher order procedures; or as the value of
    application.

An Interpreter with Lazy Evaluation

  • Exercise 4.27

    The first <response> should be 1 and that of second <response> and third
    would be 10 and 2 respectively since

    1. The value expression of w is evaluated by eval-definition but as it’s
      application of compound procedure, it will evaluate the body of given
      compound procedure with delayed arguments.
    2. Since the first statement of the body is special form with application of
      primitive procedure, it will change the value of count from 0 to 1.
    3. The last statement of that is variable, it will lookup in the extended
      environment and return the delayed value, that is the promise to evaluate
      (id 10) in that environment.

    Here is the verification:

    1. ;;; L-Eval input:
    2. (define count 0)
    3. ;;; L-Eval value:
    4. ok
    5. ;;; L-Eval input:
    6. (define (id x)
    7. (set! count (+ count 1))
    8. x)
    9. ;;; L-Eval value:
    10. ok
    11. ;;; L-Eval input:
    12. (define w (id (id 10)))
    13. ;;; L-Eval value:
    14. ok
    15. ;;; L-Eval input:
    16. count
    17. ;;; L-Eval value:
    18. 1
    19. ;;; L-Eval input:
    20. w
    21. ;;; L-Eval value:
    22. 10
    23. ;;; L-Eval input:
    24. count
    25. ;;; L-Eval value:
    26. 2
  • Exercise 4.28

    The example should be the case where the evaluated value is thunk and then it
    used as operator in combination.

    Generally, we need this extra complexity for dealing with the case where the
    expression is itself result of combination of applying compound procedure where
    it participate as argument.

    We can deduce this using the previous exercise as example. Observe that in the
    previous exercise that the arguments in the application (of compound procedure)
    are made thunk by list-of-delayed-args. By using this observation, we can make
    the situation where satisfy the property outlined above:

    1. (((lambda (x) x) +) 2 4)

    Then verification:

    1. (thunk? (eval '((lambda (x) x) +) the-global-environment))
    2. ;Value: #t

    So we need the actual-value to get the value, which in turn applied to given
    operands:

    1. (thunk? (actual-value '((lambda (x) x) +) the-global-environment))
    2. ;Value: #f
  • Exercise 4.29

    The good example of such program should exhibit the situation where some object
    evaluated more than once; so

    1. (factorial (factorial 20))

    would make huge difference between with memoization and without that.

    And for the second question, we can reason about this as follows:

    • For with memoization:
      1. Evaluate (square (id 10)), which in turn
      2. extend the environment with binding x: (thunk (id 10) env)
        and evaluate (* x1 x2) (here we used x1 and x2 instead x since
        we need to take the time when we accessed to that variable since
        memoization introduce assignment implicitly);
      3. force x1, which in turn evaluate (id 10); it leads to set count to
        be 1 and in the environment set x to be evaluated thunk with value 10;
      4. force x2, which already evaluated by force x1, so it just return 10;
      5. returned to (* x1 x2), as now the values are obtained, it return 100 as
        final value.
    • For without memoization:
      1. Evaluate (square (id 10)), which in turn
      2. extend the environment with binding x: (thunk (id 10) env)
        and evaluate (* x1 x2);
      3. force x1, which in turn evaluate (id 10); it leads to set count to
        be 1;
      4. force x2, which in turn evaluate (id 10); it leads to set count to
        be 2;
      5. returned to (* x1 x2), as now the values are obtained, it return 100 as
        final value.

    So the first response is same in both as 100; the second are 1, 2 in with
    memoization and without that respectively.

  • Exercise 4.30

    • a.

      Since

      • as this is application of compound procedure;
      • it evaluate the body of for-each with delayed arguments;
      • in turn, if-predicate force items to be actual value;
      • by the first statement of begin of for-each is application so proc got
        forced;
      • it leads the body of proc evaluated with thunked (car items) – 57 in this
        example;
      • as all of the statement of proc is application of primitive procedures the
        argument, x, is forced and evaluated – 57 is displayed at this moment;
      • now another for-each is evaluated same as above since all the conditions are
        same for applying the above argument.

      By above argument all the statements in the sequence got forced.

    • b.

      As we noted in ex 4.28, the thunked expression is not evaluated as value without
      forcing it – by actual-value –, in turn, user can thunk the expression only
      via application of compound procedure.

      So by applying this observation to the given Cy’s examples with original
      eval-sequence, we got (p1 1)(1 2) and (p2 1) → 1; since while the
      first does not involve any application of compound procedure; the second
      assignment thunked and that, in turn, evaluated without forcing it. But with
      Cy’s modified version return (1 2) in both cases since now all the
      intermediate statement forced.

    • c.

      Since already forced value can not be changed by additional force-it. As we
      noted in a. each statement of in the sequence already fully evaluated –
      forced, so the modification does not affect at all to that specific example.

    • d.

      Whatever choice we made, the evaluation of sequence being made in unexpected or
      hard to be estimated time. It is totally due to the nature of normal-order
      evaluation rule: By choosing the normal-order evaluation, we decoupled the time
      when the expression got assembled from the time the expression actually got
      evaluated. However, using assignment, we are meant to control the “time”
      explicitly in the computer, that is when the evaluation is being carried out,
      which mirrored the real world. The time is “state.” So those – the assignment
      and normal-order rule – are incompatible in themselves; we shouldn’t expect to
      use sequence of expressions per se side-effects.

  • Exercise 4.31

    We need to change list-of-delayed-args to encompass this new extension. And
    also we need to change the force-it and add additional data structure that is
    analogous to thunkmemo-thunk.

    We decide thunk to represent the delayed object without memoized; memo-thunk
    for delayed object with memoization.

    Let us code what we designed using digital paper!

    1. (define (apply procedure arguments env)
    2. (cond ((primitive-procedure? procedure)
    3. (apply-primitive-procedure
    4. procedure
    5. (list-of-arg-values arguments env))) ; changed
    6. ((compound-procedure? procedure)
    7. (eval-sequence
    8. (procedure-body procedure)
    9. (let ((params (procedure-parameters procedure)))
    10. (extend-environment
    11. (map param-name params)
    12. (list-if-delayed-args
    13. arguments (map param-type params) env) ; changed
    14. (procedure-environment procedure)))))
    15. (else
    16. (error
    17. "Unknown procedure type -- APPLY" procedure))))
    18. ;; param ADT
    19. (define (typed-param? param)
    20. (and (pair? param)
    21. (pair? (cdr param))))
    22. (define (param-type param)
    23. (if (typed-param? param)
    24. (cadr param)
    25. 'strict))
    26. (define (param-name param)
    27. (if (typed-param? param)
    28. (car param)
    29. param))
    30. (define (list-if-delayed-args exps types env)
    31. (cond ((no-operands? exps) '())
    32. ((null? types)
    33. (error "the number of arguments do not agree with procedure"
    34. ;; actually whether we should use more sophisticated error message
    35. ;; or should delegate the error raise to extend-environment
    36. ;; current error message is not informative enough to be useful.
    37. exps))
    38. (else
    39. (cons
    40. ((case (first types)
    41. ((strict) actual-value)
    42. ((lazy) delay-it)
    43. ((lazy-memo) delay-memo-it)
    44. (else (error "Unknown parameter type")))
    45. (first-operand exps)
    46. env)
    47. (list-if-delayed-args (rest-operands exps)
    48. (cdr types)
    49. env)))))
    50. ;; memo-thunk ADT
    51. (define (delay-memo-it exp env)
    52. (list 'memo-thunk exp env))
    53. (define (memo-thunk? obj)
    54. (tagged-list? obj 'memo-thunk))
    55. (define (force-it obj)
    56. (cond ((thunk? obj)
    57. (actual-value (thunk-exp obj) (thunk-env obj)))
    58. ((memo-thunk? obj)
    59. (let ((result (actual-value
    60. (thunk-exp obj)
    61. (thunk-env obj))))
    62. (set-car! obj 'evaluated-thunk)
    63. (set-car! (cdr obj) result) ; replace exp with its value
    64. (set-cdr! (cdr obj) '()) ; forget unneeded env
    65. result))
    66. ((evaluated-thunk? obj)
    67. (thunk-value obj))
    68. (else obj)))

    Then test! Here we use ex 4.29 for test:

    1. ;;; L-Eval input:
    2. (define count 0)
    3. ;;; L-Eval value:
    4. ok
    5. ;;; L-Eval input:
    6. (define count-memo 0)
    7. ;;; L-Eval value:
    8. ok
    9. ;;; L-Eval input:
    10. (define (id-memo (x lazy-memo))
    11. (set! count-memo (+ count-memo 1))
    12. x)
    13. ;;; L-Eval value:
    14. ok
    15. ;;; L-Eval input:
    16. (define (id-lazy (x lazy))
    17. (set! count (+ count 1))
    18. x)
    19. ;;; L-Eval value:
    20. ok
    21. ;;; L-Eval input:
    22. (define (square-memo (x lazy-memo))
    23. (* x x))
    24. ;;; L-Eval value:
    25. ok
    26. ;;; L-Eval input:
    27. (define (square-lazy (x lazy))
    28. (* x x))
    29. ;;; L-Eval value:
    30. ok
    31. ;;; L-Eval input:
    32. (square-memo (id-memo 10))
    33. ;;; L-Eval value:
    34. 100
    35. ;;; L-Eval input:
    36. count-memo
    37. ;;; L-Eval value:
    38. 1
    39. ;;; L-Eval input:
    40. (square-lazy (id-lazy 10))
    41. ;;; L-Eval value:
    42. 100
    43. ;;; L-Eval input:
    44. count
    45. ;;; L-Eval value:
    46. 2

    Works!

  • Exercise 4.32

    Now we can construct reversed stream, that is, we use car to traverse and
    cdr to store elements. With this extra concept combined with original stream,
    we can now manipulate line not only ray! – what we defined as integers in
    stream was actually just natural numbers not integers, strictly speaking. More
    over, we can manipulate tree operations lazily as well as list structure. So
    until this point, to manipulate tree in combination with stream, we needed to
    first construct whole tree strictly and then convert that into stream. If we
    represent the graph in lazy tree structure, conceptually it equivalent to the
    non-deterministic programming, which decomposed automatic search from
    computation. We can think this differently: By allowed to manipulate not only
    future – cdr part – but also past – car part; so we can now traverse the
    time branches back and forth freely.

  • Exercise 4.33

    The reason it produce an error is as follows:

    1. Quoted expression processed by the read procedure, by which we get the
      input from terminal, of underlying Lisp language; it is “list” in
      implementation language not that in language being implemented.
    2. So we need transform from list structure of implementation language into that
      of language being implemented.

    Here is the code do the right thing:

    1. (define (list->list lst)
    2. (fold-right (lambda (item ->list)
    3. `(cons ,item ,->list))
    4. '(quote ())
    5. lst))
    6. (define (text-of-quotation exp)
    7. (let ((contents (cadr exp)))
    8. (if (list? contents)
    9. (eval (list->list contents) the-global-environment)
    10. contents)))

    Then let’s test!

    1. ;;; setup
    2. (eval cons-def the-global-environment)
    3. (eval car-def the-global-environment)
    4. (eval cdr-def the-global-environment)
    5. ;;; test quotation
    6. (actual-value '(car '(a b c)) the-global-environment)
    7. 
    8. ;Quit!

    Unfortunately, it doesn’t return the control! It turns out due to the test
    list? since

    1. (list? '())
    2. ;Value: #t

    So

    1. (actual-value '(quote ()) the-global-environment)
    2. 
    3. ;Quit!

    runs forever!

    Fix the procedure as

    1. (define (text-of-quotation exp)
    2. (let ((contents (cadr exp)))
    3. (if (and (not (null? contents))
    4. (list? contents))
    5. (eval (list->list contents) the-global-environment)
    6. contents)))

    Then re-run:

    1. (actual-value '(car '(a b c)) the-global-environment)
    2. ;Unbound variable a

    Now the problem is that list->list does not quote the element! Let’s fix:

    1. (define (list->list lst)
    2. (fold-right (lambda (item ->list)
    3. `(cons ',item ,->list))
    4. '(quote ())
    5. lst))

    Now it produces right result:

    1. (actual-value '(car '(a b c)) the-global-environment)
    2. ;Value: a
  • Exercise 4.34

    To print the pair, we need to recognize whether given object is pair by pair?
    We can exploit the message passing paradigm:

    1. Then (define (pair? x) (x 'pair?))
    2. This will work if we request only to pair object; but predicate meant to
      check whether given argument satisfy specific condition. So predicate should
      not signal error unless it provided certain condition should be satisfied but
      violated by user (although it is not appropriate to assume informal
      contraction without verification).
    3. So we’d better to take another way: Make cons, car, cdr non-strict
      primitive procedure; by including pair? as primitive (strict) procedure, we
      got what we wanted.

    So we need to modify apply to cope with primitive non-strict application, and
    the new syntax procedure for non-strict primitive procedures, finally the
    setup-environment:

    1. (define (apply procedure arguments env)
    2. (cond ((primitive-procedure? procedure) ;strict primitive
    3. (apply-primitive-procedure
    4. procedure
    5. (list-of-arg-values arguments env)))
    6. ((non-strict-primitive-procedure? procedure) ;non-strict primitive
    7. (apply-primitive-procedure
    8. procedure
    9. (list-of-delayed-args arguments env)))
    10. ((compound-procedure? procedure)
    11. (eval-sequence
    12. (procedure-body procedure)
    13. (extend-environment
    14. (procedure-parameters procedure)
    15. (list-of-delayed-args arguments env)
    16. (procedure-environment procedure))))
    17. (else
    18. (error
    19. "Unknown procedure type -- APPLY" procedure))))
    20. (define (non-strict-primitive-procedure? proc)
    21. (tagged-list? proc 'non-strict))
    22. (define (non-strict-procedure-names)
    23. (map car
    24. non-strict-procedures))
    25. (define (non-strict-procedure-objects)
    26. (map (lambda (proc) (list 'non-strict (cadr proc)))
    27. non-strict-procedures))
    28. (define (setup-environment)
    29. (let ((initial-env
    30. (extend-environment
    31. (non-strict-procedure-names)
    32. (non-strict-procedure-objects)
    33. (extend-environment (primitive-procedure-names)
    34. (primitive-procedure-objects)
    35. the-empty-environment))))
    36. (define-variable! 'true true initial-env)
    37. (define-variable! 'false false initial-env)
    38. initial-env))
    39. ;; initialize the startup environment
    40. (define primitive-procedures
    41. (append (list
    42. (list 'pair? (lambda (p) (tagged-list? p 'pair)))
    43. (list 'car cadr)
    44. (list 'cdr cddr))
    45. primitive-procedures))
    46. (define non-strict-procedures
    47. `((cons ,(lambda (x y) (cons 'pair (cons x y))))))

    We need to make pair as tagged list to discern with other data structure. Also
    note that we made only cons non-strict; but not the car and cdr since
    there is no need to make those non-strict and further more, if we make them so,
    it only make the implementation complicate to select the part appropriately! You
    need to reason this by experimenting with non-strict car and cdr.

    Then test!

    1. (actual-value '(car (cons 2 4)) the-global-environment)
    2. ;Value: 2
    3. (thunk? (eval '(car (cons 2 4)) the-global-environment))
    4. ;Value: #t
    5. (eval '(pair? (cons 2 4)) the-global-environment)
    6. ;Value: #t

    Now we return to display the lazy pair appropriately. Here we used structural
    induction on object. For the pair object, we also used induction on depth
    combined with structural induction on o2. Finally for the choice of
    representation of thunk, we took that of stream:

    1. (define (represent-object o)
    2. (cond ((compound-procedure? o)
    3. (list 'compound-procedure
    4. (procedure-parameters o)
    5. (procedure-body o)
    6. '<procedure-env>))
    7. ((thunk? o)
    8. '...)
    9. ((evaluated-thunk? o)
    10. (represent-object (thunk-value o)))
    11. ((tagged-list? o 'pair)
    12. (represent-pair (cdr o)))
    13. (else o)))
    14. (define (represent-pair p)
    15. (let ((rep1 (represent-object (car p))) ;induction on depth
    16. (o2 (cdr p)))
    17. (cond ((thunk? o2)
    18. (list rep1 (represent-object o2)))
    19. ((evaluated-thunk? o2)
    20. (cons rep1 (represent-object o2)))
    21. ((tagged-list? o2 'pair)
    22. (cons rep1 (represent-pair (cdr o2))))
    23. (else ;atomic value
    24. (cons rep1 (represent-object o2))))))

    Then modify user-print as follows:

    1. (define (user-print object)
    2. (display (represent-object object)))

    Then test:

    1. ;;; L-Eval input:
    2. (define test-display (cons ((lambda (x) (+ 2 x)) 3) (cons 2 '())))
    3. ;;; L-Eval value:
    4. ok
    5. ;;; L-Eval input:
    6. test-display
    7. ;;; L-Eval value:
    8. (... ...)
    9. ;;; L-Eval input:
    10. (car (cdr test-display))
    11. ;;; L-Eval value:
    12. 2
    13. ;;; L-Eval input:
    14. test-display
    15. ;;; L-Eval value:
    16. (... 2 ...)
    17. ;;; L-Eval input:
    18. (cdr (cdr test-display))
    19. ;;; L-Eval value:
    20. ()
    21. ;;; L-Eval input:
    22. test-display
    23. ;;; L-Eval value:
    24. (... 2)
    25. ;;; L-Eval input:
    26. (car test-display)
    27. ;;; L-Eval value:
    28. 5
    29. ;;; L-Eval input:
    30. test-display
    31. ;;; L-Eval value:
    32. (5 2)

    For the completeness, here I add to user-print additional feature, detect the
    shared structure in lazy pair:

    1. (define (pair?* o) (tagged-list? o 'pair))
    2. (define car* cadr)
    3. (define cdr* cddr)
    4. (define (extract-sharings object)
    5. (let ((tracked '())
    6. (sharings '()))
    7. (define scan
    8. (lambda (o)
    9. (define (mutate-list! o not-tracked-op)
    10. (if (memq o tracked)
    11. (if (not (memq o sharings))
    12. (set! sharings (cons o sharings))
    13. 'done)
    14. (begin (set! tracked (cons o tracked))
    15. (not-tracked-op o))))
    16. (cond ((evaluated-thunk? o)
    17. (scan (thunk-value o)))
    18. ((pair?* o)
    19. (mutate-list!
    20. o (lambda (o)
    21. (scan (car* o))
    22. (scan (cdr* o))))))))
    23. (scan object)
    24. sharings))
    25. (define (display-entry object)
    26. (let ((sharings (extract-sharings object))
    27. (issue-table '(*issue*))) ;hash-table won't work!
    28. (define (issued-number o)
    29. (cond ((assq o (cdr issue-table)) => cadr)
    30. (else #f)))
    31. (define issue!
    32. (let ((id 0)) ;identification number
    33. (lambda (o)
    34. (let ((to-be-issued id))
    35. (set-cdr! issue-table (cons (list o to-be-issued)
    36. (cdr issue-table)))
    37. (set! id (1+ id))
    38. to-be-issued))))
    39. (define (display-issued-object id)
    40. (display "#")
    41. (display id)
    42. (display "#"))
    43. (define (display-issuing id)
    44. (display "#")
    45. (display id)
    46. (display "="))
    47. (define (display-object o)
    48. (cond ((compound-procedure? o)
    49. (display (list 'compound-procedure
    50. (procedure-parameters o)
    51. (procedure-body o)
    52. '<procedure-env>)))
    53. ((thunk? o)
    54. (display '...))
    55. ((evaluated-thunk? o)
    56. (display-object (thunk-value o)))
    57. ((pair?* o)
    58. (display-pair o))
    59. (else (display o))))
    60. (define (display-pair p)
    61. (define (display-pair-entry p)
    62. (display "(")
    63. (display-object (car* p))
    64. (display-iter (cdr* p))
    65. (display ")"))
    66. (define (display-shared-or-default exp default-op pad1-op pad2-op)
    67. (if (memq exp sharings) ;it is shared structure
    68. (let ((id (issued-number exp)))
    69. (if id
    70. (begin (pad1-op)
    71. (display-issued-object id))
    72. (begin (pad2-op)
    73. (display-issuing (issue! exp))
    74. (display-pair-entry exp))))
    75. (default-op exp)))
    76. (define (display-iter exp)
    77. (cond ((null? exp))
    78. ((evaluated-thunk? exp)
    79. (display-iter (thunk-value exp)))
    80. ((pair?* exp)
    81. (display-shared-or-default
    82. exp
    83. (lambda (p)
    84. (display " ")
    85. (display-object (car* p))
    86. (display-iter (cdr* p)))
    87. (lambda () (display " . "))
    88. (lambda () (display " "))))
    89. ((thunk? exp)
    90. (display " ")
    91. (display-object exp))
    92. (else
    93. (display " . ")
    94. (display-object exp))))
    95. (display-shared-or-default
    96. p (lambda (p) (display-pair-entry p))
    97. (lambda () 'ignore)
    98. (lambda () 'ignore)))
    99. (display-object object)
    100. (set-cdr! issue-table '()))) ;clear the cached

    Then our user-print became

    1. (define (user-print object)
    2. ;; (display (represent-object object))
    3. (display-entry object))

    Then test code:

    1. ;;; L-Eval input:
    2. (define ones (cons 1 ones))
    3. ;;; L-Eval value:
    4. ok
    5. ;;; L-Eval input:
    6. ones
    7. ;;; L-Eval value:
    8. (... ...)
    9. ;;; L-Eval input:
    10. (car ones)
    11. ;;; L-Eval value:
    12. 1
    13. ;;; L-Eval input:
    14. (cdr ones)
    15. ;;; L-Eval value:
    16. #0=(1 . #0#)
    17. ;;; L-Eval input:
    18. (define one (cons 1 two)) ;mutual recursive definition
    19. ;;; L-Eval value:
    20. ok
    21. ;;; L-Eval input:
    22. (define two (cons 2 one))
    23. ;;; L-Eval value:
    24. ok
    25. ;;; L-Eval input:
    26. (car one)
    27. ;;; L-Eval value:
    28. 1
    29. ;;; L-Eval input:
    30. (cdr one)
    31. ;;; L-Eval value:
    32. (... ...)
    33. ;;; L-Eval input:
    34. (car two)
    35. ;;; L-Eval value:
    36. 2
    37. ;;; L-Eval input:
    38. (cdr two)
    39. ;;; L-Eval value:
    40. #0=(1 2 . #0#)
    41. ;;; L-Eval input:
    42. one
    43. ;;; L-Eval value:
    44. #0=(1 2 . #0#)
    45. ;;; L-Eval input:
    46. two
    47. ;;; L-Eval value:
    48. #0=(2 1 . #0#)

Variations on a Scheme – Nondeterministic Computing

  • Exercise 4.35

    Note that this procedure should behave similar with an-element-of:

    1. (define (an-integer-between low high)
    2. (require (<= low high))
    3. (amb low (an-integer-between (1+ low) high)))

    Then here is the test:

    1. ;;; Amb-Eval input:
    2. (an-integer-between 1 10)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. 1
    6. ;;; Amb-Eval input:
    7. try-again
    8. ;;; Amb-Eval value:
    9. 2
    10. ;;; Amb-Eval input:
    11. try-again
    12. ;;; Amb-Eval value:
    13. 3
    14. ;;; Amb-Eval input:
    15. try-again
    16. ;;; Amb-Eval value:
    17. 4
    18. ;;; Amb-Eval input:
    19. try-again
    20. ;;; Amb-Eval value:
    21. 5
    22. ;;; Amb-Eval input:
    23. try-again
    24. ;;; Amb-Eval value:
    25. 6
    26. ;;; Amb-Eval input:
    27. try-again
    28. ;;; Amb-Eval value:
    29. 7
    30. ;;; Amb-Eval input:
    31. try-again
    32. ;;; Amb-Eval value:
    33. 8
    34. ;;; Amb-Eval input:
    35. try-again
    36. ;;; Amb-Eval value:
    37. 9
    38. ;;; Amb-Eval input:
    39. try-again
    40. ;;; Amb-Eval value:
    41. 10
    42. ;;; Amb-Eval input:
    43. try-again
    44. ;;; There are no more values of
    45. (an-integer-between 1 10)
  • Exercise 4.36

    The specified idea coded as

    1. (define (a-pythagorean-triple-from low high)
    2. (let ((i (an-integer-starting-from low)))
    3. (let ((j (an-integer-starting-from i)))
    4. (let ((k (an-integer-starting-from j)))
    5. (require (= (+ (* i i) (* j j)) (* k k)))
    6. (list i j k)))))

    Unfortunately the last let expression doesn’t end; it tries all the integers
    higher than j. So it will never tries other than i equals low, j equals low.

    So we need to constrain k using following rules – the triangle inequality:

    • i2+j2 ≥ k2 ⇔ i+j > k for i,j ≥ 1

    This also would not work since now i is fixed to low; this kind problem is
    exactly same as before, when we tried to produce stream of pairs from two
    input streams. We need to ensure arbitrary element should appear in the
    resulting stream after finite number of cdr ing down.

    Can we fix current problem using the analogous strategy from stream’s? Or can we
    come up with nondeterminism specific algorithm?

    Here we try the latter approach using the observation from
    a-pythagorean-triple-between; in that procedure, it uses strategy of
    elimination – at any stage of computation, it removes the cases where i lower
    than current i, e.g. if it started from low = 2 and current i is 4 then
    it is provided that every case where lowi ≤ 4; similar argument
    applied to j and k –, we can apply that strategy to this problem as k to
    be the first stage of computation.

    Here is the code:

    1. (define (a-pythagorean-triple-from low)
    2. (let ((k (an-integer-starting-from low)))
    3. (let ((i (an-integer-between low k)))
    4. (let ((j (an-integer-between i k)))
    5. (require (= (+ (* i i) (* j j)) (* k k)))
    6. (list i j k)))))

    And test:

    1. ;;; Amb-Eval input:
    2. (a-pythagorean-triple-from 1)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. (3 4 5)
    6. ...
    7. ;;; Amb-Eval input:
    8. try-again
    9. ;;; Amb-Eval value:
    10. (12 16 20)
    11. ;;; Amb-Eval input:
    12. try-again
    13. ;;; Amb-Eval value:
    14. (7 24 25)

    From this exercise, we learned our amb evaluator can be thought of as
    generator in python as long as it traverse the choice tree in depth-first.
    So until now, amb evaluator allowed us to loop over infinite range, like
    stream allowed to process infinite list. Is this observation is general enough
    to characterize amb evaluator? To answer with this question, we need to
    explore following examples with this mind.

  • Exercise 4.37

    Yes since the number of cases to try out is different in asymptotic complexity.

    The former one is Θ(n3) and this one is Θ(n2), where n is the
    number of element in the given range – [low, high].

    So as long as same in both are asymptotic complexities of computation in each
    step.

    The difference in computation of both is the use of sqrt. From this
    source, sqrt‘s complexity same as the multiplication.

    So consequently this Ben’s version is efficient in asymptotic order.

Examples of Nondeterministic Programs

  • Exercise 4.38

    Here is the result:

    1. ;;; Amb-Eval input:
    2. (multiple-dwelling)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. ((baker 1) (cooper 2) (fletcher 4) (miller 3) (smith 5))
    6. ;;; Amb-Eval input:
    7. try-again
    8. ;;; Amb-Eval value:
    9. ((baker 1) (cooper 2) (fletcher 4) (miller 5) (smith 3))
    10. ;;; Amb-Eval input:
    11. try-again
    12. ;;; Amb-Eval value:
    13. ((baker 1) (cooper 4) (fletcher 2) (miller 5) (smith 3))
    14. ;;; Amb-Eval input:
    15. try-again
    16. ;;; Amb-Eval value:
    17. ((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1))
    18. ;;; Amb-Eval input:
    19. try-again
    20. ;;; Amb-Eval value:
    21. ((baker 3) (cooper 4) (fletcher 2) (miller 5) (smith 1))
    22. ;;; Amb-Eval input:
    23. try-again
    24. ;;; There are no more values of
    25. (multiple-dwelling)

    So, 4 more answers!

  • Exercise 4.39

    Yes, it matter. We can manage to reduce the average number of tests to get aborted or
    succeed. Let we think about the possibilities that each branches passes the
    specific requirement. Then reorder the restrictions in the increasing order
    of this “weight.” After that, we obtain minimum number of tests needed to get answer.

    Here is the reordering:

    1. (define (multiple-dwelling)
    2. (let ((baker (amb 1 2 3 4 5))
    3. (cooper (amb 1 2 3 4 5))
    4. (fletcher (amb 1 2 3 4 5))
    5. (miller (amb 1 2 3 4 5))
    6. (smith (amb 1 2 3 4 5)))
    7. (require
    8. (distinct? (list baker cooper fletcher miller smith)))
    9. (require (> miller cooper))
    10. (require (not (= (abs (- smith fletcher)) 1)))
    11. (require (not (= (abs (- fletcher cooper)) 1)))
    12. (require (not (= baker 5)))
    13. (require (not (= cooper 1)))
    14. (require (not (= fletcher 5)))
    15. (require (not (= fletcher 1)))
    16. (list (list 'baker baker)
    17. (list 'cooper cooper)
    18. (list 'fletcher fletcher)
    19. (list 'miller miller)
    20. (list 'smith smith))))

    Then compare the time consumed:

    1. ;; original version
    2. (timed (lambda ()
    3. (ambeval '(multiple-dwelling) the-global-environment
    4. (lambda (val fail) val) (lambda () 'ignore))))
    5. time expended: .86
    6. ;Value: ((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1))
    7. ;;modified definition
    8. (timed (lambda ()
    9. (ambeval '(multiple-dwelling-modified) the-global-environment
    10. (lambda (val fail) val) (lambda () 'ignore))))
    11. time expended: .77
    12. ;Value: ((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1))
  • Exercise 4.40

    Like we did when we transit from list processing to stream processing, let we
    interleave construction with filtering along with the criterion as before –
    ordering the requirement in “weight”.

    Let we first inspect the result and then discuss what caused that difference.

    Here is the code:

    1. (define (multiple-dwelling-optimized)
    2. (let ((fletcher (amb 1 2 3 4 5)))
    3. (require (not (= fletcher 5)))
    4. (require (not (= fletcher 1)))
    5. (let ((cooper (amb 1 2 3 4 5)))
    6. (require (not (= cooper 1)))
    7. (require (not (= (abs (- fletcher cooper)) 1)))
    8. (let ((miller (amb 1 2 3 4 5)))
    9. (require (> miller cooper))
    10. (let ((baker (amb 1 2 3 4 5)))
    11. (require (not (= baker 5)))
    12. (let ((smith (amb 1 2 3 4 5)))
    13. (require (not (= (abs (- smith fletcher)) 1)))
    14. (require
    15. (distinct? (list baker cooper fletcher miller smith)))
    16. (list (list 'baker baker)
    17. (list 'cooper cooper)
    18. (list 'fletcher fletcher)
    19. (list 'miller miller)
    20. (list 'smith smith))))))))

    Then compare the time it consumed!

    1. (timed (lambda ()
    2. (ambeval '(multiple-dwelling-optimized) the-global-environment
    3. (lambda (val fail) val) (lambda () 'ignore))))
    4. time expended: .14000000000000057
    5. ;Value: ((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1))

    Huge improvement! We could make this optimization since we made that in each
    filtering step the difference of the number of branches, between before and after that
    step, minimum. As the number of remaining branches should be only one, it means
    we traverse the minimum number of branches at the very beginning.

  • Exercise 4.41

    As we noted before, the amb evaluator has a lot of analogy with programming
    with iterative process. So here we implement requested task using loop.

    The typical imperative style coding:

    1. (define (multiple-dwelling)
    2. (let baker-loop ((baker 1))
    3. (let cooper-loop ((cooper 1))
    4. (let fletcher-loop ((fletcher 1))
    5. (let miller-loop ((miller 1))
    6. (let smith-loop ((smith 1))
    7. (if (and (distinct? (list baker cooper fletcher miller smith))
    8. (not (= baker 5))
    9. (not (= cooper 1))
    10. (not (= fletcher 5))
    11. (not (= fletcher 1))
    12. (> miller cooper)
    13. (not (= (abs (- smith fletcher)) 1))
    14. (not (= (abs (- fletcher cooper)) 1)))
    15. (list (list 'baker baker)
    16. (list 'cooper cooper)
    17. (list 'fletcher fletcher)
    18. (list 'miller miller)
    19. (list 'smith smith))
    20. (cond ((< smith 5) (smith-loop (1+ smith)))
    21. ((< miller 5) (miller-loop (1+ miller)))
    22. ((< fletcher 5) (fletcher-loop (1+ fletcher)))
    23. ((< cooper 5) (cooper-loop (1+ cooper)))
    24. ((< baker 5) (baker-loop (1+ baker)))
    25. (else 'failed!)))))))))

    Then test:

    1. (multiple-dwelling)
    2. ;Value: ((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1))

    Just for curiosity, here we also implement the functional version – stream
    version:

    1. (define (multiple-dwelling-stream)
    2. (stream-filter
    3. (lambda (result) result)
    4. (stream-append-map
    5. (lambda (baker)
    6. (stream-append-map
    7. (lambda (cooper)
    8. (stream-append-map
    9. (lambda (fletcher)
    10. (stream-append-map
    11. (lambda (miller)
    12. (stream-map
    13. (lambda (smith)
    14. (and (distinct? (list baker cooper fletcher miller smith))
    15. (not (= baker 5))
    16. (not (= cooper 1))
    17. (not (= fletcher 5))
    18. (not (= fletcher 1))
    19. (> miller cooper)
    20. (not (= (abs (- smith fletcher)) 1))
    21. (not (= (abs (- fletcher cooper)) 1))
    22. (list (list 'baker baker) ;return if all the previous test passed
    23. (list 'cooper cooper)
    24. (list 'fletcher fletcher)
    25. (list 'miller miller)
    26. (list 'smith smith))))
    27. (stream-enumerate-interval 1 5)))
    28. (stream-enumerate-interval 1 5)))
    29. (stream-enumerate-interval 1 5)))
    30. (stream-enumerate-interval 1 5)))
    31. (stream-enumerate-interval 1 5))))

    Actually this pattern appears frequently when we try to emulate loop in
    functional world! So in Scala, by default, for loop desugared into above
    flat-map map combination.

    Test!

    1. (multiple-dwelling-stream)
    2. ;Value: {((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1)) ...}
  • Exercise 4.42

    It is just brain stretching exercise:

    1. (define (either p1 p2)
    2. (or (and p1 (not p2))
    3. (and (not p1) p2)))
    4. (define (solve-Liars)
    5. (let ((Betty (amb 1 2 3 4 5))
    6. (Ethel (amb 1 2 3 4 5))
    7. (Joan (amb 1 2 3 4 5))
    8. (Kitty (amb 1 2 3 4 5))
    9. (Mary (amb 1 2 3 4 5)))
    10. (require (distinct? (list Kitty Betty Ethel Joan Mary)))
    11. (require (either (= Kitty 2) (= Betty 3)))
    12. (require (either (= Ethel 1) (= Joan 2)))
    13. (require (either (= Joan 3) (= Ethel 5)))
    14. (require (either (= Kitty 2) (= Mary 4)))
    15. (require (either (= Mary 4) (= Betty 1)))
    16. (list (list 'Betty Betty)
    17. (list 'Ethel Ethel)
    18. (list 'Joan Joan)
    19. (list 'Kitty Kitty)
    20. (list 'Mary Mary))))
    1. ;;; Amb-Eval input:
    2. (solve-Liars)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. ((betty 3) (ethel 5) (joan 2) (kitty 1) (mary 4))
    6. ;;; Amb-Eval input:
    7. try-again
    8. ;;; There are no more values of
    9. (solve-liars)

    To support or and and, using which we defined either, we need to modify
    our analyzer:

    1. *** in analyze
    2. ((and? exp) (analyze (and->transformed exp)))
    3. ((or? exp) (analyze (or->transformed exp)))

    For the implementation of those transformation please visit my other project.

  • Exercise 4.43

    As our amb evaluator does not support the multi-directional computation, we
    need to capture the relationship in one direction. After some experiment, I’ve
    come to realize that let the fathers has a daughter and a yacht would make
    our task concise.

    Here is (inefficient but) concise problem definition:

    1. (define (solve-daughter-and-yacht)
    2. ;; underlying relation ADT
    3. (define yacht cdr)
    4. (define daughter car)
    5. (define has-daughter-and-yacht cons)
    6. (define (has-daughter? father daugh)
    7. (eq? (daughter father) daugh))
    8. (define (has-yacht? owner yac)
    9. (eq? (yacht owner) yac))
    10. (define (assign-daughter-and-yachts daughters yachts)
    11. (has-daughter-and-yacht (an-element-of daughters)
    12. (an-element-of yachts)))
    13. (let ((daughters '(Mary Lorna Rosalind Gabrielle Melissa)))
    14. (let ((yachts daughters))
    15. (let ((moore (assign-daughter-and-yachts daughters yachts))
    16. (hall (assign-daughter-and-yachts daughters yachts))
    17. (barnacle (assign-daughter-and-yachts daughters yachts))
    18. (parker (assign-daughter-and-yachts daughters yachts))
    19. (colonel (assign-daughter-and-yachts daughters yachts)))
    20. (require (has-daughter? moore 'Mary))
    21. (require (has-yacht? moore 'Lorna))
    22. (require (has-yacht? hall 'Rosalind))
    23. (require (has-yacht? barnacle 'Gabrielle))
    24. (require (has-daughter? barnacle 'Melissa))
    25. (require (has-yacht? colonel 'Melissa))
    26. (let ((Gabrielle-father (amb moore hall barnacle parker colonel)))
    27. (require (has-daughter? Gabrielle-father 'Gabrielle)
    28. (has-yacht? Gabrielle-father (daughter parker))))
    29. (list (list 'Moore moore)
    30. (list 'Hall hall)
    31. (list 'Barnacle barnacle)
    32. (list 'Parker parker)
    33. (list 'Colonel colonel))))))

    Yet, this won’t work; more precisely, it is hard to wait until it spit the answer.

    So let’s optimize as we did before:

    1. (define (solve-daughter-and-yacht)
    2. ;; underlying relation ADT
    3. (define yacht cdr)
    4. (define daughter car)
    5. (define has-daughter-and-yacht cons)
    6. (define (has-daughter? father daugh)
    7. (eq? (daughter father) daugh))
    8. (define (has-yacht? owner yac)
    9. (eq? (yacht owner) yac))
    10. (define (assign-daughter-and-yachts daughters yachts)
    11. (has-daughter-and-yacht (an-element-of daughters)
    12. (an-element-of yachts)))
    13. (let ((daughters '(Mary Lorna Rosalind Gabrielle Melissa)))
    14. (let ((yachts daughters))
    15. (let ((moore (assign-daughter-and-yachts daughters yachts)))
    16. (require (has-daughter? moore 'Mary))
    17. (require (has-yacht? moore 'Lorna))
    18. (let ((barnacle (assign-daughter-and-yachts daughters yachts)))
    19. (require (has-yacht? barnacle 'Gabrielle))
    20. (require (has-daughter? barnacle 'Melissa))
    21. (let ((hall (assign-daughter-and-yachts daughters yachts)))
    22. (require (has-yacht? hall 'Rosalind))
    23. (let ((colonel (assign-daughter-and-yachts daughters yachts)))
    24. (require (has-yacht? colonel 'Melissa))
    25. (let ((parker (assign-daughter-and-yachts daughters yachts)))
    26. (let ((fathers (list moore barnacle hall colonel parker)))
    27. (require (distinct? (map daughter fathers)))
    28. (require (distinct? (map yacht fathers)))
    29. (let ((Gabrielle-father (an-element-of fathers)))
    30. (require (has-daughter? Gabrielle-father 'Gabrielle))
    31. (require (has-yacht? Gabrielle-father (daughter parker))))
    32. (list (list 'Moore moore)
    33. (list 'Hall hall)
    34. (list 'Barnacle barnacle)
    35. (list 'Parker parker)
    36. (list 'Colonel colonel)))))))))))

    Then now it response.

    1. ;;; Amb-Eval input:
    2. (solve-daughter-and-yacht)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. ((moore (mary . lorna)) (hall (gabrielle . rosalind)) (barnacle (melissa . gabrielle)) (parker (rosalind . mary)) (colonel (lorna . melissa)))
    6. ;;; Amb-Eval input:
    7. try-again
    8. ;;; There are no more values of
    9. (solve-daughter-and-yacht)

    So Lorna’s father is Colonel Downing.

  • Exercise 4.44

    Here is the straight forward transformation:

    1. (define (solve-queens board-size)
    2. (define empty-board '())
    3. (define (adjoin-position new-row rest-of-queens)
    4. (cons new-row rest-of-queens))
    5. (define (safe? positions)
    6. (define (not-equal-to? nr rest)
    7. (or (null? rest)
    8. (and (not (= nr (car rest)))
    9. (not-equal-to? nr (cdr rest)))))
    10. (define (pm-i-not-equal-to? nr i rest)
    11. (or (null? rest)
    12. (and (not (or (= (+ nr i) (car rest))
    13. (= (- nr i) (car rest))))
    14. (pm-i-not-equal-to? nr (1+ i) (cdr rest)))))
    15. (let ((new-row (car positions))
    16. (rest-queens (cdr positions)))
    17. (and (not-equal-to? new-row rest-queens) ;provided that positions not empty
    18. (pm-i-not-equal-to? new-row 1 rest-queens))))
    19. (define (queens-cols k)
    20. (if (= k 0)
    21. empty-board
    22. (let ((positions
    23. (adjoin-position
    24. (an-integer-between 1 board-size)
    25. (queens-cols (- k 1)))))
    26. (require (safe? positions))
    27. positions)))
    28. (queens-cols board-size))

    It works

    1. ;;; Amb-Eval input:
    2. (solve-queens 6)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. (2 4 6 1 3 5)

    Unfortunately, this algorithm in nondeterministic programming is way too slow.
    It is due to the order of recursive calling and branching:

    1. (define (solve-queens board-size)
    2. ...
    3. (define (queens-cols k)
    4. (if (= k 0)
    5. empty-board
    6. (let ((rest-queens (queens-cols (- k 1)))) ;the only change
    7. (let ((positions (adjoin-position
    8. (an-integer-between 1 board-size)
    9. rest-queens)))
    10. (require (safe? positions))
    11. positions))))
    12. (queens-cols board-size))

    By forcing the recursion before branching – an-integer-between – our program
    got way more fast:

    1. ;;; Amb-Eval input:
    2. (solve-queens 10)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. (7 4 2 9 5 10 8 6 3 1)
    6. ;;; Amb-Eval input:
    7. try-again
    8. ;;; Amb-Eval value:
    9. (8 5 2 4 10 7 9 6 3 1)

    Conclusion: Computation order impact hugely on efficiency!

  • Exercise 4.45

    Input and output:

    1. (parse '(The professor lectures to the student in the class with the cat))
    2. ;; output
    3. (sentence
    4. (simple-noun-phrase (article the) (noun professor))
    5. (verb-phrase
    6. (verb-phrase (verb-phrase (verb lectures) (prep-phrase (prep to) (simple-noun-phrase (article the) (noun student)))) (prep-phrase (prep in) (simple-noun-phrase (article the) (noun class))))
    7. (prep-phrase (prep with) (simple-noun-phrase (article the) (noun cat)))))
    8. (sentence
    9. (simple-noun-phrase (article the) (noun professor))
    10. (verb-phrase (verb-phrase (verb lectures) (prep-phrase (prep to) (simple-noun-phrase (article the) (noun student))))
    11. (prep-phrase (prep in) (noun-phrase (simple-noun-phrase (article the) (noun class)) (prep-phrase (prep with) (simple-noun-phrase (article the) (noun cat)))))))
    12. (sentence
    13. (simple-noun-phrase (article the) (noun professor))
    14. (verb-phrase
    15. (verb-phrase (verb lectures) (prep-phrase (prep to) (noun-phrase (simple-noun-phrase (article the) (noun student)) (prep-phrase (prep in) (simple-noun-phrase (article the) (noun class))))))
    16. (prep-phrase (prep with) (simple-noun-phrase (article the) (noun cat)))))
    17. (sentence
    18. (simple-noun-phrase (article the) (noun professor))
    19. (verb-phrase
    20. (verb lectures)
    21. (prep-phrase
    22. (prep to)
    23. (noun-phrase (noun-phrase (simple-noun-phrase (article the) (noun student)) (prep-phrase (prep in) (simple-noun-phrase (article the) (noun class))))
    24. (prep-phrase (prep with) (simple-noun-phrase (article the) (noun cat)))))))
    25. (sentence
    26. (simple-noun-phrase (article the) (noun professor))
    27. (verb-phrase
    28. (verb lectures)
    29. (prep-phrase
    30. (prep to)
    31. (noun-phrase (simple-noun-phrase (article the) (noun student))
    32. (prep-phrase (prep in) (noun-phrase (simple-noun-phrase (article the) (noun class)) (prep-phrase (prep with) (simple-noun-phrase (article the) (noun cat)))))))))

    Interpretation:

    1. First output means “the professor is with the cat” and “lectures in the class”
      to student.
    2. Second means the professor is in “the class with the cat” and lectures to student.
    3. Third means “the professor is with the cat” and lectures to “student in the class.”
    4. Forth means the professor lectures to “student in the class” with the cat.
    5. Last means the professor lectures to student in “the class with the cat.”
  • Exercise 4.46

    Note that parse-prepositional-phrase and parse-noun-phrase procedure is
    mutually recursive. If the evaluator evaluate in different order than now,
    evaluating parse-prepositional-phrase leads to parse-noun-phrase without
    consuming preposition and which in turn consume simple-noun-phrase and then
    execute parse-prepositional-phrase. Which is not the one we wanted.

  • Exercise 4.47

    This version did not store the checkered point; so as long as there is answer it
    can consume it will work as expected. Otherwise, it falls into the infinite
    loop. You can understand this situation easily by drawing the tree structure it
    branches.

    If we interchange the expression order, it even won’t work if we try
    (parse '(The professor)). It just run forever without spitting anything.

  • Exercise 4.48

    Here we try to include the clause; now we can parse compound sentence. Here is
    the design with the code:

    1. (define subordinates '(subord when if))
    2. ;; Subordinate-clause := Subordinate + Sentence
    3. (define (parse-subordinate-clause)
    4. (list 'subordinate-clause
    5. (parse-word subordinates)
    6. (parse-sentence)))
    7. ;; Setence := Simple-sentence | Sentence + Subordinate-clause
    8. (define (parse-sentence)
    9. (define (maybe-extend sentence)
    10. (amb sentence
    11. (maybe-extend (list 'compound-sentence
    12. sentence
    13. (parse-subordinate-clause)))))
    14. (maybe-extend (parse-simple-sentence)))

    Then test:

    1. ;;; Amb-Eval input:
    2. (parse '(the professor lectures to the student in the class when the cat eats))
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. (compound-sentence
    6. (simple-sentence
    7. (simple-noun-phrase (article the) (noun professor))
    8. (verb-phrase (verb-phrase (verb lectures) (prep-phrase (prep to) (simple-noun-phrase (article the) (noun student)))) (prep-phrase (prep in) (simple-noun-phrase (article the) (noun class)))))
    9. (subordinate-clause (subord when) (simple-sentence (simple-noun-phrase (article the) (noun cat)) (verb eats))))
  • Exercise 4.49

    To support generate we make following changes:

    1. (define (parse-word word-list)
    2. ;; (require (not (null? *unparsed*)))
    3. ;; (require (memq (car *unparsed*) (cdr word-list)))
    4. (let ((found-word (an-element-of (cdr word-list))))
    5. ;; (set! *unparsed* (cdr *unparsed*))
    6. (list (car word-list) found-word)))
    7. (define (generate)
    8. (parse '()))

    Then test:

    1. ;;; Amb-Eval input:
    2. (generate)
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. (simple-sentence (simple-noun-phrase (article the) (noun student)) (verb studies))
    6. ;;; Amb-Eval input:
    7. try-again
    8. ;;; Amb-Eval value:
    9. (compound-sentence (simple-sentence (simple-noun-phrase (article the) (noun student)) (verb studies))
    10. (subordinate-clause (subord when) (simple-sentence (simple-noun-phrase (article the) (noun student)) (verb studies))))
    11. ;;; Amb-Eval input:
    12. try-again
    13. ;;; Amb-Eval value:
    14. (compound-sentence
    15. (compound-sentence (simple-sentence (simple-noun-phrase (article the) (noun student)) (verb studies))
    16. (subordinate-clause (subord when) (simple-sentence (simple-noun-phrase (article the) (noun student)) (verb studies))))
    17. (subordinate-clause (subord when) (simple-sentence (simple-noun-phrase (article the) (noun student)) (verb studies))))

    As implied, it is not interesting; just repeat the sentence “the student
    studies” using the when subordinate adjunct.

Implementing the Amb Evaluator

  • Exercise 4.50

    Here is the implementation. Design should be done in prior, in my case I’ve done
    in my digital paper. And here is the resulting code:

    1. ;; ramb syntax procedure
    2. (define (ramb? exp) (tagged-list? exp 'ramb))
    3. (define ramb-choices amb-choices)
    1. *** in analyze
    2. ((ramb? exp) (analyze-ramb exp))
    1. (define (analyze-ramb exp)
    2. (let ((cprocs (map analyze (amb-choices exp))))
    3. (lambda (env succeed fail)
    4. (define (try-rand choices)
    5. (let ((chosen (pick-random choices)))
    6. (if chosen
    7. (chosen
    8. env succeed
    9. (lambda ()
    10. (try-rand (filter (lambda (choice) (not (eq? choice chosen)))
    11. choices))))
    12. (fail))))
    13. (try-rand cprocs))))

    Then here is modification:

    1. (define (parse-word word-list)
    2. ;; (require (not (null? *unparsed*)))
    3. ;; (require (memq (car *unparsed*) (cdr word-list)))
    4. (let ((found-word (choose-randomly (cdr word-list))))
    5. ;; (set! *unparsed* (cdr *unparsed*))
    6. (list (car word-list) found-word)))
    7. (define (choose-randomly lst)
    8. (require (not (null? lst)))
    9. (ramb (car lst) (choose-randomly (cdr lst))))

    Then the result from generate:

    ```scheme
    ;;; Amb-Eval input:
    (generate)

    ;;; Starting a new problem
    ;;; Amb-Eval value:
    (simple-sentence (simple-noun-phrase (article a) (noun professor)) (verb studies))

    ;;; Amb-Eval input:
    try-again

    ;;; Amb-Eval value:
    (compound-sentence (simple-sentence (simple-noun-phrase (article a) (noun professor)) (verb studies))

    1. (subordinate-clause (subord if) (simple-sentence (simple-noun-phrase (article the) (noun cat)) (verb studies))))

    ;;; Amb-Eval input:
    try-again

    ;;; Amb-Eval value:
    (compound-sentence (compound-sentence (simple-sentence (simple-noun-phrase (article a) (noun professor)) (verb studies))

    1. (subordinate-clause (subord if) (simple-sentence (simple-noun-phrase (article the) (noun cat)) (verb studies))))
    2. (subordinate-clause (subord when) (simple-sentence (simple-noun-phrase (article the) (noun professor)) (verb studies))))

    ;;; Amb-Eval input:
    (generate)

    ;;; Starting a new problem
    ;;; Amb-Eval value:
    (simple-sentence (simple-noun-phrase (article the) (noun professor)) (verb sleeps))

    ;;; Amb-Eval input:
    try-again

    ;;; Amb-Eval value:
    (compound-sentence (simple-sentence (simple-noun-phrase (article the) (noun professor)) (verb sleeps))

    1. (subordinate-clause (subord when) (simple-sentence (simple-noun-phrase (article a) (noun cat)) (verb eats))))
  1. ```
  2. Quite interesting!
  • Exercise 4.51

    To make assignment permanently, we just remove the undo steps in
    analyze-assignment:

    1. (define (analyze-permanent-assignment exp)
    2. (let ((var (assignment-variable exp))
    3. (vproc (analyze (assignment-value exp))))
    4. (lambda (env succeed fail)
    5. (vproc env
    6. (lambda (val fail2)
    7. (set-variable-value! var val env)
    8. (succeed 'ok fail2))
    9. fail))))

    Then detector (we just reuse the selectors from assignment):

    1. (define (permanent-assignment? exp)
    2. (tagged-list? exp 'permanent-set!))

    Then test!

    1. ;;; Amb-Eval input:
    2. (define count 0)
    3. (let ((x (an-element-of '(a b c)))
    4. (y (an-element-of '(a b c))))
    5. (permanent-set! count (+ count 1))
    6. (require (not (eq? x y)))
    7. (list x y count))
    8. ;;; Starting a new problem
    9. ;;; Amb-Eval value:
    10. ok
    11. ;;; Amb-Eval input:
    12. ;;; Starting a new problem
    13. ;;; Amb-Eval value:
    14. (a b 2)
    15. ;;; Amb-Eval input:
    16. try-again
    17. ;;; Amb-Eval value:
    18. (a c 3)

    If we used set! instead permanent-set!, then the count always be 1 since
    it set! after branching so the undoing step is done in prior of backtracking.

  • Exercise 4.52

    You should design that is analogous to if:

    1. (define (analyze-if-fail exp)
    2. (let ((try (analyze (if-fail-try exp)))
    3. (failed (analyze (if-fail-failed exp))))
    4. (lambda (env succeed fail)
    5. (try env succeed
    6. (lambda () (failed env succeed fail))))))
    7. (define (if-fail? exp) (tagged-list? exp 'if-fail))
    8. (define (if-fail-try exp) (cadr exp))
    9. (define (if-fail-failed exp) (caddr exp))

    Then test:

    1. ;;; Amb-Eval input:
    2. (if-fail (let ((x (an-element-of '(1 3 5))))
    3. (require (even? x))
    4. x)
    5. 'all-odd)
    6. ;;; Starting a new problem
    7. ;;; Amb-Eval value:
    8. all-odd
    9. ;;; Amb-Eval input:
    10. (if-fail (let ((x (an-element-of '(1 3 5 8))))
    11. (require (even? x))
    12. x)
    13. 'all-odd)
    14. ;;; Starting a new problem
    15. ;;; Amb-Eval value:
    16. 8
  • Exercise 4.53

    The result would be the list of all the possible prime-sum-pair s! You can
    reason this about by keeping track of the time tree it makes or it would be more
    convicing that you see the result in your eyes:

    1. ;;; Amb-Eval input:
    2. (let ((pairs '()))
    3. (if-fail (let ((p (prime-sum-pair '(1 3 5 8) '(20 35 110))))
    4. (permanent-set! pairs (cons p pairs))
    5. (amb))
    6. pairs))
    7. ;;; Starting a new problem
    8. ;;; Amb-Eval value:
    9. ((8 35) (3 110) (3 20))
  • Exercise 4.54

    It is easy:

    1. (define (require? exp) (tagged-list? exp 'require))
    2. (define (require-predicate exp) (cadr exp))
    3. (define (analyze-require exp)
    4. (let ((pproc (analyze (require-predicate exp))))
    5. (lambda (env succeed fail)
    6. (pproc env
    7. (lambda (pred-value fail2)
    8. (if (false? pred-value)
    9. (fail2)
    10. (succeed 'ok fail2)))
    11. fail))))

Logic Programming

Deductive Information Retrieval

  • Exercise 4.55

    • a.

      1. ;;; Query input:
      2. (supervisor ?x (Bitdiddle Ben))
      3. ;;; Query results:
      4. (supervisor (tweakit lem e) (bitdiddle ben))
      5. (supervisor (fect cy d) (bitdiddle ben))
      6. (supervisor (hacker alyssa p) (bitdiddle ben))
    • b.

      1. ;;; Query input:
      2. (job ?x (accounting . ?y))
      3. ;;; Query results:
      4. (job (cratchet robert) (accounting scrivener))
      5. (job (scrooge eben) (accounting chief accountant))
    • c.

      1. ;;; Query input:
      2. (address ?x (Slumerville . ?rest))
      3. ;;; Query results:
      4. (address (aull dewitt) (slumerville (onion square) 5))
      5. (address (reasoner louis) (slumerville (pine tree road) 80))
      6. (address (bitdiddle ben) (slumerville (ridge road) 10))
  • Exercise 4.56

    • a.

      1. ;;; Query input:
      2. (and (supervisor ?person (Bitdiddle Ben))
      3. (address ?person ?where))
      4. ;;; Query results:
      5. (and (supervisor (tweakit lem e) (bitdiddle ben))
      6. (address (tweakit lem e) (boston (bay state road) 22)))
      7. (and (supervisor (fect cy d) (bitdiddle ben))
      8. (address (fect cy d) (cambridge (ames street) 3)))
      9. (and (supervisor (hacker alyssa p) (bitdiddle ben))
      10. (address (hacker alyssa p) (cambridge (mass ave) 78)))
    • b.

      1. ;;; Query input:
      2. (and (salary (Bitdiddle Ben) ?Ben-amount)
      3. (salary ?person ?amount)
      4. (lisp-value < ?amount ?Ben-amount))
      5. ;;; Query results:
      6. (and (salary (bitdiddle ben) 60000)
      7. (salary (aull dewitt) 25000)
      8. (lisp-value < 25000 60000))
      9. (and (salary (bitdiddle ben) 60000)
      10. (salary (cratchet robert) 18000)
      11. (lisp-value < 18000 60000))
      12. (and (salary (bitdiddle ben) 60000)
      13. (salary (reasoner louis) 30000)
      14. (lisp-value < 30000 60000))
      15. (and (salary (bitdiddle ben) 60000)
      16. (salary (tweakit lem e) 25000)
      17. (lisp-value < 25000 60000))
      18. (and (salary (bitdiddle ben) 60000)
      19. (salary (fect cy d) 35000)
      20. (lisp-value < 35000 60000))
      21. (and (salary (bitdiddle ben) 60000)
      22. (salary (hacker alyssa p) 40000)
      23. (lisp-value < 40000 60000))
    • c.

      1. ;;; Query input:
      2. (and (supervisor ?person ?someone)
      3. (not (job ?someone (computer . ?any)))
      4. (job ?someone ?his-job))
      5. ;;; Query results:
      6. (and (supervisor (aull dewitt) (warbucks oliver))
      7. (not (job (warbucks oliver) (computer . ?any)))
      8. (job (warbucks oliver) (administration big wheel)))
      9. (and (supervisor (cratchet robert) (scrooge eben))
      10. (not (job (scrooge eben) (computer . ?any)))
      11. (job (scrooge eben) (accounting chief accountant)))
      12. (and (supervisor (scrooge eben) (warbucks oliver))
      13. (not (job (warbucks oliver) (computer . ?any)))
      14. (job (warbucks oliver) (administration big wheel)))
      15. (and (supervisor (bitdiddle ben) (warbucks oliver))
      16. (not (job (warbucks oliver) (computer . ?any)))
      17. (job (warbucks oliver) (administration big wheel)))
  • Exercise 4.57

    1. (rule (can-be-replaced-by ?p1 ?p2)
    2. (and (job ?p1 ?job1) (job ?p2 ?job2)
    3. (or (same ?job1 ?job2)
    4. (can-do-job ?job2 ?job1))
    5. (not (same ?p1 ?p2))))
    • a.

      1. ;;; Query input:
      2. (can-be-replaced-by (Fect Cy D) ?whom)
      3. ;;; Query results:
      4. (can-be-replaced-by (fect cy d) (bitdiddle ben))
      5. (can-be-replaced-by (fect cy d) (hacker alyssa p))
    • b.

      1. ;;; Query input:
      2. (and (can-be-replaced-by ?p1 ?p2)
      3. (salary ?p1 ?a1) (salary ?p2 ?a2)
      4. (lisp-value > ?a1 ?a2))
      5. ;;; Query results:
      6. (and (can-be-replaced-by (warbucks oliver) (aull dewitt)) (salary (warbucks oliver) 150000) (salary (aull dewitt) 25000) (lisp-value > 150000 25000))
      7. (and (can-be-replaced-by (hacker alyssa p) (fect cy d)) (salary (hacker alyssa p) 40000) (salary (fect cy d) 35000) (lisp-value > 40000 35000))
  • Exercise 4.58

    Here is the definition:

    1. (rule (big-shot ?p)
    2. (and (job ?p (?div . ?rest))
    3. (not (and (supervisor ?p ?boss) ;not have a boss who work in the same division
    4. (job ?boss (?div2 . ?rest2))
    5. (same ?div ?div2)))))

    Then test:

    1. ;;; Query input:
    2. (big-shot ?who)
    3. ;;; Query results:
    4. (big-shot (scrooge eben))
    5. (big-shot (warbucks oliver))
    6. (big-shot (bitdiddle ben))
  • Exercise 4.59

    • a.

      1. ;;; Query input:
      2. (meeting ?division (Friday ?time))
      3. ;;; Query results:
      4. (meeting administration (friday |1pm|))
    • b.

      Here is the resulting code:

      1. (rule (meeting-time ?person ?day-and-time)
      2. (or (meeting whole-company ?day-and-time)
      3. (and (job ?person (?division . ?subtitle))
      4. (meeting ?division ?day-and-time))))
    • c.

      1. ;;; Query input:
      2. (meeting-time (Hacker Alyssa P) (Wednesday ?time))
      3. ;;; Query results:
      4. (meeting-time (hacker alyssa p) (wednesday |4pm|))
      5. (meeting-time (hacker alyssa p) (wednesday |3pm|))
  • Exercise 4.60

    Since the rule is symmetry: If (lives-near p1 p2) then also (lives-near p2 p1). To make the requested happen, we need query with non-symmetric relation
    by imposing order in persons. The most canonical way of doing this is
    lexicographical ordering:

    1. (define (name<? s-lst1 s-lst2)
    2. (symbol<? (fold-right symbol-append '|| s-lst1)
    3. (fold-right symbol-append '|| s-lst2)))

    Then we can query as

    1. ;;; Query input:
    2. (and (lives-near ?person-1 ?person-2) (lisp-value name<? ?person-1 ?person-2))
    3. ;;; Query results:
    4. (and (lives-near (aull dewitt) (reasoner louis)) (lisp-value name<? (aull dewitt) (reasoner louis)))
    5. (and (lives-near (aull dewitt) (bitdiddle ben)) (lisp-value name<? (aull dewitt) (bitdiddle ben)))
    6. (and (lives-near (fect cy d) (hacker alyssa p)) (lisp-value name<? (fect cy d) (hacker alyssa p)))
    7. (and (lives-near (bitdiddle ben) (reasoner louis)) (lisp-value name<? (bitdiddle ben) (reasoner louis)))
  • Exercise 4.61

    1. (?x next-to ?y in (1 (2 3) 4))

    would results in

    1. ?x = 1, ?y = (2 3) ;by applying base case
    2. ?x = (2 3), ?y = 4 ;by applying recursive case and then base

    And the

    1. (?x next-to 1 in (2 1 3 1))

    would spit out

    1. ?x = 2 ;by applying base case
    2. ?x = 3 ;by applying recursive case twice and then base

    And verification:

    1. ;;; Query input:
    2. (?x next-to ?y in (1 (2 3) 4))
    3. ;;; Query results:
    4. (1 next-to (2 3) in (1 (2 3) 4))
    5. ((2 3) next-to 4 in (1 (2 3) 4))
    6. ;;; Query input:
    7. (?x next-to 1 in (2 1 3 1))
    8. ;;; Query results:
    9. (2 next-to 1 in (2 1 3 1))
    10. (3 next-to 1 in (2 1 3 1))

    This exercise shows we are not limited to the prefix notation, which is used in
    underlying Scheme evaluator, in query language.

  • Exercise 4.62

    Here is the straight forward transformation:

    1. (rule (last-pair (?x) (?x))) ;base case
    2. (rule (last-pair (?head . ?tail) ?x) ;recursive case
    3. (last-pair ?tail ?x))

    Here you should be careful in assert! order: You should assert! recursive
    rule first and then base rule. It might sound counterintuitive; it is due to the
    implementation details of data base where the assertions and rules stored.

    Then test!

    1. ;;; Query input:
    2. (last-pair (3) ?x)
    3. ;;; Query results:
    4. (last-pair (3) (3))
    5. ;;; Query input:
    6. (last-pair (2 ?x) (3))
    7. ;;; Query results:
    8. (last-pair (2 3) (3))

    Then the last unfolding case:

    1. ;;; Query input:
    2. (last-pair ?x (3))
    3. ;;; Query results:
    4. (last-pair (3) (3))
    5. (last-pair (?head-181 3) (3))
    6. (last-pair (?head-181 ?head-185 3) (3))
    7. (last-pair (?head-181 ?head-185 ?head-189 3) (3))
    8. (last-pair (?head-181 ?head-185 ?head-189 ?head-193 3) (3))
    9. (last-pair (?head-181 ?head-185 ?head-189 ?head-193 ?head-197 3) (3))
    10. (last-pair (?head-181 ?head-185 ?head-189 ?head-193 ?head-197 ?head-201 3) (3))
    11. (last-pair (?head-181 ?head-185 ?head-189 ?head-193 ?head-197 ?head-201 ?head-205 3) (3))
    12. ...

    It is reasonable since the revert is right!

    1. ;;; Query input:
    2. (last-pair (?head-181 ?head-185 ?head-189 ?head-193 ?head-197 3) (3))
    3. ;;; Query results:
    4. (last-pair (?head-304 ?head-306 ?head-308 ?head-310 ?head-312 3) (3))

    And as ?head-... is free variable the result assert (infinite number of) facts
    that for any assignment to ?head-... the given statements are satisfied.

    And also note that the number appended to the variable name like 181 in
    ?head-181 is due to the implementation detail.

  • Exercise 4.63

    1. (rule (grandson ?g ?s)
    2. (and (son ?f ?s) (son ?g ?f)))
    3. (rule (son ?m ?s)
    4. (and (wife ?m ?w) (son ?w ?s)))

    Then test!

    1. ;;; Query input:
    2. (grandson Cain ?who)
    3. ;;; Query results:
    4. (grandson cain irad)
    5. ;;; Query input:
    6. (son Lamech ?son)
    7. ;;; Query results:
    8. (son lamech jubal)
    9. (son lamech jabal)
    10. ;;; Query input:
    11. (grandson Methushael ?who)
    12. ;;; Query results:
    13. (grandson methushael jubal)
    14. (grandson methushael jabal)

How the Query System Works

Is Logic Programming Mathematical Logic?

  • Exercise 4.64

    By the high level specification of this logic language, we can trace the
    query (outranked-by (Bitdiddle Ben) ?who) as follows:

    1. ?staff-person is bound to (Bitdiddle Ben) and ?who is bound to ?boss
      (since after this point by using the body of rule they are going to bind
      ?boss; so we need to bind ?who here to propagate the subsequent bindings).
    2. Depending on the implementation details, whether it spits out the first
      output of (supervisor ?staff-person ?boss) or (and (outranked-by ?middle-manager) (supervisor ?staff-person ?middle-manager))
    3. Here let we assume the case or consume the latter one first. Then by the
      specification of and it first query (outranked-by ?middle-manager ?boss)
      with the input frame, which is extended from 1. but has no bindings for
      ?middle-manager and ?boss.
    4. By the same arguments from 1. to 3. it infinitely process (outranked-by ?middle-manager ?boss) without bindings for those.
    5. Now let we consider other way around: or first process the former one. Then
      it now extend frame with binding for ?boss; so it spits out possible
      answers but to find more it should consume the latter one. It follows to the
      reasoning we did in the above case but spitting out each answer it encounters
      , after that, falling into infinite loops.

    Let’s experiment with our evaluator to verify what path our evaluator proceed:

    1. ;;; Query input:
    2. (outranked-by (Bitdiddle Ben) ?who)
    3. ;;; Query results:
    4. (outranked-by (bitdiddle ben) (warbucks oliver))
    5. (outranked-by (bitdiddle ben) (warbucks oliver)) C-c C-c
    6. ;Quit!

    Yes the rather one is what our evaluator chose.

  • Exercise 4.65

    Since there is four different frames satisfying Oliver to be wheel:

    1. ?middle-manager ?x
    2. Bitdiddle Ben Hacker Alyssa P
    3. Bitdiddle Ben Fect Cy D
    4. Bitdiddle Ben Tweakit Lem E
    5. Scrooge Eben Cartchet Robert
  • Exercise 4.66

    Think about the following request:

    1. (sum ?amount
    2. (and (wheel? ?x)
    3. (salary ?x ?amount)))

    to find out the total salaries of all the wheels in the Microshaft company. But
    as we seen before Oliver counted forth times. What we wanted! We wanted to count
    once for each wheel without duplication.

    To deal with this malfunction, he need to remove the duplicate frames from the
    output stream of query. Here we need to define what is duplicate frames
    rigorously:

    1. Extract the pattern variables used in <query pattern> from each output frames;
    2. Take the first frame in the output frame stream and then filtering the rest
      of output stream based on the values bound to extracted variables, that is,
      to remove frames that has the same set of values with the first frame from
      the rest ones.
  • Exercise 4.67

    This situation is similar with the designing the detector of cycle in the
    section 3 in the text; but got more complicated.

    The key point is that in what situation we know that we came to the “same” query
    that processed before in this query chain. If the variable names are same as
    before in frame should be that case? Certainly not since rule application is
    recursive in their nature as we have seen in previous exercises
    (append-to-form or last-pair and so on).

    And it is also important to recognize in what situation we possibly falls into
    infinite loop? We can reason about this question by thinking the cycle case or
    all the other programs that results in infinite loops: The means of abstraction
    allows us to program which can leads to infinite loop. So the rule application
    is the main region we should consider.

    Combining the preceding two paragraph, we can conclude that if we trace what
    rule application we have used so far in this query chain with the values of the
    variables in rule conclusion with which the body of that rule application
    executed, we can detect whether we are going to loop infinitely:

    • If we the same rule application with the same values – same values is means
      either the values of two variables is bound to the equal value (for the case
      the value also contains variables, it recursively walk the tree structure to
      find out whether the two variables bound to same value) or both variables are
      unbound – it should signal that we are now in the loop.
  • Exercise 4.68

    To make the rule work in both way, we need to the recursive rule conclusion to
    be decomposed in both argument. Here we try to accomplish this restriction using
    append-to-form since it decompose the input arguments in multi-directional
    way:

    1. (rule (reverse () ())) ;base case
    2. (rule (reverse (?x . ?xs) ?y) ;recursive case
    3. (and (append-to-form ?rs (?x) ?y)
    4. (reverse ?xs ?rs)))

    But this works in unidirectional way:

    1. ;;; Query input:
    2. (reverse ?x (1 2 3))
    3. ;;; Query results:
    4. (reverse (3 2 1) (1 2 3))
    5. ;;; Query input:
    6. (reverse (1 2 3) ?x)
    7. ;;; Query results:
    8. (reverse (1 2 3) (3 2 1)) C-c C-c
    9. ;Quit!

    To resolve this situation, we need to think it falls into the infinite loop
    after spitting out the answer.

    After some experimenting or reasoning we can reach to think it is due to
    unfortunate of append-to-form:

    1. ;;; Query input:
    2. (append-to-form ?x (1) ?y)
    3. ;;; Query results:
    4. (append-to-form () (1) (1))
    5. (append-to-form (?u-397) (1) (?u-397 1))
    6. (append-to-form (?u-397 ?u-399) (1) (?u-397 ?u-399 1))
    7. (append-to-form (?u-397 ?u-399 ?u-401) (1) (?u-397 ?u-399 ?u-401 1))
    8. (append-to-form (?u-397 ?u-399 ?u-401 ?u-403) (1) (?u-397 ?u-399 ?u-401 ?u-403 1))

    This stream of frames passed to (reverse ?xs ?rs) when we try to execute
    (reverse (1 2 3) ?x); no limit in this unfolding. So if we can limit this
    infinite unfolding, then we got what we wanted!

    Here is the deal:

    1. (assert! (rule (same-length () ())))
    2. (assert! (rule (same-length (?x . ?xs) (?y . ?ys))
    3. (same-length ?xs ?ys)))

    Then restrict our unfolding using above rule!

    1. (assert! (rule (reverse () ())))
    2. (assert! (rule (reverse (?x . ?xs) ?y)
    3. (and (same-length (?x . ?xs) ?y)
    4. (append-to-form ?rs (?x) ?y)
    5. (reverse ?xs ?rs))))

    Now try our new reverse

    1. ;;; Query input:
    2. (reverse (1 2 3) ?x)
    3. ;;; Query results:
    4. (reverse (1 2 3) (3 2 1))
    5. ;;; Query input:
    6. (reverse ?x (1 2 3))
    7. ;;; Query results:
    8. (reverse (3 2 1) (1 2 3))

    Now it works in multi-direction.

    The lesson from this exercise: We can restrict the pattern variable like we did
    in the nondeterministic programming; but in more confined way. We can limit the
    variable via confining the relations but not in a direct way such as if or like
    that control stuff.

  • Exercise 4.69

    Here is the first task: Write rule that determine if a list ends in the word
    grandson

    1. (rule (end-with-grandson ?rel)
    2. (last-pair ?rel (grandson)))

    Then the main tasks:

    1. (rule ((grandson) ?x ?y)
    2. (grandson ?x ?y))
    3. (rule ((great . ?rel) ?x ?y)
    4. (and (son ?x ?g)
    5. (?rel ?g ?y)
    6. (end-with-grandson ?rel)))

    The first clause of above rules is needed since our recursive rule definition
    only reduced to (grandson). So we need to connect from (grandson) to
    grandson.

    Then test:

    1. ;;; Query input:
    2. ((great grandson) ?g ?ggs)
    3. ;;; Query results:
    4. ((great grandson) mehujael jubal)
    5. ((great grandson) irad lamech)
    6. ((great grandson) mehujael jabal)
    7. ((great grandson) enoch methushael)
    8. ((great grandson) cain mehujael)
    9. ((great grandson) adam irad)
    10. ;;; Query input:
    11. (?relationship Adam Irad)
    12. ;;; Query results:
    13. ((great grandson) adam irad)
    14. ;;; Query input:
    15. ((great great great great grandson) ?g ?gggggs)
    16. ;;; Query results:
    17. ((great great great great grandson) cain jubal)
    18. ((great great great great grandson) adam lamech)
    19. ((great great great great grandson) cain jabal)

Implementing the Query System

  • Exercise 4.70

    Note that we have combined assignment with lazy evaluation, which normally what
    we shouldn’t do if we can escape.

    Here we expect to mutate using what I get if we lookup the environment in this
    state – THE-ASSERTIONS; however since the cdr part of cons-stream delayed
    implicitly, we don’t know which “version” we actually mutate when the evaluation
    being made in computer.

    In this specific situation, we know what the result would be: The infinite
    stream of one element, newly added assertion like ones.

  • Exercise 4.71

    To answer this question we need to simulate what our evaluator does.

    For the first situation – simple-query if it uses delayed append:

    1. From the entry point (by user input in the driver loop or that of subsequent
      query) simple-query is called by data directed dispatch.
    2. The body of simple-query is evaluated:
      1. stream-flatmap
      2. lambda expression
      3. frame-stream
    3. The body of stream-flatmap is evaluated. Before stream-append‘s body
      being evaluated, find-assertions is evaluated but apply-rules since it is
      delayed.
    4. After find-assertions terminates with the returning stream of frames, the
      body of stream-append-delayed is evaluated.
    5. Only if that stream is empty stream-append-delayed force the evaluation of
      apply-rules; otherwise the body of stream-flatmap is evaluated.
    6. As the result, the body of stream-flatmap is halted with returning output stream.

    If simple-query uses stream-append instead delayed version of that:

    1. Until 2. the evaluations are same as above.
    2. Now stream-flatmap‘s body being evaluated. apply-rules being evaluated
      as well as find-assertions before the body of stream-append.
    3. By the same arguments as preceding ones, apply-rules in turn calls
      apply-a-rule that evaluate the body of given rule with the frame.
    4. In apply-rules, apply-a-rule is evaluated until find the rule that matched
      with the query or exhausted the rules to be tested with.
    5. If found one that is matched then the matched rule’s body is evaluated.
    6. Note that so far we have not processed any frame completely. We just chained
      the evaluation of argument.
    7. However rule can call recursively itself.
    8. Normally recursive rule meant to reduce the given problem into more simpler
      problem; so if the recursive rule calling ended with base case rule – the
      rule has empty body, then it halted as normally.
    9. It is possible that the base case depends on the assertions not the rule
      with empty body. In this case, also halted as normally since there is no rule
      that can be applied to this query.

    So the logic program that would halt in one version, also halt in another
    version. Although here we showed this assertion using the evaluation process in
    both evaluator, we can think this in more abstract manner: Delay or stream can
    only mutate the evaluation order not the result if we don’t mix the assignment.
    This observation should be familiar with us; in situation where substitution
    model can be applied, the evaluation order can not alter the result.

    So two different simple-query can not make any change at all for the program
    which halted; it only make difference in non-halting program: run forever of
    signaling an error. We can inspect this assertion using following examples:

    1. (assert! (rule (infinite ?x)
    2. (infinite ?x)))
    3. (assert! (infinite answer))

    First the test with original simple-query:

    1. ;;; Query input:
    2. (infinite ?x)
    3. ;;; Query results:
    4. (infinite answer)
    5. (infinite answer)
    6. (infinite answer)
    7. (infinite answer)
    8. ...
    9. (infinite answer)
    10. (infinite answer)
    11. (infinite answer) C-c C-c
    12. ;Quit!

    Then the simple-query without append delayed:

    1. ;;; Query input:
    2. (infinite ?x)
    3. ;;; Query results: C-c C-c
    4. ;Quit!

    So the difference became apparent: One finds all the possible answers yet run
    forever; other one just run forever without find any.

    Let’s do the same kind of argument to the second question. It is almost same as
    the first one – simple-query case.

    The preceding observation applies here too. So here we just show the example
    that embodies the difference.

    For that example here we use the one from ex 4.64:

    1. (outranked-by (Bitdiddle Ben) ?who)

    with the ill-defined rule definition:

    1. (rule (outranked-by ?staff-person ?boss)
    2. (or (supervisor ?staff-person ?boss)
    3. (and (outranked-by ?middle-manager ?boss)
    4. (supervisor ?staff-person ?middle-manager))))

    In previous exercise, it would spit out all the possible answers and then run
    forever. But with modified disjoin we got

    1. ;;; Query input:
    2. (outranked-by (Bitdiddle Ben) ?who)
    3. ;;; Query results: C-c C-c
    4. ;Quit!

    If we revert the modification in disjoin, we got

    1. ;;; Query input:
    2. (outranked-by (Bitdiddle Ben) ?who)
    3. ;;; Query results:
    4. (outranked-by (bitdiddle ben) (warbucks oliver))
    5. (outranked-by (bitdiddle ben) (warbucks oliver)) C-c C-c
    6. ;Quit!

    as expected (the reason we got the same answer duplicated is that we asserted
    two outranked-by – one for proper definition and one for ill-definition).

  • Exercise 4.72

    We used interleave in section 3.5.3 for dealing with pair, the stream of
    pairs from two (possibly infinite) streams; we needed that since we wanted to
    make sure that each pair element appear in the resulting stream in finite step
    of cdr ing down that stream.

    Here is also the same situation in abstract viewpoint: even if there is infinite
    stream in one of arguments, we want to the program find all the answers from the
    other arguments that has finite (or infinite) stream of frames.

    Here is the simple example describes above idea:
    If we make disjoin as

    1. (define (disjoin disjuncts frame-stream)
    2. (if (empty-disjunction? disjuncts)
    3. (amb)
    4. (stream-append-delayed
    5. (qeval (first-disjunct disjuncts) frame-stream)
    6. (delay (disjoin (rest-disjuncts disjuncts)
    7. frame-stream)))))

    Then the (stupid) query makes the evaluator fall into infinite loop without
    answering:

    1. ;;; Query input:
    2. (or (infinite ?x)
    3. (outranked-by ?who (Bitdiddle Ben)))
    4. ;;; Query results:
    5. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    6. ...
    7. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    8. (or (infinite answer) (outranked-by ?who (bitdiddle ben))) C-c C-c
    9. ;Quit!

    With original one:

    1. ;;; Query input:
    2. (or (infinite ?x)
    3. (outranked-by ?who (Bitdiddle Ben)))
    4. ;;; Query results:
    5. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    6. (or (infinite ?x) (outranked-by (tweakit lem e) (bitdiddle ben)))
    7. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    8. (or (infinite ?x) (outranked-by (reasoner louis) (bitdiddle ben)))
    9. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    10. (or (infinite ?x) (outranked-by (fect cy d) (bitdiddle ben)))
    11. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    12. (or (infinite ?x) (outranked-by (hacker alyssa p) (bitdiddle ben)))
    13. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    14. ...
    15. (or (infinite answer) (outranked-by ?who (bitdiddle ben))) C-c C-c

    For the stream-flatmap, we can apply the same observation to this. We can
    illustrate the need by showing following examples:
    First let’s setup this assertions should be added in initialized data base. The
    purpose of following definitions is to show the affection of modified
    stream-flatmap on apply-rules; that is, one of the following rule
    application can produce infinite stream of frames, others not.

    1. (assert! (rule (infinite ?x)))
    2. (assert! (rule (infinite ?x)
    3. (infinite ?x)))
    4. (assert! (infinite answer))

    Modified stream-flatmap with following definition:

    1. (define (flatten-stream stream)
    2. (if (stream-null? stream)
    3. the-empty-stream
    4. (stream-append-delayed
    5. (stream-car stream)
    6. (delay (flatten-stream (stream-cdr stream))))))

    Then run in the original version:

    1. ;;; Query input:
    2. (infinite ?x)
    3. ;;; Query results:
    4. (infinite answer) ;assertion
    5. (infinite answer) ;infinite rule application
    6. (infinite ?x-98763) ;base case rule application
    7. (infinite answer)
    8. (infinite ?x-98765)
    9. (infinite answer)
    10. (infinite ?x-98767)
    11. (infinite answer)
    12. (infinite ?x-98769)
    13. ...
    14. (infinite answer) C-c C-c

    And the modified one:

    1. ;;; Query input:
    2. (infinite ?x)
    3. ;;; Query results:
    4. (infinite answer) ;inifinite rule application with find-assertions
    5. (infinite answer)
    6. (infinite answer)
    7. ...
    8. (infinite answer)

    The original version produces all the possible answers but the modified one does
    not as expected.

  • Exercise 4.73

    The modified version implicitly forces all the element of given stream until it
    encountered with the-empty-stream:

    1. Evaluate flatten-stream with given argument stream;
    2. evaluate stream-cdr of given stream and evaluate flatten-stream with
      resulting stream;
    3. … if the subsequent flatten-stream calls encountered with
      the-empty-stream then calls return the control to the caller.
    4. finally the 1. is get the value from subsequent calls and then return the
      control to the caller.

    By above reasoning,

    1. without delaying, flatten-stream violates what we meant to stream at the
      first place – deferring the evaluation until it is really needed;
    2. even more, stream, with which flatten-stream called with, can possess
      infinite number of elements. In this case, flatten-stream run forever
      without returning the control to the caller.

    In this discourse, we used caller and callee concept explicitly since
    without this notation reader get easy to confuse with result given process can
    produce (infinite stream) with step of evolution (infinite loop).

  • Exercise 4.74

    • a.

      By the type constraint, we can fill the slots as

      1. (define (simple-flatten stream)
      2. (stream-map stream-car
      3. (stream-filter
      4. (lambda (s) (not (stream-null? s)))
      5. stream)))
    • b.

      No, since in those cases, both result in same stream. We can prove this
      assertion by structure induction on negate, lisp-value, find-assertions
      combined with induction on length of stream.

      More specifically we need to show

      1. In both implementation, the resulting stream has same element in same order.
      2. If one of the resulting stream is delayed in some part then the other one
        should result the stream with delayed in corresponding part.

      As above implementation satisfies given contracts, the behavior of both should
      be same.

  • Exercise 4.75

    It is analogous to not:

    1. (define (uniquely-asserted operand frame-stream)
    2. (stream-flatmap
    3. (lambda (frame)
    4. (let ((output-frames
    5. (qeval (unique-query operand)
    6. (singleton-stream frame))))
    7. (if (and (not (stream-null? output-frames))
    8. (stream-null? (stream-cdr output-frames)))
    9. output-frames
    10. the-empty-stream)))
    11. frame-stream))
    12. (define (unique-query operand) (car operand))

    And install on qeval:

    1. *** in initialize-data-base
    2. (put 'unique 'qeval uniquely-asserted)

    Then test:

    1. ;;; Query input:
    2. (unique (job ?x (computer wizard)))
    3. ;;; Query results:
    4. (unique (job (bitdiddle ben) (computer wizard)))
    5. ;;; Query input:
    6. (unique (job ?x (computer programmer)))
    7. ;;; Query results:
    8. ;;; Query input:
    9. (and (job ?x ?j) (unique (job ?anyone ?j)))
    10. ;;; Query results:
    11. (and (job (aull dewitt) (administration secretary)) (unique (job (aull dewitt) (administration secretary))))
    12. (and (job (cratchet robert) (accounting scrivener)) (unique (job (cratchet robert) (accounting scrivener))))
    13. (and (job (scrooge eben) (accounting chief accountant)) (unique (job (scrooge eben) (accounting chief accountant))))
    14. (and (job (warbucks oliver) (administration big wheel)) (unique (job (warbucks oliver) (administration big wheel))))
    15. (and (job (reasoner louis) (computer programmer trainee)) (unique (job (reasoner louis) (computer programmer trainee))))
    16. (and (job (tweakit lem e) (computer technician)) (unique (job (tweakit lem e) (computer technician))))
    17. (and (job (bitdiddle ben) (computer wizard)) (unique (job (bitdiddle ben) (computer wizard))))
  • Exercise 4.76

    After some experimenting with this new strategy, we can come up algorithm do the
    right things as follows:

    1. Assume that we have unify-frames, that is,
      • (unify-frames <frame1> <frame2>): takes two frames and produces output frame that contains all the variables
        in both frames with unified value if the frames are compatible with each
        other otherwise symbol 'failed.
    2. Takes two input streams which produced by each conjunct in and, then for
      each frame (say <frame1>) in the first argument stream, for each frame (say
      <frame2>) in the second argument, stream produce (unify-frames <frame1> <frame2>).
    3. So far we just dealt with binary operation; we can implement n-ary conjoin
      operation using induction on n based with above binary operation.

    Here is the codes for the unify-frames:

    1. (define (unify-frames frame1 frame2)
    2. (unify-bindings (frame->binding-list frame1) frame2))
    3. (define (unify-bindings bindings frame)
    4. (cond ((eq? frame 'failed) 'failed)
    5. ((null? bindings) frame)
    6. (else
    7. (unify-bindings
    8. (cdr bindings)
    9. (let ((binding (car bindings)))
    10. (extend-if-possible (binding-variable binding)
    11. (binding-value binding)
    12. frame))))))
    13. ;; ADT for frame, tranforming the type
    14. ;; Frame -> List<Binding>
    15. (define (frame->binding-list frame) frame)

    And following is the code for doing the second specification:

    1. ;; Frame -> (Query, Query -> Stream<Frame>)
    2. (define (conjoin-from-frame frame)
    3. (lambda (conjunct1 conjunct2)
    4. (stream-filter
    5. (lambda (frame)
    6. (not (eq? frame 'failed)))
    7. (stream-append-map
    8. (lambda (frame1)
    9. (stream-map
    10. (lambda (frame2)
    11. (unify-frames frame1 frame2))
    12. (qeval conjunct2 (singleton-stream frame))))
    13. (qeval conjunct1 (singleton-stream frame))))))

    Then the code for third one:

    1. ;; Frame -> (Conjuncts -> Stream<Frame>)
    2. (define (conjoin-from-frame frame)
    3. (define (conjoiner conjuncts)
    4. (if (empty-conjunction? (rest-conjuncts conjuncts))
    5. (first-conjunct conjuncts)
    6. (stream-filter
    7. (lambda (frame)
    8. (not (eq? frame 'failed)))
    9. (stream-append-map
    10. (lambda (frame1)
    11. (stream-map
    12. (lambda (frame2)
    13. (unify-frames frame1 frame2))
    14. (conjoiner (rest-conjuncts conjuncts))))
    15. (qeval (first-conjunct conjuncts)
    16. (singleton-stream frame))))))
    17. (lambda (conjuncts)
    18. (if (empty-conjunction? conjuncts)
    19. (singleton-stream frame)
    20. (conjoiner conjuncts))))

    It uses the algorithm that is analogous to binary operation.

    Or iterative process:

    1. (define (conjoin-from-frame frame)
    2. (define (conjoiner conjuncts conjoined)
    3. (if (empty-conjunction? conjuncts)
    4. conjoined
    5. (conjoiner
    6. (rest-conjuncts conjuncts)
    7. (stream-filter
    8. (lambda (frame)
    9. (not (eq? frame 'failed)))
    10. (stream-append-map
    11. (lambda (frame1)
    12. (stream-map
    13. (lambda (frame2)
    14. (unify-frames frame1 frame2))
    15. conjoined))
    16. (qeval (first-conjunct conjuncts)
    17. (singleton-stream frame)))))))
    18. (lambda (conjuncts)
    19. (if (empty-conjunction? conjuncts)
    20. (singleton-stream frame)
    21. (conjoiner (rest-conjuncts conjuncts)
    22. (qeval (first-conjunct conjuncts)
    23. (singleton-stream frame))))))

    Then the entry point becomes

    1. (define (conjoin conjuncts frame-stream)
    2. (stream-flatmap
    3. (lambda (frame)
    4. ((conjoin-from-frame frame) conjuncts))
    5. frame-stream))

    Then let’s test:

    1. ;;; Query input:
    2. (and (supervisor ?x ?boss) (supervisor ?y ?x) (supervisor ?z ?y))
    3. ;;; Query results:
    4. (and (supervisor (bitdiddle ben) (warbucks oliver)) (supervisor (hacker alyssa p) (bitdiddle ben)) (supervisor (reasoner louis) (hacker alyssa p)))
    5. ;;; Query input:
    6. (and (wheel ?who) (job ?who ?j))
    7. ;;; Query results:
    8. (and (wheel (warbucks oliver)) (job (warbucks oliver) (administration big wheel)))
    9. (and (wheel (warbucks oliver)) (job (warbucks oliver) (administration big wheel)))
    10. (and (wheel (warbucks oliver)) (job (warbucks oliver) (administration big wheel)))
    11. (and (wheel (warbucks oliver)) (job (warbucks oliver) (administration big wheel)))
    12. (and (wheel (bitdiddle ben)) (job (bitdiddle ben) (computer wizard)))

    It works on “simple” compound queries; however not work on the recursive rule
    application:

    1. ;;; Query input:
    2. (outranked-by ?x (Bitdiddle Ben))
    3. ;;; Query results:
    4. (outranked-by (tweakit lem e) (bitdiddle ben)) C-c C-c
    5. ;Quit!

    Or even not work on seemingly quite simple compound query:

    1. ;;; Query input:
    2. (grandson ?g ?gs)
    3. ;;; Query results: C-c C-c
    4. ;Quit!

    Actually this malfunction is due to (hidden) recursive rule:

    1. (assert! (rule (son ?m ?s)
    2. (and (wife ?m ?w) (son ?w ?s))))

    Without this rule definition, our conjoin works as expected:

    1. ;;; Query input:
    2. (grandson ?x ?y)
    3. ;;; Query results:
    4. (grandson mehujael lamech)
    5. (grandson irad methushael)
    6. (grandson enoch mehujael)
    7. (grandson cain irad)
    8. (grandson adam enoch)

    Even fails in this simple query:

    1. ;;; Query input:
    2. (lives-near (Bitdiddle Ben) ?who)
    3. ;;; Query results:

    Now let’s think about the cases where our new conjoin fails to halt. The
    commonalities among them are

    • it involve recursive rule application in addition to and in their body;
    • if we change the order of conjuncts with the original cojoin, it will fail
      to halt also.

    Actually the latter condition is crucial; thinking about why we failed in the
    latter condition even with the original one, we can notice that the
    “malfunction” is not due to our “ill-defined” procedure but its own limitation:
    We have meant to evaluate the conjuncts at the same time from the same frame,
    and then check their compatibility.

    So, the rules fail in our new conjoin if those would fail with our original
    one why not works if we change their order.

    It implies that we need the original conjoin even with this new conjoin but
    in a different syntax or explicit syntax – andthen. The order of clauses are
    matter in original conjoin but the name of syntax of that does not implies
    that or even confuse us to think it would work independently with the order of
    its clauses. Now our new conjoin works independently with the order as the
    name, and, implies; but we can embody the idea we have implemented with
    original conjoin so far.

    So let us rename original conjoin as conjoin-in-order and the syntax as
    andthen:

    1. (define (conjoin-in-order conjuncts frame-stream)
    2. (if (empty-conjunction? conjuncts)
    3. frame-stream
    4. (conjoin-in-order (rest-conjuncts conjuncts)
    5. (qeval (first-conjunct conjuncts)
    6. frame-stream))))
    7. *** in initialize-data-base
    8. (put 'andthen 'qeval conjoin-in-order)

    Then let’s test with:

    1. *** microshaft-data-base
    2. (rule (lives-near ?person-1 ?person-2)
    3. (andthen (and (address ?person-1 (?town . ?rest-1))
    4. (address ?person-2 (?town . ?rest-2)))
    5. (not (same ?person-1 ?person-2))))
    6. (rule (outranked-by ?staff-person ?boss)
    7. (or (supervisor ?staff-person ?boss)
    8. (andthen (supervisor ?staff-person ?middle-manager)
    9. (outranked-by ?middle-manager ?boss))))

    Now lives-near works:

    1. ;;; Query input:
    2. (lives-near ?who ?neighbor)
    3. ;;; Query results:
    4. (lives-near (reasoner louis) (aull dewitt))
    5. (lives-near (aull dewitt) (reasoner louis))
    6. (lives-near (bitdiddle ben) (aull dewitt))
    7. (lives-near (aull dewitt) (bitdiddle ben))
    8. (lives-near (bitdiddle ben) (reasoner louis))
    9. (lives-near (hacker alyssa p) (fect cy d))
    10. (lives-near (reasoner louis) (bitdiddle ben))
    11. (lives-near (fect cy d) (hacker alyssa p))

    And also recursive rule:

    1. ;;; Query input:
    2. (outranked-by ?who (Bitdiddle Ben))
    3. ;;; Query results:
    4. (outranked-by (tweakit lem e) (bitdiddle ben))
    5. (outranked-by (reasoner louis) (bitdiddle ben))
    6. (outranked-by (fect cy d) (bitdiddle ben))
    7. (outranked-by (hacker alyssa p) (bitdiddle ben))
  • Exercise 4.77

    We need to change the representation of frame. So far we used alist as our
    frame. Since we used ADT for frame, we can easily change the representation
    without dramatical change of code.

    Then we need to specify what features our frame should have. As we are going to
    support the “promise” to filter this frame if the condition satisfied; also we
    should have to support that if and only if some prior condition, let we call
    this as trigger condition, is satisfied the promised filter operation being
    tried.

    These specification is quite familiar with us; it indicates event-driven
    programming, that is, to check whether given object’s the condition has changed
    and it satisfy specified trigger condition, call the stored procedures or in
    other terms take an action.

    So our frame should work as wire in the digital simulator language or
    connector is constraint based arithmetic language in section 3.

    So our frame has to store callback list to be called whenever some program
    extend the given frame; callback function should return either frame of symbol
    failed. We can implement the filtering function by let the callback function
    return failed whenever the given condition is satisfied.

    Also whenever we add new callback function to our frame we should call given
    function with respect to current given frame since it may already have satisfied
    the trigger condition.

    And it may necessary to make our callback work one time only; that is whenever
    it once triggered, it should not be called anymore since our frame structure
    does not mutate existing bindings.

    So as summary to the callback function, it should meet following format:

    1. It should specify the trigger condition which take frame as argument.
    2. It should specify the action procedure which take frame and return frame or
      symbol failed.

    For the frame,

    1. It should store callback function list to activate whenever it extended.
    2. In that activating process, it should remove the callback function from the
      callback list after calling its action procedure whenever its trigger
      condition meets.

    Now we can rebuild the ADT of frame as follows:

    • Constructors
      • empty-frame: empty frame structure.
      • (make-frame <bindings> <callbacks>): return frame structure with
        <bindings> and <callbacks> as its part.
    • Selectors
      • (bindings <frame>): returns binding list of given <frame>.
      • (callbacks <frame>): returns callback list of given <frame>.
    • Operations

      • (binding-in-frame <variable> <frame>): returns binding in the <frame> that
        has <variable> as its variable part.

      • (extend <variable> <value> <frame>): returns extended frame with the given
        <variable> <value> binding or symbol failed.

        It call activate-callbacks with respect to the extended frame and return its
        result.

      • (add-callback <callback> <frame>): returns frame with extended callback
        list or symbol failed.

        It activate given <callback> with respect to given <frame> then if the
        <callback>‘s trigger condition doesn’t meet return the extended frame or
        return the result of <callback>‘s action application with respect to
        <frame>.

    Then the ADT for callback function:

    • Constructors
      • (make-callback <trigger> <action>): returns callback function with given
        <trigger> function and <action> function.
    • Selectors
      • (trigger <callback>): returns trigger function of given <callback>.
      • (action <callback>): returns action function of given <callback>.

    Then here is the code implementing above specifications with auxiliary
    procedures:

    1. ;;;; Frame ADT
    2. (define (binding-in-frame variable frame)
    3. (assoc variable (bindings frame)))
    4. (define (extend variable value frame)
    5. (activate-callbacks
    6. (make-frame (cons (make-binding variable value) (bindings frame))
    7. (callbacks frame))))
    8. ;; Callback -> Frame | failed
    9. (define (add-callback callback frame)
    10. (if ((trigger callback) frame)
    11. ((action callback) frame)
    12. (make-frame (bindings frame)
    13. (cons-callback
    14. callback (callbacks frame)))))
    15. ;; Frame -> Frame | failed
    16. (define (activate-callbacks frame)
    17. ;; Callback-list, Callback-list, Frame -> Frame | failed
    18. (define (loop wait-callbacks activateds frame)
    19. (cond ((eq? frame 'failed) 'failed)
    20. ((empty-callbacks? wait-callbacks)
    21. (make-frame (bindings frame)
    22. activateds))
    23. (else
    24. (let ((callback (first-callback wait-callbacks))
    25. (rests (rest-callbacks wait-callbacks)))
    26. (if ((trigger callback) frame)
    27. (loop rests
    28. activateds
    29. ((action callback) frame))
    30. (loop rests
    31. (cons-callback callback activateds)
    32. frame))))))
    33. (loop (callbacks frame) empty-callbacks frame))
    34. ;; Frame -> List<Binding>
    35. (define (bindings frame)
    36. (car frame))
    37. ;; Frame -> Callback-list
    38. (define (callbacks frame)
    39. (cadr frame))
    40. ;; List<Binding>, Callback-list -> Frame
    41. (define (make-frame bindings callbacks)
    42. (list bindings callbacks))
    43. (define empty-frame
    44. (make-frame empty-bindings empty-callbacks))
    45. ;;;; Callback list ADT
    46. (define empty-callbacks '())
    47. (define empty-callbacks? null?)
    48. (define rest-callbacks cdr)
    49. (define first-callback car)
    50. (define cons-callback cons)
    51. ;;;; Callback ADT
    52. ;; Trigger := Frame -> boolean
    53. ;; Action := Frame -> Frame | failed
    54. ;; Trigger, Action -> Callback
    55. (define (make-callback trigger-op action-op)
    56. (list trigger-op action-op))
    57. ;; Callback -> Trigger
    58. (define (trigger callback)
    59. (car callback))
    60. ;; Callback -> Action
    61. (define (action callback)
    62. (cadr callback))

    Then we are going to design procedure that would be cinch to defining trigger
    operation:

    • (has-constant? <variable> <frame>): returns true if the given <variable>
      has constant value in <frame> otherwise false.
    • (has-constants? <variable list> <frame>): take as input the list of
      variable and return true only if all the variables in the given list
      has-constant? in the given <frame>.
    • (extract-vars <pattern>): extract list of variables in the given <pattern>.

      In usual case, <pattern> is query.

    Here is the codes of above specifications:

    1. ;; Variable, Frame -> boolean
    2. (define (has-constant? val frame)
    3. (cond ((var? val)
    4. (let ((binding (binding-in-frame val frame)))
    5. (if binding
    6. (has-constant? val frame)
    7. false)))
    8. ((pair? val)
    9. (and (has-constant? (car val) frame)
    10. (has-constant? (cdr val) frame)))
    11. (else
    12. ;; constant
    13. true)))
    14. ;; List<Variable>, Frame -> boolean
    15. (define (has-constants? vars frame)
    16. (if (null? vars) true
    17. (and (has-constant? (car vars) frame)
    18. (has-constants? (cdr vars) frame))))
    19. (define (extract-vars pattern)
    20. ;; Pattern, List<Variable> -> List<Variable>
    21. (define (tree-walk exp extracteds)
    22. (cond ((var? exp)
    23. (cons exp extracteds))
    24. ((pair? exp)
    25. (tree-walk (cdr exp)
    26. (tree-walk (car exp)
    27. extracteds)))
    28. (else extracteds)))
    29. (tree-walk pattern '()))

    Then the negate becomes

    1. (define (negate operands frame-stream)
    2. (define callback
    3. (let ((vars (extract-vars (negated-query operands))))
    4. (make-callback
    5. (lambda (frame)
    6. (has-constants? vars frame))
    7. (lambda (frame)
    8. (if (stream-null? (qeval (negated-query operands)
    9. (singleton-stream frame)))
    10. frame
    11. 'failed)))))
    12. (stream-filter
    13. (lambda (frame)
    14. (not (eq? frame 'failed)))
    15. (stream-map
    16. (lambda (frame)
    17. (add-callback callback frame))
    18. frame-stream)))

    Similarly lisp-value:

    1. (define (lisp-value call frame-stream)
    2. (define callback
    3. (let ((vars (extract-vars call)))
    4. (make-callback
    5. (lambda (frame)
    6. (has-constants? vars frame))
    7. (lambda (frame)
    8. (if (execute
    9. (instantiate
    10. call
    11. frame
    12. (lambda (v f)
    13. (error "Unknown pat var -- LISP-VALUE" v))))
    14. frame
    15. 'failed)))))
    16. (stream-filter
    17. (lambda (frame)
    18. (not (eq? frame 'failed)))
    19. (stream-map
    20. (lambda (frame)
    21. (add-callback callback frame))
    22. frame-stream)))

    We need to modify unify-frames to combine the callbacks of both frame:

    1. (define (unify-frames frame1 frame2)
    2. (unify-bindings
    3. (frame->binding-list frame1)
    4. (fold-left ;add all the callbacks of frame1 to frame2
    5. (lambda (frame callback)
    6. (if (eq? frame 'failed)
    7. 'failed ;propagate failed
    8. (add-callback callback frame)))
    9. frame2
    10. (callbacks frame1))))

    Let’s test!

    1. ;;; Query input:
    2. (not (job (Bitdiddle Ben) (computer)))
    3. ;;; Query results:
    4. (not (job (bitdiddle ben) (computer)))
    5. ;;; Query input:
    6. (not (job (Bitdiddle Ben) (computer wizard)))
    7. ;;; Query results:
    8. ;;; Query input:
    9. (lisp-value < 1 2)
    10. ;;; Query results:
    11. (lisp-value < 1 2)
    12. ;;; Query input:
    13. (lives-near ?person ?neighbor)
    14. ;;; Query results: C-c C-c
    15. ;Quit!

    It works for the simple compound query but not the recursive one; run forever.

    This is time for debug. Here we are going to use binary search method with
    display:

    1. (define (negate operands frame-stream)
    2. (define callback
    3. (let ((vars (extract-vars (negated-query operands))))
    4. (make-callback
    5. (lambda (frame)
    6. (newline)
    7. (display "Variables:\t")
    8. (display vars)
    9. (display "\tin frame \t:")
    10. (display frame)
    11. (has-constants? vars frame))
    12. (lambda (frame)
    13. (newline)
    14. (display "action procedure")
    15. (if (stream-null? (qeval (negated-query operands)
    16. (singleton-stream frame)))
    17. frame
    18. 'failed)))))
    19. (stream-filter
    20. (lambda (frame)
    21. (not (eq? frame 'failed)))
    22. (stream-map
    23. (lambda (frame)
    24. (add-callback callback frame))
    25. frame-stream)))

    Then query:

    1. ;;; Query input:
    2. (lives-near ?person ?neighbor)
    3. ;;; Query results:
    4. Variables: ((? 25 person-2) (? 25 person-1)) in frame :((((? 25 person-2) aull dewitt) ((? 25 rest-2) (onion square) 5) ((? 25 rest-1) (onion square) 5) ((? 25 town) . slumerville) ((? 25 person-1) aull dewitt) ((? neighbor) ? 25 person-2) ((? person) ? 25 person-1)) ()) C-c C-c
    5. ;Quit!

    From this test, we can realize our extract-vars works as expected; the
    trigger function called once but never the action function of callback. It
    implies our has-constants? has problem in it:

    1. (define (has-constant? val frame)
    2. (cond ((var? val)
    3. (let ((binding (binding-in-frame val frame)))
    4. (if binding
    5. (has-constant?
    6. ;; val
    7. (binding-value binding)
    8. frame)
    9. false)))
    10. ((pair? val)
    11. (and (has-constant? (car val) frame)
    12. (has-constant? (cdr val) frame)))
    13. (else
    14. ;; constant
    15. true)))

    We have modified the argument of recursive call from val into (binding-value binding). This was the bug after fixing as above, let’s re-run the test:

    1. ;;; Query input:
    2. (lives-near ?person ?neighbor)
    3. ;;; Query results:
    4. (lives-near (reasoner louis) (aull dewitt))
    5. (lives-near (aull dewitt) (reasoner louis))
    6. (lives-near (bitdiddle ben) (aull dewitt))
    7. (lives-near (aull dewitt) (bitdiddle ben))
    8. (lives-near (bitdiddle ben) (reasoner louis))
    9. (lives-near (hacker alyssa p) (fect cy d))
    10. (lives-near (reasoner louis) (bitdiddle ben))
    11. (lives-near (fect cy d) (hacker alyssa p))

    Now works as expected.

    Do more tests:

    1. ;;; Query input:
    2. (assert! (rule (can-be-replaced-by ?p1 ?p2)
    3. (and (job ?p1 ?job1) (job ?p2 ?job2)
    4. (or (same ?job1 ?job2)
    5. (can-do-job ?job2 ?job1))
    6. (not (same ?p1 ?p2)))))
    7. Assertion added to data base.
    8. ;;; Query input:
    9. (can-be-replaced-by (Fect Cy D) ?whom)
    10. ;;; Query results:
    11. (can-be-replaced-by (fect cy d) (hacker alyssa p))
    12. (can-be-replaced-by (fect cy d) (bitdiddle ben))

    works as expected and if we change the order of not in the body, it should
    works as before:

    1. ;;; Query input:
    2. (assert! (rule (can-be-replaced-by ?p1 ?p2)
    3. (and (not (same ?p1 ?p2)) (job ?p1 ?job1)
    4. (job ?p2 ?job2)
    5. (or (same ?job1 ?job2)
    6. (can-do-job ?job2 ?job1)))))
    7. Assertion added to data base.
    8. ;;; Query input:
    9. (can-be-replaced-by (Fect Cy D) ?whom)
    10. ;;; Query results: C-c C-c
    11. ;Quit!

    But it isn’t. We can progress with our binary search debug:

    1. *** in action procedure
    2. (if (stream-null? (qeval (negated-query operands)
    3. (singleton-stream frame)))
    4. (begin
    5. (newline)
    6. (display "frame worked! with result:\t")
    7. (display frame)
    8. frame)
    9. (begin (newline) (display "failed worked!") 'failed))

    Then the response:

    1. ;;; Query input:
    2. (can-be-replaced-by (Fect Cy D) ?whom)
    3. ;;; Query results:
    4. Variables: ((? 7395 p2) (? 7395 p1)) in frame :((((? whom) ? 7395 p2) ((? 7395 p1) fect cy d)) ())
    5. Variables: ((? 7395 p2) (? 7395 p1)) in frame :((((? 7395 job1) computer programmer) ((? whom) ? 7395 p2) ((? 7395 p1) fect cy d)) ((#[compound-procedure 13] #[compound-procedure 14])))
    6. Variables: ((? 7395 p2) (? 7395 p1)) in frame :((((? 7395 job2) administration secretary) ((? 7395 job1) computer programmer) ((? whom) ? 7395 p2) ((? 7395 p1) fect cy d)) ((#[compound-procedure 13] #[compound-procedure 14])))
    7. Variables: ((? 7395 p2) (? 7395 p1)) in frame :((((? 7395 p2) aull dewitt) ((? 7395 job2) administration secretary) ((? 7395 job1) computer programmer) ((? whom) ? 7395 p2) ((? 7395 p1) fect cy d)) ((#[compound-procedure 13] #[compound-procedure 14])))
    8. action procedure with frame: ((((? 7395 p2) aull dewitt) ((? 7395 job2) administration secretary) ((? 7395 job1) computer programmer) ((? whom) ? 7395 p2) ((? 7395 p1) fect cy d)) ((#[compound-procedure 13] #[compound-procedure 14])))
    9. ...
    10. C-c C-c
    11. ;Quit!

    But none of the branches of action procedure have been passed. Now we know
    where is the bug: the predicate part of if. Let’s put more display in that
    part:

    1. *** in action procedure
    2. (if (stream-null?
    3. (begin
    4. (newline)
    5. (display "the negated-query\t")
    6. (display (negated-query operands))
    7. (newline)
    8. (display "With frame:\t")
    9. (display frame)
    10. (qeval (negated-query operands)
    11. (singleton-stream frame))))
    12. ...)

    Then we can run the specific case in manually:

    1. ;;; Query input:
    2. (can-be-replaced-by (Fect Cy D) ?whom)
    3. ;;; Query results:
    4. the negated-query (same (? 27833 p1) (? 27833 p2))
    5. With frame: ((((? 27833 p2) aull dewitt) ((? 27833 job2) administration secretary) ((? 27833 job1) computer programmer) ((? whom) ? 27833 p2) ((? 27833 p1) fect cy d)) ((#[compound-procedure 15] #[compound-procedure 16])))
    6. ...

    as

    1. (define query '(same (? 27833 p1) (? 27833 p2)))
    2. (define frame
    3. `((((? 27833 p2) aull dewitt)
    4. ((? 27833 job2) administration secretary)
    5. ((? 27833 job1) computer programmer)
    6. ((? whom) ? 27833 p2)
    7. ((? 27833 p1) fect cy d))
    8. ((,#@15 ,#@16))))

    Then:

    1. (qeval query (singleton-stream frame))
    2. the negated-query (same (? 27833 p1) (? 27833 p2))
    3. With frame: ((((? 27873 x) fect cy d) ((? 27833 p2) aull dewitt) ((? 27833 job2) administration secretary) ((? 27833 job1) computer programmer) ((? whom) ? 27833 p2) ((? 27833 p1) fect cy d)) ((#[compound-procedure 15] #[compound-procedure 16])))
    4. the negated-query (same (? 27833 p1) (? 27833 p2))
    5. ...

    This result is quite complicated mess. Here what we should expect from the above
    execution was just

    1. (qeval query (singleton-stream frame))
    2. ;Value: ()

    not calling the callback action recursively, which led into infinite loop.

    After some reasoning, we can understand that it is due to the unify-match
    calling in the apply-a-rule, which in turn called when we process the same
    query above. Unify-match calls extend-if-possible in processing the body of
    it, and it led to call extend; that now calls the action procedure with
    which all the process started.

    To fix this, all we need to do is just remove all the callbacks from the frame
    , with which the predicate part of if processed, which in turn led all of
    these mass.

    The followings do what specified above:

    1. (define (remove-callbacks frame)
    2. (make-frame (bindings frame)
    3. empty-callbacks))
    1. *** in negate
    2. (define callback
    3. (let ((vars (extract-vars (negated-query operands))))
    4. (make-callback
    5. (lambda (frame)
    6. (has-constants? vars frame))
    7. (lambda (frame)
    8. (if (stream-null?
    9. (qeval (negated-query operands)
    10. (singleton-stream (remove-callbacks frame))))
    11. frame
    12. 'failed)))))

    Now it works:

    1. ;;; Query input:
    2. (assert! (rule (can-be-replaced-by ?p1 ?p2)
    3. (and (not (same ?p1 ?p2)) (job ?p1 ?job1)
    4. (job ?p2 ?job2)
    5. (or (same ?job1 ?job2)
    6. (can-do-job ?job2 ?job1)))))
    7. Assertion added to data base.
    8. ;;; Query input:
    9. (can-be-replaced-by (Fect Cy D) ?whom)
    10. ;;; Query results:
    11. (can-be-replaced-by (fect cy d) (hacker alyssa p))
    12. (can-be-replaced-by (fect cy d) (bitdiddle ben))

    Or like this (variation of Exercise 4.56 c.):

    1. ;;; Query input:
    2. (and (lisp-value > ?a1 ?a2)
    3. (can-be-replaced-by ?p1 ?p2) (salary ?p1 ?a1)
    4. (salary ?p2 ?a2))
    5. ;;; Query results:
    6. (and (lisp-value > 150000 25000) (can-be-replaced-by (warbucks oliver) (aull dewitt)) (salary (warbucks oliver) 150000) (salary (aull dewitt) 25000))
    7. (and (lisp-value > 40000 35000) (can-be-replaced-by (hacker alyssa p) (fect cy d)) (salary (hacker alyssa p) 40000) (salary (fect cy d) 35000))

    With this, our lives-near rule has not use andthen, just and:

    1. (rule (lives-near ?person-1 ?person-2)
    2. ;; (andthen (and (address ?person-1 (?town . ?rest-1))
    3. ;; (address ?person-2 (?town . ?rest-2)))
    4. ;; (not (same ?person-1 ?person-2)))
    5. (and (not (same ?person-1 ?person-2))
    6. (address ?person-1 (?town . ?rest-1))
    7. (address ?person-2 (?town . ?rest-2))))

    Then

    1. ;;; Query input:
    2. (lives-near ?who ?neighbor)
    3. ;;; Query results:
    4. (lives-near (reasoner louis) (aull dewitt))
    5. (lives-near (aull dewitt) (reasoner louis))
    6. (lives-near (bitdiddle ben) (aull dewitt))
    7. (lives-near (aull dewitt) (bitdiddle ben))
    8. (lives-near (bitdiddle ben) (reasoner louis))
    9. (lives-near (hacker alyssa p) (fect cy d))
    10. (lives-near (reasoner louis) (bitdiddle ben))
    11. (lives-near (fect cy d) (hacker alyssa p))
  • Exercise 4.78

    We need to map from frames as stream representation to frames as time branches.
    We don’t need to change all of the procedures from the scratch. Actually we can
    re-use all of the procedures that is not specific to the stream representation.

    So our pattern matcher and unifier works as before in this new scheme. Even for
    the stream specific implementation, the mapping is quite straightforward. That
    is, where we processed with stream of frames, we are good to map it to just one
    frame instance; where we used the-empty-stream to abort current frame, we need
    to make that frame fail – call (amb).

    The problem is disjoin. What is the counterpart of interleave-delayed in
    amb evaluator? We need to reason about the relationship between the input
    streams and the output stream; it produce the stream as if it searched the input
    streams in breadth first manner. However, as our amb evaluator chose
    chronological search order – so called the depth first search – it cause
    quite huge modification from the very scratch to emulate this BFS (Breadth First
    Search) in our amb evaluator.

    So, here we just ramb to implement disjoin in our situation. The reasons why
    we chose this are

    1. If we just used amb instead of ramb to combine disjuncts, it would be
      equivalent for stream version to use stream-append-delayed in combining them.
    2. It is still true that in amb evaluator we can not emulate BFS even with
      ramb; however we are able to produce all the answers if the user type
      try-again enough times in theory. While, if we stick with amb, then the
      probabilities it going to happen goes to 0.

    So we better off with ramb in disjoin.

    For the negate, we need control structure to “try out” the given frame with
    the nagated-query of given operands. As we postulated above,
    the-empty-stream is equivalent (amb) in amb evaluator. So what we need
    here is to check whether the given evaluation is going to fail, if so, do
    something, else do others. This is exactly what if-fail does:

    1. (define (negate operands frame)
    2. (define callback
    3. (let ((vars (extract-vars (negated-query operands))))
    4. (make-callback
    5. (lambda (frame)
    6. (has-constants? vars frame))
    7. (lambda (frame)
    8. (if-fail
    9. (begin
    10. (qeval (negated-query operands)
    11. (remove-callbacks frame))
    12. 'failed)
    13. frame)))))
    14. (filter-failed (add-callback callback frame)))

    Unfortunately, the above implementation would blindly pass frame whether or
    not we have given negated-query fails or not:

    1. ;; Test negate
    2. (define (simulate-negate-action)
    3. (if-fail
    4. (begin (amb 1 2 3 4)
    5. 'failed)
    6. 'bilndly-pass-through))
    7. (filter-failed (simulate-negate-action))

    Then (for the definition of filter-failed, please reference the whole code
    will appear below):

    1. ;;; Amb-Eval input:
    2. (filter-failed (simulate-negate-action))
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. bilndly-pass-through

    It is due to mainly our deficiency in the behavior we expect from negate. We
    need to specify the behavior of negate abstractly enough to be implemented in
    any programming language framework.

    After some reasoning we can deduce abstract behavior of negate in amb
    language:

    1. The frame branches of qeval part in negate should be empty.
    2. Need to traverse the time branches caused by qeval part then if it success
      ever, then it should signal this fact to the caller of qeval.

    For the implementation details, we can achieve the second behavior of above
    using assignment; however with the same argument as disjoin, we can not fast
    escape
    in amb language as we did in stream version. That is to abort as soon
    as it found that the produced frames are not empty, it quits to evaluate
    subsequent frames otherwise it would produced.

    The reason behind this fact is that we aren’t have any explicit method for
    manipulating the time branches amb evaluation. We can not teleport the time
    point into specific point in the time tree9. So we need to traverse all the
    time branches produced by

    1. (qeval (negated-query operands)
    2. (remove-callbacks frame))

    just to abort. It may produce infinite number of frame branches; in that case,
    the evaluation of negate would also run forever without returning the control
    to the caller.

    This is huge difference and unfortunate from the stream version of query
    language.

    Anyway we can implement this idea as

    1. (define (negate operands frame)
    2. (define callback
    3. (let ((vars (extract-vars (negated-query operands))))
    4. (make-callback
    5. (lambda (frame)
    6. (has-constants? vars frame))
    7. (lambda (frame)
    8. (let ((succeed? false))
    9. (if-fail
    10. (begin (qeval (negated-query operands)
    11. (remove-callbacks frame))
    12. (permanent-set! succeed? true))
    13. 'ignore)
    14. (if succeed?
    15. 'failed
    16. frame))))))
    17. (filter-failed (add-callback callback frame)))

    Here we used the combination of if-fail and permanent-set! as we did before
    to cope with the time branch abortion.

    Then (unit) test:

    1. ;;; Amb-Eval input:
    2. (define (simulate-negate-action-failed)
    3. (let ((succeed? false))
    4. (if-fail
    5. (begin (amb 1 2 3 4)
    6. (permanent-set! succeed? true))
    7. 'ignore)
    8. (if succeed? 'failed
    9. 'frame)))
    10. ;;; Starting a new problem
    11. ;;; Amb-Eval value:
    12. ok
    13. ;;; Amb-Eval input:
    14. (define (simulate-negate-action-succeed)
    15. (let ((succeed? false))
    16. (if-fail
    17. (amb)
    18. 'ignore)
    19. (if succeed? 'failed
    20. 'frame)))
    21. ;;; Starting a new problem
    22. ;;; Amb-Eval value:
    23. ok
    24. ;;; Amb-Eval input:
    25. (filter-failed (simulate-negate-action-failed))
    26. ;;; Starting a new problem
    27. ;;; There are no more values of
    28. (filter-failed (simulate-negate-action-failed))
    29. ;;; Amb-Eval input:
    30. (filter-failed (simulate-negate-action-succeed))
    31. ;;; Starting a new problem
    32. ;;; Amb-Eval value:
    33. frame

    Yet another difficulty is execute. If we chose ordinary eval/apply
    interpreter as evaluator for backbone of query language, it is easy to
    implement; however in our amb evaluator, as the implementation of that
    evaluator consisted of succeed/fail chains, to reason about this chains to
    implement the counterparts of apply and eval.

    Here we also try to capture the behavior we want from execute:

    • Just extract the expression off the quoted expression and then evaluate that
      with the received env, succeed, fail.

    Here is the implementation:

    1. (define (execute? exp) (tagged-list? exp 'execute))
    2. (define (execute-expression exp) (cadr exp))
    3. (define (analyze-execute exp)
    4. (lambda (env succeed fail)
    5. ((analyze-quoted (execute-expression exp))
    6. env
    7. (lambda (exp2 fail2)
    8. ((analyze exp2) env
    9. succeed fail2))
    10. fail)))

    Then test:

    1. ;;; Amb-Eval input:
    2. (execute '(> 5 4))
    3. ;;; Starting a new problem
    4. ;;; Amb-Eval value:
    5. #t

    From this implementation, we got the point that the nondeterministic evaluator
    does offer quite restricted freedom to user for the purpose to support the
    automatic search.

    Rest is quite rather simple task than above ones.

    Then test:

    1. ;;; Amb-Eval input:
    2. (query-driver-loop)
    3. ;;; Starting a new problem
    4. ;;; Query input:
    5. (lives-near ?who ?neighbor)
    6. ;;; Query results:
    7. (lives-near (aull dewitt) (reasoner louis))
    8. ;;; Query input:
    9. next-result
    10. ;;; Query results:
    11. (lives-near (aull dewitt) (bitdiddle ben))
    12. ;;; Query input:

    unique test:

    1. ;;; Query input:
    2. (unique (job ?x (computer wizard)))
    3. ;;; Query results:
    4. (unique (job (bitdiddle ben) (computer wizard)))
    5. ;;; Query input:
    6. (unique (job ?x (computer programmer)))
    7. ;;; Query results:
    8. (lives-near (reasoner louis) (bitdiddle ben))
    9. ;;; Query input:

    The last response is quite quirk. We can fix this with if-fail:

    1. (define (query-driver-loop)
    2. (prompt-for-input input-prompt)
    3. (let ((input (read)))
    4. (if (eq? input 'next-result)
    5. (begin
    6. (newline)
    7. (display output-prompt)
    8. (newline)
    9. (amb))
    10. (let ((q (query-syntax-process input)))
    11. (cond ((assertion-to-be-added? q)
    12. (add-rule-or-assertion! (add-assertion-body q))
    13. (newline)
    14. (display "Assertion added to data base.")
    15. (query-driver-loop))
    16. (else
    17. (newline)
    18. (display output-prompt)
    19. ;; [extra newline at end] (announce-output output-prompt)
    20. (newline)
    21. (if-fail
    22. (display
    23. (instantiate q
    24. (qeval q empty-frame)
    25. (lambda (v f)
    26. (contract-question-mark v))))
    27. (begin
    28. (display ";;; There are no more result of")
    29. (newline)
    30. (display
    31. (instantiate
    32. q empty-frame
    33. (lambda (v f)
    34. (contract-question-mark v))))))
    35. (query-driver-loop)))))))

    Then

    1. ;;; Query input:
    2. (unique (job ?x (computer wizard)))
    3. ;;; Query results:
    4. (unique (job (bitdiddle ben) (computer wizard)))
    5. ;;; Query input:
    6. next-result
    7. ;;; Query results:
    8. ;;; There are no more result of
    9. (unique (job ?x (computer wizard)))
    10. ;;; Query input:
    11. (unique (job ?x (computer programmer)))
    12. ;;; Query results:
    13. ;;; There are no more result of
    14. (unique (job ?x (computer programmer)))
    15. ;;; Query input:

    Others also works fine:

    1. ;;; Query input:
    2. (outranked-by ?who (Bitdiddle Ben))
    3. ;;; Query results:
    4. (outranked-by (tweakit lem e) (bitdiddle ben))
    5. ;;; Query input:
    6. next-result
    7. ;;; Query results:
    8. (outranked-by (fect cy d) (bitdiddle ben))
    9. ;;; Query input:

    Then as we noted above, the difference of this implementation from the stream
    version is the followings:

    1. ;;; Query input:
    2. (not (infinite answer))
    3. ;;; Query results:
    4. C-c C-c;Quit!

    Or the or from the previous example:

    1. ;;; Starting a new problem
    2. ;;; Query input:
    3. (or (infinite ?x)
    4. (outranked-by ?who (Bitdiddle Ben)))
    5. ;;; Query results:
    6. (or (infinite ?x) (outranked-by (tweakit lem e) (bitdiddle ben)))
    7. ;;; Query input:
    8. next-result
    9. ;;; Query results:
    10. (or (infinite ?x) (outranked-by (fect cy d) (bitdiddle ben)))

    And:

    1. ;;; Query input:
    2. (or (infinite ?x)
    3. (outranked-by ?who (Bitdiddle Ben)))
    4. ;;; Query results:
    5. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    6. ;;; Query input:
    7. next-result
    8. ;;; Query results:
    9. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))
    10. ...
    11. ;;; Query input:
    12. next-result
    13. ;;; Query results:
    14. (or (infinite answer) (outranked-by ?who (bitdiddle ben)))

    As noted above.

  • Exercise 4.79

    Here we try to change the implementation details from the scratch; that is, we
    change our model of evaluation or control system from stream (or branches) of
    frames to environment model as we learned in section 3 and implemented the
    preceding parts of this section.

    To emigrate to environment, first, we need to understand what feature of
    environment model we are going to implement and what new aspect we need to add
    that we haven’t encountered in previous evaluation model.

    So we re-learn about the environment model as the text indicates. In the problem
    statement, they posted the examples that illustrate the killer feature –
    avoiding the name capture. This is one of the most important concept in the
    lambda calculus, which is the backbone of all of the computer science theory.

    Contrast with which, so far in this query language implementation, we escaped
    this name capture problem by issuing the unique name every each time we apply
    the rule. Actually we can use this alternative strategy to our meta circular
    evaluator which used environment model instead in that time.

    The “issuing” unique variable name is quite simple and clever idea especially in
    this query language since the query itself can (and usually do) have variable,
    which yet unbound in that time. If the variable failed to be bound as the result
    of query, this specific implementation detail helps a lot or make sense the
    representation of this fact.

    Think about the (last-pair <list> <last-pair>) query with the <list> part
    unbound. As we seen from ex 4.62, it unfolds all possible answers using the
    unbound variable and using “different” variable representation. It make sense
    since in our language, same variable name indicates has to be bound with same
    value. But in that example, all of the variable do not need to be bound at same
    value. In other cases, where the result unbound variables should be bound at
    same value when it be bound, our current implementation deals with those without
    any problem also.

    Exactly this is the difference between our previous evaluation situation from
    current query language implementation. Can our new implementation deals with
    this problem without any confusing? Or should we implement this behavior? That
    is, to inform the user those are possibly different variables even it bound
    using same rule. Or is it good enough to be consistent with those concept
    internally, i.e. in machine information.

    Previously, we did not concern with those concept or need not be bothered by
    those since our evaluation or computation progress in unidirectional way: When
    we apply given procedure, the argument should be bound with some constant value.
    So if we hand over the values of given arguments to the procedure, we are free
    from worrying about the environment where the evaluation made; The environment
    part of procedure to be applied would be used afterward evaluation.

    One way to deal with the unbound variables in procedural style is to make
    unbound object whenever we encounter with those; and hand over those to
    subsequent rule application arguments. And those objects would be bound
    afterward evaluations (or queries). This is similar strategy we took in
    constraint based arithmetic language in section 3. In this way, we can resolve
    the name capture error since we do not hand over the frame itself to the
    subsequent process any more – just pass the places where those process should
    set those value if the query has succeeded.

    In this way of implementation, the previously observed problem would be No
    confusion in machine level but yes in human level
    .

    Then as our query only require whether it succeeded or not, if the subsequent
    process succeeded, it will just inform this fact to the caller unlike the
    procedure evaluation, where the callee should return some (possibly useful)
    value to the caller.

    With the above informal specification how we are going to implement (or change)
    the query language, we implicitly used mutator (set) to bind the value to the
    given place (or variable). Actually if we decided another way around such as to
    decide to return valuable value as success of query process – frame for the
    unbound variables, for which the query made – we may eliminate all the
    mutations in implementation going to be made. This implies we can exploit our
    stream implementation of query language as base implementation to which we are
    going to make change from frames into environments.

    However, if we make up our mind to stick with mutation version, we would be
    better to go with nondeterministic implementation, which we made in preceding
    exercise, since it has the feature to revert the mutation once its process
    aborted; and more, the assignment and stream is very different entities as we
    have been noted over and over again in preceding captures.

    So here we are going to implement the emigration to the environment using the
    nondeterministic evaluator with object and mutation. This would cause change to
    the unifier and matcher implementation a little bit. And if we, afterward,
    extends our language to the block-structured rule definition as we did in
    page 36 in text in procedure, we need to change the rule definition to capture
    the environment where the definition made.

    To deal with these changes, we need to analyze which component is the
    counterpart of that of meta circular evaluator (or underlying Lisp) to implement
    those concept properly. As the text noticed or we learned from the
    implementation of query language, rule is like procedure. Then what is
    assertions? It is definitely the primitive elements that composing the query
    language; and it has many similarities with primitive procedures since the rule
    application and assertion matches tried to the same query as the primitive
    procedures and compound procedures are tried in manner (in apply).

    From this observation we may want to implement the assert new assertion not to
    capture the evaluation environment or force it to be made only in, say, global
    environment.

    We have delivered a lot of specification that can be made before actually
    implement any of the details.

    Finally before start to implement the details of above specifications, let we
    consider the last question in the problem statement. That is, to relate the
    block structured rule to the problem of making deductions in a context (e.g. “If
    I supposed that P were true, then I would be able to deduce A and B.”) as
    a method of problem solving.

    First, let we consider the “If I supposed that P were true, then I would be
    able to deduce A“ in logically. Actually, in the truth table, it is equivalent
    to (\neg P \lor A); but in our query language as not is not the logical not as
    noted in the text, we can not express this fact. However we can express yet
    another equivalent form (\neg (P \land \neg A)):

    1. (not (and P (not A)))

    Let’s test this fact with the grandson relation in exercise 4.63:

    1. ;;; Query input:
    2. (not (and (grandson ?g ?s)
    3. (not (and (son ?g ?f)
    4. (son ?f ?s)))))
    5. ;;; Query results:
    6. (not (and (grandson ?g ?s) (not (and (son ?g ?f) (son ?f ?s)))))

    It seems like works but actually not:

    1. ;;; Query input:
    2. (and (grandson ?g ?s)
    3. (not (and (son ?g ?f)
    4. (son ?f ?s))))
    5. ;;; Query results:
    6. (and (grandson mehujael lamech) (not (and (son mehujael ?f) (son ?f lamech))))
    7. (and (grandson methushael jubal) (not (and (son methushael ?f) (son ?f jubal))))
    8. (and (grandson methushael jabal) (not (and (son methushael ?f) (son ?f jabal))))
    9. (and (grandson irad methushael) (not (and (son irad ?f) (son ?f methushael))))
    10. (and (grandson enoch mehujael) (not (and (son enoch ?f) (son ?f mehujael))))
    11. (and (grandson cain irad) (not (and (son cain ?f) (son ?f irad))))
    12. (and (grandson adam enoch) (not (and (son adam ?f) (son ?f enoch))))

    Since now our not is modified from the original one in exercise 4.76; our new
    not does not process the negated query unless the variables in that are all
    bound. We need the behavior of previous not in this case to accomplish this.
    So we’ve added not!, which is exactly the previous not, that process the
    negated query independent whether those variables has value or not10.

    Then let re-run what we tested above:

    1. ;;; Query input:
    2. (not! (and (grandson ?g ?s)
    3. (not! (and (son ?g ?f)
    4. (son ?f ?s)))))
    5. ;;; Query results:
    6. (not! (and (grandson ?g ?s) (not! (and (son ?g ?f) (son ?f ?s)))))
    7. ;;; Query input:
    8. (and (grandson ?g ?s)
    9. (not! (and (son ?g ?f)
    10. (son ?f ?s))))
    11. ;;; Query results:
    12. ;;; Query input:

    Now works as expected; and more, this usage of not! works as it works in
    logic:

    1. ;;; Query input:
    2. (not! (not! (and (grandson ?g ?s)
    3. (not! (and (son ?g ?f)
    4. (son ?f ?s))))))
    5. ;;; Query results:
    6. ;;; Query input:

    Unfortunately our logical if ~ then can not cope with arbitrary arguments
    while in mathematical logic can reason about that:

    1. ;;; Query input:
    2. (not! (andthen (andthen (reverse ?x ?y)
    3. (reverse ?z ?x))
    4. (not! (same ?z ?y))))
    5. ;;; Query results: C-c C-c
    6. ;Quit!

    This is due to the behavior of our query language, that is, to verify even if
    the given one is arbitrary one (unbound one) rather than add that property
    (reverse relation) to the given entities and then check whether this newly added
    property is consistent with existing ones.

    What we want to capture would be for all x1, x2, … if we can say A
    and B is satisfied in a context where P is true, then we can say we deduced
    A and B from P. If we represent this statement in a mathematical logic
    language, it becomes

    • [\forall (x{1}, x{2}, \ldots).~P(x{1}, x{2}, \ldots) \implies \left{
      A(x{1}, x{2}, \ldots) \land B(x{1}, x{2}, \ldots) \right} ]

    As noted before it is equivalent to

    • [\neg \exists (x{1}, x{2}, \ldots).~ P(x{1}, x{2}, \ldots) \land \neg
      \left{ A(x{1}, x{2}, \ldots) \land B(x{1}, x{2}, \ldots)\right}]

    So we can express this fact in our query language if the variables in those
    (x1, x2, \ldots) are closed in our discourse – what we can find in
    assertions, e.g.:

    1. (rule (grandson->def)
    2. (not! (and (grandson ?g ?s)
    3. (not! (and (son ?g ?f)
    4. (son ?f ?s))))))
    5. ;;; Query input:
    6. (grandson->def)
    7. ;;; Query results:
    8. (grandson->def)

    So far, we interpreted the quoted sentence in mathematical logic; since we did
    this without any use of block structured rule, it would not be the one the text
    wanted from us. It may want us to use the context captured by rule definition.

    Since yet we haven’t implemented the environment model in the query language, we
    are not able to capture what the text wanted exactly.

    So it’s time for design & coding. We are going to implement the whole system by
    starting with simple and small working examples and then extend to the whole
    complex system.

    We will implement whole system by breaking into two pieces:

    1. Get rid all of frames (into environment).
    2. Extend the process of definition of rules to be able to capture the context
      – environment.
    • Get rid all of frames

      We are going to implement this task relying on our old strategy, wishful
      thinking. That is, we will design the high level procedure first, and then from
      this knowledge, we are going to specify the low level design details (the
      matcher and unifier).

      After that, we can implement high level procedures with the specification of low
      level procedure, and then fill the rest.

      First we should skim all of the process of current evaluation to design high
      level procedures.

      After some inspection, we’ve deduced following specifications:

      • Qeval should extend given environment by binding each unbound variable in
        the query with new variable object.
      • Subsequent process of simple-query should instantiate received query pattern
        with the given environment since pattern matcher or unifier needs that.
      • Since we use nondeterministic evaluator as implementation language, we can
        exploit the (amb) in the pattern matcher or unifier; that is, we can abort
        as soon as we encounter the match failed rather than propagate this fact
        to chained process.

      • The smallest working sample: Using pattern matcher

        The goal of this section is to implement answering the following query using
        environment:

        1. (job ?p ?j)

        To make this happen, as we said above, we need new variable object, which has
        identity in it:

        ```scheme
        ;;; variable object ADT
        ;;;; constructor
        (define (make-var-obj)
        (list ‘var (make-var-value)))
        ;;;; detector
        (define (var-obj? obj)
        (tagged-list? obj ‘var))

        (define (has-value? var-obj)
        (and (var-obj? var-obj)

        1. ((var-value var-obj) 'bound?)))

        ;;;; selector
        (define (bound-value var-obj)
        ((var-value var-obj) ‘value))

        ;;;; mutator
        ;;; ↓can not revert the mutation when subbranching aborted
        ;; (define (set-value! var value)
        ;; (let ((val (var-value var)))
        ;; (set-car! val ‘bound)
        ;; (set-cdr! val value)))

        (define (set-value! var-obj val)
        (((var-value var-obj) ‘set-value!) val))

        ;;;; operation on var-obj
        (define (instantiate-value exp)
        (cond ((var-obj? exp)

        1. (instantiate-value (bound-value exp)))
        2. ((pair? exp)
        3. (cons (instantiate-value (car exp))
        4. (instantiate-value (cdr exp))))
        5. (else exp)))

        ;; internal selector
        (define (var-value obj)
        (cadr obj))

  1. ;;; var-value ADT
  2. (define (make-var-value)
  3. (let ((bound? false)
  4. (value '()))
  5. (lambda (m)
  6. (cond ((eq? m 'bound?) bound?)
  7. ((eq? m 'value) value)
  8. ((eq? m 'set-value!)
  9. (lambda (new-value)
  10. (set! bound? true)
  11. (set! value new-value)))))))
  12. ```
  13. And the rest changes:
  14. ```scheme
  15. ;; Design & implementation
  16. ;;; Phase 1 implement simple & working sample
  17. ;;; Environment ADT
  18. (define empty-environment '())
  19. (define (empty-env? env) (null? env))
  20. (define (extend-env frame env) (cons frame env))
  21. (define (first-frame env) (car env))
  22. (define (enclosing-env env) (cdr env))
  23. (define (binding-in-env var env)
  24. (if (empty-env? env)
  25. false
  26. (or (binding-in-frame var (first-frame env))
  27. (binding-in-env var (enclosing-env env)))))
  28. ;;; Driver-loop
  29. (define (query-driver-loop)
  30. (prompt-for-input input-prompt)
  31. (let ((input (read)))
  32. (if (eq? input 'next-result)
  33. (begin
  34. (newline)
  35. (display output-prompt)
  36. (newline)
  37. (amb))
  38. (let ((q (query-syntax-process input)))
  39. (cond ((assertion-to-be-added? q)
  40. (add-rule-or-assertion! (add-assertion-body q))
  41. (newline)
  42. (display "Assertion added to data base.")
  43. (query-driver-loop))
  44. (else
  45. (newline)
  46. (display output-prompt)
  47. ;; [extra newline at end] (announce-output output-prompt)
  48. (newline)
  49. (if-fail
  50. (display
  51. (instantiate
  52. q (qeval q empty-environment)
  53. (lambda (v f)
  54. (contract-question-mark v))
  55. (lambda (obj env)
  56. (instantiate-value obj))))
  57. (begin
  58. (display ";;; There are no more result of")
  59. (newline)
  60. (display
  61. (instantiate
  62. q empty-environment
  63. (lambda (v f)
  64. (contract-question-mark v))
  65. (lambda (var env)
  66. var)))))
  67. (query-driver-loop)))))))
  68. ;;;The Evaluator
  69. ;; Query, Environment -> Environment
  70. (define (qeval query env)
  71. (let ((qproc (get (type query) 'qeval))
  72. (env (extend-if-unbound-variable
  73. query env)))
  74. (if qproc
  75. (qproc (contents query) env)
  76. (simple-query query env))
  77. env))
  78. ;; Pattern, Env -> Env
  79. (define (extend-if-unbound-variable exp env)
  80. ;; Pattern, Frame -> Frame
  81. (define (extend-frame exp frame)
  82. (cond ((var? exp)
  83. (let ((binding (binding-in-env exp env)))
  84. (if (not binding)
  85. (extend
  86. exp (make-var-obj) frame)
  87. frame)))
  88. ((pair? exp)
  89. (extend-frame (cdr exp)
  90. (extend-frame (car exp) frame)))
  91. (else frame)))
  92. (extend-env (extend-frame exp empty-frame) env))
  93. (define (instantiate exp env unbound-var-handler var-obj-handler)
  94. (define (copy exp)
  95. (cond ((var? exp)
  96. (let ((binding (binding-in-env exp env)))
  97. (if binding
  98. (copy (binding-value binding))
  99. (unbound-var-handler exp env))))
  100. ((var-obj? exp) ;debugging!
  101. (var-obj-handler exp env))
  102. ((pair? exp)
  103. (cons (copy (car exp)) (copy (cdr exp))))
  104. (else exp)))
  105. (copy exp))
  106. ;;;Simple queries
  107. ;; Query, Env -> 'done
  108. (define (simple-query query-pattern env)
  109. (amb (find-assertions query-pattern env)
  110. (apply-rules query-pattern env)))
  111. ;;;Finding Assertions by Pattern Matching
  112. (define (find-assertions pattern env)
  113. (check-an-assertion (fetch-assertions pattern env)
  114. pattern env))
  115. (define (check-an-assertion assertion query-pat query-env)
  116. (pattern-match
  117. (instantiate
  118. query-pat query-env
  119. (lambda (v f)
  120. (error "pat var is not instantiated -- CHECK-AN-ASSERTION" v))
  121. (lambda (var env)
  122. var))
  123. assertion))
  124. ;; Pattern, Datum -> 'done | abort
  125. (define (pattern-match pat dat)
  126. (cond ((equal? pat dat) 'done)
  127. ((var-obj? pat) (assign-if-consistent pat dat))
  128. ((and (pair? pat) (pair? dat))
  129. (pattern-match (car pat) (car dat))
  130. (pattern-match (cdr pat) (cdr dat)))
  131. (else (amb)))) ;abort
  132. (define (assign-if-consistent var dat)
  133. (if (has-value? var)
  134. (pattern-match (bound-value var) dat)
  135. (set-value! var dat)))
  136. ```
  137. Then let's test!
  138. ```scheme
  139. ;;; Query input:
  140. (job ?p ?j)
  141. ;;; Query results:
  142. (job (aull dewitt) (administration secretary))
  143. ;;; Query input:
  144. next-result
  145. ;;; Query results:
  146. (job (cratchet robert) (accounting scrivener))
  147. ;;; Query input:
  148. (address ?p ?a)
  149. ;;; Query results:
  150. (address (aull dewitt) (slumerville (onion square) 5))
  151. ;;; Query input:
  152. next-result
  153. ;;; Query results:
  154. (address (cratchet robert) (allston (n harvard street) 16))
  155. ```
  156. Works well.
  157. But it seems awkward a little bit to make `instantiate` handle unbound variable;
  158. since now all the previously "unbound" variable bound to the "unassigned." So
  159. now our `instantiate` procedure do not work as much as before.
  160. Actually we can integrate `instantiate` with `instantiate-value`:
  161. ```scheme
  162. (define (instantiate exp env unbound-var-handler unassigned-var-handler)
  163. (define (copy exp)
  164. (cond ((var? exp)
  165. (let ((binding (binding-in-env exp env)))
  166. (if binding
  167. (copy (binding-value binding))
  168. (unbound-var-handler exp env))))
  169. ((var-obj? exp) ;debugging!
  170. (if (has-value? exp)
  171. (copy (bound-value exp))
  172. (unassigned-var-handler exp env)))
  173. ((pair? exp)
  174. (cons (copy (car exp)) (copy (cdr exp))))
  175. (else exp)))
  176. (copy exp))
  177. ```
  178. - Extend to the rest method of compound expression
  179. Here we are going to implement method of combination. Those expression is
  180. evaluated with respect to given environment.
  181. - **`andthen`:** Evaluate given conjuncts in order. Return symbol `done` as we did
  182. in the `simple-query`.
  183. - **`or`:** Evaluate one of disjuncts.
  184. - **`not!`:** Evaluate the negated query.
  185. - **`unique`:** Evaluate the given query and if the result is unique, returns
  186. symbol `done` else aborts.
  187. The rest depend on the `unify-frames`, which in turn depends on `unify-match` or
  188. using the `callback` structure in [exercise 4.77](#orgdfa92cf); that we are going to implement
  189. in following step.
  190. Then the code:
  191. ```scheme
  192. ;;;Compound queries
  193. (define (conjoin-in-order conjuncts env)
  194. (if (empty-conjunction? conjuncts)
  195. 'done
  196. (conjoin-in-order (rest-conjuncts conjuncts)
  197. (qeval (first-conjunct conjuncts)
  198. env))))
  199. (define (disjoin disjuncts env)
  200. (if (empty-disjunction? disjuncts)
  201. (amb)
  202. (ramb
  203. (qeval (first-disjunct disjuncts) env)
  204. (disjoin (rest-disjuncts disjuncts)
  205. env))))
  206. (define (negate! operands env)
  207. (filter-failed
  208. (let ((succeed? false))
  209. (if-fail
  210. (begin (qeval (negated-query operands)
  211. env)
  212. (permanent-set! succeed? true))
  213. 'ignore)
  214. (if succeed?
  215. 'failed
  216. 'done))))
  217. (define (uniquely-asserted operand env)
  218. (let ((output-frames '()))
  219. (if-fail (let ((f (qeval (unique-query operand)
  220. env)))
  221. (permanent-set! output-frames
  222. (cons f output-frames))
  223. (amb))
  224. 'ignore)
  225. (if (and (not (null? output-frames))
  226. (null? (cdr output-frames)))
  227. 'done
  228. (amb))))
  229. ```
  230. Then test:
  231. ```scheme
  232. ;;; Query input:
  233. (andthen (supervisor ?middle-manager ?person)
  234. (supervisor ?x ?middle-manager))
  235. ;;; Query results:
  236. (andthen (supervisor (scrooge eben) (warbucks oliver)) (supervisor (cratchet robert) (scrooge eben)))
  237. ;;; Query input:
  238. next-result
  239. ;;; Query results:
  240. (andthen (supervisor (bitdiddle ben) (warbucks oliver)) (supervisor (tweakit lem e) (bitdiddle ben)))
  241. ;;; Query input:
  242. (unique (job ?x (computer wizard)))
  243. ;;; Query results:
  244. (unique (job ? (computer wizard)))
  245. ;;; Query input:
  246. next-result
  247. ;;; Query results:
  248. ;;; There are no more result of
  249. (unique (job ?x (computer wizard)))
  250. ;;; Query input:
  251. (unique (job ?x (computer programmer)))
  252. ;;; Query results:
  253. ;;; There are no more result of
  254. (unique (job ?x (computer programmer)))
  255. ;;; Query input:
  256. (not! (baseball ?x))
  257. ;;; Query results:
  258. (not! (baseball ?))
  259. ;;; Query input:
  260. (not! (job ?p (computer wizard)))
  261. ;;; Query results:
  262. ;;; There are no more result of
  263. (not! (job ?p (computer wizard)))
  264. ```
  265. Turns out we need to make use the subsequent evaluation result; we need to force
  266. the type constraint the dispatched process to return environment to support the
  267. process such as `unique`. This also would change the pattern matcher; it does
  268. not allowed to mutate the structure since subsequent mutation is undone by
  269. the `(amb)` in the `uniquely-asserted`.
  270. So rather than we enforce the type constraint to return the environment, let's
  271. use trick:
  272. ```scheme
  273. (define (uniquely-asserted operand env)
  274. (let ((output-envs '()))
  275. (if-fail (let ((e (qeval (unique-query operand)
  276. env)))
  277. (permanent-set! output-envs
  278. (cons e output-envs))
  279. (amb))
  280. 'ignore)
  281. (if (and (not (null? output-envs))
  282. (null? (cdr output-envs)))
  283. (qeval (unique-query operand)
  284. env)
  285. (amb))))
  286. ```
  287. Then:
  288. ```scheme
  289. ;;; Query input:
  290. (unique (job ?x (computer wizard)))
  291. ;;; Query results:
  292. (unique (job (bitdiddle ben) (computer wizard)))
  293. ;;; Query input:
  294. (unique (job ?x (computer programmer)))
  295. ;;; Query results:
  296. ;;; There are no more result of
  297. (unique (job ?x (computer programmer)))
  298. ```
  299. - Extend to the rule application
  300. So far, we implemented all the components that do not depend on the unifier.
  301. Here we are about to design & implement the unifier. But, before that, let we
  302. rethink what we done so far in conferencing with the procedural evaluator.
  303. We already noted that logic language evaluator also has many commonalities with
  304. the ordinary unidirectional evaluator; but without detailed comparison since at
  305. that point we aren't have any components to compare in logic language
  306. implemented by environment model.
  307. However we do now have the working evaluator program that do use the environment
  308. model; so I've thought this is the right moment to compare and which would
  309. direct further designing and coding of logic language evaluator.
  310. Firstly, `qeval` is like `eval` in meta circular evaluator but do extra work than
  311. that: Namely, it transform the given query expression into procedural
  312. expression it binds all the unbound variable expressions into unassigned
  313. variable objects. After that process, we can now treat the resulting expression
  314. as we did in `eval` dispatch on the expression type since now all the
  315. variable is bound.
  316. Secondly, `simple-query` is like `application` clause in meta circular
  317. evaluator; it fetches all the assertions and rules using given environment
  318. (operator part in procedural application) and then instantiate given query with
  319. respect to the given environment (operands part) and then try to apply. More
  320. specifically,
  321. - Applying assertions is just like applying the primitive procedure that mutate
  322. the given argument objects in ordinary evaluator.
  323. - Applying rules is more subtle than that of compound procedure. It should
  324. extend captured rule environment with respect to the given instantiated query
  325. (via unifier); process the body of rule with respect to this extended rule
  326. environment as we do in normal application of compound procedure.
  327. We have not yet considered the implementation details about the unifier but we
  328. know it should look similar with pattern matcher we have seen in frame version
  329. logic language.
  330. If we think the rule application in comparison with compound application, we
  331. would expect the unifier to bind the unbound variables in the rule conclusion
  332. with given query.
  333. But if we think with the view point that we are doing this implementation to
  334. avoid the name capture problem, so if we look the previous implementation for
  335. reference, we should have come to think we have chose the variable object as
  336. counterpart of rule application id in the previous one.
  337. With this point of view, they did not create any variable id to the non-rule
  338. application query since it is guaranteed to be unique already since all the
  339. variables in rule application process have different name via
  340. `rename-variables-in`.
  341. If we stick this one rather than the analogy with procedural language, we can
  342. divide the process of binding unbound variable with new variable object from the
  343. evaluation `qeval`.
  344. One way to accomplish this idea is using the preprocessing as we did in
  345. `analyze` in section 4.1.7 in text book, or more recently in
  346. `query-syntax-process`. That is to process the instantiation process
  347. extending the given environment with bindings where unbound variables bound to
  348. unassigned variable object after we process the input query using the
  349. `query-syntax-process`. For the rule application, as we did in frame
  350. implementation, before evaluating any contents of fetched rule we instantiate
  351. any of variable as variable object.
  352. This is exactly the same idea they used in implementation of query language in
  353. frame model: Give each variable unique identification. Here we accomplished this
  354. via *object* combination of message passing paradigm and assignment; they did
  355. via making each variable has unique name.
  356. We can go further than that. We can substitute all the variables even before
  357. evaluating any as preprocessing. But it would eliminate completely the need to
  358. use environment; we can implement this idea just sticking with frame model. This
  359. would leads to limitation we encountered in the frame model: We can not express
  360. block structured program (I'm not quite sure what we can do with the block
  361. structured program in logic language; we should comeback this topic later).
  362. So here we attempt to re-factor our implementation we did so far; decompose the
  363. instantiation process from the `qeval`. With this, we are able to treat the
  364. variable uniformly – there is no coexistence of `var` and `var-obj` in pattern
  365. matcher or unifier as it is in current `instantiate` procedure.
  366. Then the current implementation has one to one mapping with the previous
  367. implementation. That means we can re-use the previous procedures just by
  368. rewriting those along with the relation – `var?` became `var-obj?` and `extend`
  369. became `set-value!` etc.:
  370. ```scheme
  371. (define (rule-env rule)
  372. empty-environment) ;just for now
  373. ;;;Simple queries
  374. ;; Query, Env -> 'done
  375. (define (simple-query query-pattern env)
  376. (let ((subed-pattern
  377. (instantiate
  378. query-pattern env
  379. (lambda (v e)
  380. 'ignore)
  381. (lambda (var env)
  382. var))))
  383. (amb (pattern-match
  384. subed-pattern
  385. (fetch-assertions
  386. query-pattern env))
  387. (apply-a-rule
  388. (fetch-rules
  389. query-pattern env)
  390. subed-pattern))))
  391. ;;;Rules and Unification
  392. (define (apply-a-rule rule query-pattern)
  393. (let ((instantiated-env
  394. (extend-if-unbound-variable rule (rule-env rule))))
  395. (let ((subed-rule
  396. (instantiate
  397. rule instantiated-env
  398. (lambda (v env)
  399. 'ignore)
  400. (lambda (var env)
  401. var))))
  402. (unify-match query-pattern
  403. (conclusion subed-rule))
  404. (qeval (rule-body rule)
  405. instantiated-env))))
  406. (define (unify-match p1 p2)
  407. (cond ((eq? p1 p2) 'done) ;we can not use equal since we use object!
  408. ((var-obj? p1) (assign-if-possible p1 p2))
  409. ((var-obj? p2) (assign-if-possible p2 p1)) ; {\em ; ***}
  410. ((and (pair? p1) (pair? p2))
  411. (unify-match (car p1) (car p2))
  412. (unify-match (cdr p1) (cdr p2)))
  413. (else (amb))))
  414. (define (assign-if-possible var val)
  415. (cond ((has-value? var)
  416. (unify-match
  417. (bound-value var) val))
  418. ((var-obj? val) ; {\em ; ***}
  419. (if (has-value? val)
  420. (unify-match
  421. var (bound-value val))
  422. (set-value! var val)))
  423. ((depends-on? val var) ; {\em ; ***}
  424. (amb))
  425. (else (set-value! var val))))
  426. (define (depends-on? exp var)
  427. (define (tree-walk e)
  428. (cond ((var-obj? e)
  429. (if (eq? var e) ;same as above
  430. true
  431. (if (has-value? e)
  432. (tree-walk (bound-value e))
  433. false)))
  434. ((pair? e)
  435. (or (tree-walk (car e))
  436. (tree-walk (cdr e))))
  437. (else false)))
  438. (tree-walk exp))
  439. ```
  440. Now rule application works:
  441. ```scheme
  442. ;;; Query input:
  443. (outranked-by ?p (Bitdiddle Ben))
  444. ;;; Query results:
  445. (outranked-by (tweakit lem e) (bitdiddle ben))
  446. ;;; Query input:
  447. next-result
  448. ;;; Query results:
  449. (outranked-by (fect cy d) (bitdiddle ben))
  450. ;;; Query input:
  451. ```
  452. Actually as we transit into this decomposing with above `simple-query`, faced
  453. some bug which led to modify `unify-match` to use `eq?` instead `equal?` since
  454. we have used object (same in `depends-on?`).
  455. And all the other complex rule application works well:
  456. ```scheme
  457. ;;; Query input:
  458. (append-to-form ?x ?y ((3 4) 2 (1 2) 3))
  459. ;;; Query results:
  460. (append-to-form ((3 4) 2 (1 2) 3) () ((3 4) 2 (1 2) 3))
  461. ;;; Query input:
  462. next-result
  463. ;;; Query results:
  464. (append-to-form ((3 4) 2 (1 2)) (3) ((3 4) 2 (1 2) 3))
  465. ;;; Query input:
  466. (reverse (1 2 (3 4) (3 1) 5) ?x)
  467. ;;; Query results:
  468. (reverse (1 2 (3 4) (3 1) 5) (5 (3 1) (3 4) 2 1))
  469. ```
  470. - `Last-pair` problem
  471. For the one having curiosity of `last-pair` response we've mentioned above, in
  472. current query language implemented using `amb` evaluator, as noted before in
  473. exercise [4.78](#orge3601cd), we can not produce the output of `(last-pair ?x (2))`:
  474. ```scheme
  475. ;;; Query input:
  476. (last-pair (1 2 3 4) ?x)
  477. ;;; Query results:
  478. (last-pair (1 2 3 4) (4))
  479. ;;; Query input:
  480. (last-pair ?x (2))
  481. ;;; Query results:
  482. C-c C-c;Quit!
  483. ```
  484. This is not due to the environment implementation but `amb` evaluator it can
  485. not produce breadth first search process. So to work around this, as we did in
  486. `disjoin`, we have to use `ramb` to avoid infinite loop:
  487. ```scheme
  488. (define (a-random-of items)
  489. (require (not (null? items)))
  490. (ramb (car items) (a-random-of (cdr items))))
  491. (define (fetch-rules pattern env)
  492. (a-random-of
  493. (if (use-index? pattern)
  494. (get-indexed-rules pattern)
  495. (get-all-rules))))
  496. ```
  497. Armed with above procedure,
  498. ```scheme
  499. ;;; Query input:
  500. (last-pair ?x (1))
  501. ;;; Query results:
  502. (last-pair (? 1) (1))
  503. ;;; Query input:
  504. next-result
  505. ;;; Query results:
  506. (last-pair (? ? ? ? ? 1) (1))
  507. ```
  508. (We may need to come up with more clever notation for unassigned variable rather
  509. than current one to express the constraint e.g. which variables should bound
  510. to same value.)
  511. - Support counterpart of `unify-frames` and callback
  512. We actually already support this feature in our implementation; `andthen` is
  513. also the kind of `and` that used `unify-frames` in previous implementation.
  514. Since we decomposed the instantiation process from the `qeval` and we are now
  515. using assignment rather than using immutable data structure, our `andthen`
  516. processes what `unfiy-frames` did in a more concise way.
  517. We could make another `and` special form that do not imposing the evaluation
  518. order as `andthen` did using `ramb`; but I've found no use of that in our case,
  519. so let it go as it is.
  520. For the `callback` feature we made in the [previous exercise](#orgdfa92cf) to frame structure,
  521. we need to transit into more constraint arithmetic language flavored
  522. implementation; that is, to make our callback process as object as it did in
  523. constraint object – adder, multiplier, etc.
  524. Then we have to make change the current frame structure: Frame does not have to
  525. keep track of the callback objects; variable object would do that directly. To
  526. implement this idea, we have to change current variable object to have callback
  527. objects as state variable, which would be informed one by one whenever the given
  528. variable object assigned a (constant) value.
  529. To integrate this feature with current implementation, as current one allow to
  530. variable object to have another variable object as its value, we need to
  531. propagate the callback list from entry variable object to the value variable
  532. object whenever it has value; of course if the value is constant, just activates
  533. the callback list.
  534. This is exactly what `has-constant?` did in previous implementation – this is
  535. the way of current version of callback system.
  536. This feature support is interesting in its own; but let us move on to the next
  537. part of implementation since that is more interesting one and this one can be
  538. done later if we found it is the really valuable one. For now I'm not quite
  539. convinced by the usefulness of this feature.
  540. So for now we can implement the counterpart of `lisp-value` as before where we
  541. haven't used callback structure; but naming it as `list-value!` in case where we
  542. found to want to add callback structure in our language:
  543. ```scheme
  544. (define (lisp-value! call env)
  545. (if (execute
  546. (instantiate
  547. call
  548. frame
  549. (lambda (v e)
  550. 'ignore)
  551. (lambda (v e)
  552. (error "Unknown pat var -- LISP-VALUE" v))))
  553. 'done
  554. (amb)))
  555. ```
  556. Then:
  557. ```scheme
  558. ;;; Query input:
  559. (lisp-value! > 5 4)
  560. ;;; Query results:
  561. (lisp-value! > 5 4)
  562. ```
  563. Actually does not work:
  564. ```scheme
  565. ;;; Query input:
  566. (lisp-value! > 5 7)
  567. ;;; Query results:
  568. (lisp-value! > 5 7)
  569. ```
  570. It just blindly evaluate the consequent clause. After debugging, we found it
  571. really just evaluate the conseqence part in any case:
  572. ```scheme
  573. (define (lisp-value! call env)
  574. (if (execute
  575. (instantiate
  576. call
  577. env
  578. (lambda (v e)
  579. 'ignore)
  580. (lambda (v e)
  581. (error "Unknown pat var -- LISP-VALUE" v))))
  582. (begin (newline)
  583. (display "conseq processed") 'done)
  584. (begin (newline)
  585. (display "altern processed") (amb))))
  586. ;;; Query input:
  587. (lisp-value! > 2 7)
  588. ;;; Query results:
  589. conseq processed(lisp-value! > 2 7)
  590. ```
  591. This is due to the way of processing of `execute`:
  592. ```scheme
  593. ;;; Amb-Eval input:
  594. (if (execute
  595. (instantiate
  596. '(> 5 7) empty-environment
  597. (lambda (v e) 'ignore) (lambda (v e) 'ignore)))
  598. 'conseq 'alter)
  599. ;;; Starting a new problem
  600. ;;; Amb-Eval value:
  601. conseq
  602. ```
  603. We need to make `execute` to evaluate its argument expressen before strip off
  604. the quotation or in other word, all we need to do is double evaluation:
  605. ```scheme
  606. (define (analyze-execute exp)
  607. (lambda (env succeed fail)
  608. ((analyze (execute-expression exp))
  609. env
  610. (lambda (exp2 fail2)
  611. ((analyze exp2) env
  612. succeed fail2))
  613. fail)))
  614. ```
  615. Now,
  616. ```scheme
  617. ;;; Amb-Eval input:
  618. (if (execute
  619. (instantiate
  620. '(> 5 7) empty-environment
  621. (lambda (v e) 'ignore) (lambda (v e) 'ignore)))
  622. 'conseq 'alter)
  623. ;;; Starting a new problem
  624. ;;; Amb-Eval value:
  625. alter
  626. ;;; Query input:
  627. (lisp-value! > 5 7)
  628. ;;; Query results:
  629. ;;; There are no more result of
  630. (lisp-value! > 5 7)
  631. ```
  632. More complex one also works:
  633. ```scheme
  634. ;;; Query input:
  635. (andthen (can-be-replaced-by ?p1 ?p2)
  636. (salary ?p1 ?a1) (salary ?p2 ?a2)
  637. (lisp-value! > ?a1 ?a2))
  638. ;;; Query results:
  639. (andthen (can-be-replaced-by (warbucks oliver) (aull dewitt))
  640. (salary (warbucks oliver) 150000) (salary (aull dewitt) 25000)
  641. (lisp-value! > 150000 25000))
  642. ;;; Query input:
  643. next-result
  644. ;;; Query results:
  645. (andthen (can-be-replaced-by (hacker alyssa p) (fect cy d))
  646. (salary (hacker alyssa p) 40000) (salary (fect cy d) 35000)
  647. (lisp-value! > 40000 35000))
  648. ;;; Query input:
  649. next-result
  650. ;;; Query results:
  651. ;;; There are no more result of
  652. (andthen (can-be-replaced-by ?p1 ?p2)
  653. (salary ?p1 ?a1) (salary ?p2 ?a2)
  654. (lisp-value! > ?a1 ?a2))
  655. ```
  656. - Extend the process of definition of rules
  657. Now we are done the half of the task what we planned to do. The rest is to
  658. transition from current "whole data base in one place" into "data base in
  659. context of environment."
  660. If we have done with this transition, we are able to capture the context where
  661. the rule is being defined. What would this change make us to do more useful
  662. works? I don't know well; even more I couldn't understand well what the logic
  663. language for neither in concrete sense nor abstract one while I've used it so
  664. far.
  665. To make use of this new feature capturing the environment where the rule
  666. definition evaluated we need to understand exactly what logic program is for,
  667. what can we do in current version, and what if we can make use the "capturing"
  668. in the context of preceding ones.
  669. So it's time to learn from lecture – Logic programming, [part 1](rCqMiPk1BJE) and [part 2](GReBwkGFZcs).
  670. It says,
  671. > People use logic program to express what is true, to check whether something is
  672. > true, and to find out what's true.
  673. Let's think about this phrase with concrete example. We have used (or
  674. programmed) logic language to define the recursive relation in `append-to-form`
  675. or `last-pair`. This is analogous definition for recursive definition of
  676. `append` and `last-pair` in Lisp.
  677. Here is working example, which define natural number in set theory:
  678. ```scheme
  679. (assert! (rule (Suc ?p (?p . ?p))))
  680. (assert! (rule (Add () ?n ?n)))
  681. (assert! (rule (Add ?m+1 ?n ?m+n+1)
  682. (andthen (Suc ?m ?m+1)
  683. (Suc ?m+n ?m+n+1)
  684. (Add ?m ?n ?m+n))))
  685. ```
  686. That is, if we define zero as `()` then the rest natural number defined along
  687. with `Suc`:
  688. ```scheme
  689. ;;; Query input:
  690. (Suc () ?one)
  691. ;;; Query results:
  692. (suc () (()))
  693. ;;; Query input:
  694. (Suc (()) ?two)
  695. ;;; Query results:
  696. (suc (()) ((()) ()))
  697. ;;; Query input:
  698. (Suc ((()) ()) ?three)
  699. ;;; Query results:
  700. (suc ((()) ()) (((()) ()) (()) ()))
  701. ...
  702. ```
  703. With this, we can add two set theory number:
  704. ```scheme
  705. ;;; Query input:
  706. (add ((()) ()) (((()) ()) (()) ()) ?five)
  707. ;;; Query results:
  708. (add ((()) ()) (((()) ()) (()) ()) (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ()))
  709. ```
  710. Since our query language define rules as relation not unidirectional
  711. computation, we can ask also the reverse question, that is, to ask what is five
  712. add of?
  713. ```scheme
  714. ;;; Query input:
  715. (add ?a1 ?a2
  716. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  717. )
  718. ;;; Query results:
  719. (add ()
  720. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  721. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  722. )
  723. ;;; Query input:
  724. next-result
  725. ;;; Query results:
  726. (add (())
  727. ((((()) ()) (()) ()) ((()) ()) (()) ())
  728. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  729. )
  730. ;;; Query input:
  731. next-result
  732. ;;; Query results:
  733. (add (((()) ()) (()) ())
  734. ((()) ())
  735. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  736. )
  737. ;;; Query input:
  738. next-result
  739. ;;; Query results:
  740. (add (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  741. ()
  742. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  743. )
  744. ;;; Query input:
  745. next-result
  746. ;;; Query results:
  747. (add ((((()) ()) (()) ()) ((()) ()) (()) ())
  748. (())
  749. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  750. )
  751. ;;; Query input:
  752. next-result
  753. ;;; Query results:
  754. (add ((()) ())
  755. (((()) ()) (()) ())
  756. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  757. )
  758. ;;; Query input:
  759. next-result
  760. ;;; Query results:
  761. ;;; There are no more result of
  762. (add ?a1 ?a2
  763. (((((()) ()) (()) ()) ((()) ()) (()) ()) (((()) ()) (()) ()) ((()) ()) (()) ())
  764. )
  765. ```
  766. So from this example, we can define or express what is true – natural number –
  767. and find our what is true – what is successor of given number, what number is
  768. the result of addition of given two number, and what numbers are composing the
  769. given number via addition – but we can not *prove* the following theorem:
  770. ```scheme
  771. (rule (add-assoc ?l ?m ?n)
  772. (andthen (Add ?l ?m ?l+m)
  773. (Add ?l+m ?n ?<l+m>+n)
  774. (Add ?m ?n ?m+n)
  775. (Add ?l ?m+n ?l+<m+n>)
  776. (same ?<l+m>+n ?l+<m+n>)))
  777. ;;; Query input:
  778. (add-assoc ?l ?m ?n)
  779. ;;; Query results:
  780. (add-assoc ((()) ()) ((((()) ()) (()) ()) ((()) ()) (()) ()) ?)
  781. ```
  782. We wanted to prove above association rule in addition using logic program but it
  783. only can find the instantiations that satisfies given rule. Let's attempt to
  784. resolve this unfortunate: Direct the proving method induction on `?l`
  785. ```scheme
  786. (rule (prove-add-assoc-base ?l ?m ?n)
  787. (andthen (same ?l ())
  788. (add-assoc ?l ?m ?n)))
  789. ;; or directly
  790. (rule (prove-add-assoc-base ?l ?m ?n)
  791. (andthen (same ?l ())
  792. (Add ?l ?m ?l+m)
  793. (Add ?l+m ?n ?<l+m>+n)
  794. (Add ?m ?n ?m+n)
  795. (Add ?l ?m+n ?l+<m+n>)
  796. (same ?<l+m>+n ?l+<m+n>)))
  797. ```
  798. This base case indifferently betray our hope:
  799. ```scheme
  800. ;;; Query input:
  801. (prove-add-assoc-base ?l ?m ?n)
  802. ;;; Query results:
  803. (prove-add-assoc-base () () ?)
  804. ;;; Query input:
  805. next-result
  806. ;;; Query results:
  807. (prove-add-assoc-base () ((((()) ()) (()) ()) ((()) ()) (()) ()) ?)
  808. ```
  809. What we expected was
  810. ```scheme
  811. ;;; Query input:
  812. (andthen (same () ?l) (Add ?l ?m ?l+m))
  813. ;;; Query results:
  814. (andthen (same () ()) (add () ? ?))
  815. ;;; Query input:
  816. next-result
  817. ;;; Query results:
  818. ;;; There are no more result of
  819. (andthen (same () ?l) (add ?l ?m ?l+m))
  820. ```
  821. Which implies for all `?m` and `?<l+m>` where `?l` is same as `()`, `?m` is same
  822. as `?l+m`:
  823. ```scheme
  824. ;;; Query input:
  825. (assert! (rule (prove-assoc-sub ?l ?m)
  826. (not! (andthen (same () ?l)
  827. (Add ?l ?m ?l+m)
  828. (not! (same ?m ?l+m))))))
  829. Assertion added to data base.
  830. ;;; Query input:
  831. (prove-assoc-sub ?l ?m)
  832. ;;; Query results:
  833. (prove-assoc-sub ? ?)
  834. ;;; Query input:
  835. next-result
  836. ;;; Query results:
  837. ;;; There are no more result of
  838. (prove-assoc-sub ?l ?m)
  839. ```
  840. Unfortunately we can not apply this same strategy to the previous one since it
  841. falls into infinite loop as we noted before:
  842. ```scheme
  843. ;;; Query input:
  844. (assert! (rule (prove-add-assoc-base2 ?l ?m ?n)
  845. (not! (andthen (same ?l ())
  846. (Add ?l ?m ?l+m)
  847. (Add ?l+m ?n ?<l+m>+n)
  848. (Add ?m ?n ?m+n)
  849. (Add ?l ?m+n ?l+<m+n>)
  850. (not! (same ?<l+m>+n ?l+<m+n>))))))
  851. Assertion added to data base.
  852. ;;; Query input:
  853. (prove-add-assoc-base2 ?l ?m ?n)
  854. ;;; Query results:
  855. C-c C-c;Quit!
  856. ```
  857. So consequently we can not use our query language to prove some theorem in
  858. itself; but it pose some possibilities one can use the idea behind this query
  859. language to prove some theorem whose correctness checked by the computer.
  860. That is, rather than use the multi-directional search method to find some fact
  861. in the data base, if we can use the definition of data in multi way to
  862. compute the output as ordinary programming language and also to prove some
  863. property concerning with that data structure using induction on that structure
  864. one can define data structure and use that and also prove the behavior of
  865. program that uses defined data structure exploiting the structural induction on
  866. that data structure.
  867. Defined data structure is used to aggregate given arguments into one concept and
  868. to prove the theorem about that data structure by decomposing aggregated concept
  869. to simpler ones.
  870. As conclusion, query language is for constructing *closed world* from where we
  871. deduce all the facts; it is not for proving some property using defined data
  872. base along with mathematical logic, it is for defining what canonical logic
  873. rule is and what we can deduce from constructed closed world.
  874. What I've expected from logic language was the framework where all the
  875. mathematical inference rules are inherited; but it was not. It is more
  876. programming language that has three components which makes the language powerful
  877. – primitive data, means of combination, means of abstraction. Rule definition
  878. is not more than the means of abstraction in query language; we should not
  879. expect any magic more than that.
  880. From this argument, we now can digest what the statement of this exercise meant.
  881. The block structured program is meant to prevent the program from being hard to
  882. be read, at the same time, not leaking the guts of the specific rule definition
  883. that would not be used anywhere other than outside the enclosing rule
  884. definition. And for the last question, it is unable to accomplish that in our
  885. query language but I've outlined the possibilities if we generalize what we
  886. learned from this language even if our query works in multi-directional way,
  887. our rule definitions only works in one way, from body to conclusion.
  888. Using those ideas we can achieve making deductions in a context since in that
  889. language, where even the means of abstraction works in multi way, we can use the
  890. assumption to compose desiring fact.
  891. - Change the Data Base structure
  892. Now we are going to implement the final part of our task. Our data base is going
  893. to have meaning only in the context of environment. First let's change the high
  894. level procedures to use the environment to fetch and aggregate the data base:
  895. ```scheme
  896. (define (fetch-assertions pattern env)
  897. (an-element-of
  898. (if (use-index? pattern)
  899. (get-indexed-assertions pattern env)
  900. (get-all-assertions env))))
  901. (define (get-all-assertions env) (assertions env))
  902. (define (get-indexed-assertions pattern env)
  903. (get-list (index-table env) (index-key-of pattern) 'assertion-list))
  904. (define (fetch-rules pattern env)
  905. (a-random-of
  906. (if (use-index? pattern)
  907. (get-indexed-rules pattern env)
  908. (get-all-rules env))))
  909. (define (get-all-rules env) (rules env))
  910. (define (get-indexed-rules pattern env)
  911. (let ((index-table (index-table env)))
  912. (append
  913. (get-list index-table (index-key-of pattern) 'rule-list)
  914. (get-list index-table '? 'rule-list))))
  915. (define (add-assertion! assertion env)
  916. (store-assertion-in-index assertion env)
  917. (add-to-assertions! assertion env)
  918. 'ok)
  919. (define (add-rule! rule env)
  920. (store-rule-in-index rule env)
  921. (add-to-rules! rule env)
  922. 'ok)
  923. (define (store-assertion-in-index assertion env)
  924. (if (indexable? assertion)
  925. (let ((key (index-key-of assertion))
  926. (index-table (index-table env)))
  927. (let ((current-assertion-list
  928. (get-list
  929. index-table key 'assertion-list)))
  930. (put
  931. index-table key 'assertion-list
  932. (cons assertion
  933. current-assertion-list))))))
  934. (define (store-rule-in-index rule env)
  935. (let ((pattern (conclusion rule)))
  936. (if (indexable? pattern)
  937. (let ((key (index-key-of pattern))
  938. (index-table (index-table env)))
  939. (let ((current-rule-list
  940. (get-list
  941. index-table key 'rule-list)))
  942. (put
  943. index-table key 'rule-list
  944. (cons rule
  945. current-rule-list)))))))
  946. ```
  947. From this specification, we need to alter the environment structure to have
  948. appropriate state variables. Specifically our environment have to support
  949. following procedures:
  950. - Selectors
  951. - **`(index-table <env>)`:** returns index table where we can retrieve the indexed
  952. rules and assertions with key.
  953. - **`(assertions <env>)`:** returns a list of all the assertions stored in given
  954. environment.
  955. - **`(rules <env>)`:** returns a list of all the rules stored in given environment.
  956. - Mutators
  957. - **`(add-to-asseritons! <assertion> <env>)`:** mutates the assertion list of
  958. given environment by adding the given assertion to that list.
  959. - **`(add-to-rules! <rule> <env>)`:** mutates the rule list of given environment
  960. by adding the given rule to that list.
  961. Here is the resulting codes:
  962. ```scheme
  963. ;;; interfacing the table ADT
  964. (define (put table key1 key2 item)
  965. ((table 'insert-proc!) key1 key2 item))
  966. (define (get table key1 key2)
  967. ((table 'lookup-proc) key1 key2))
  968. ;;; Environment ADT
  969. (define empty-environment '())
  970. (define (empty-env? env) (null? env))
  971. (define (extend-env frame env) (cons frame env))
  972. (define (first-frame env) (car env))
  973. (define (enclosing-env env) (cdr env))
  974. (define (index-table env) (table-frame (first-frame env)))
  975. (define (assertions env)
  976. ((assertions-frame (first-frame env)) 'assertions))
  977. (define (rules env)
  978. ((rules-frame (first-frame env)) 'rules))
  979. (define (add-to-assertions! assertion env)
  980. (((assertions-frame (first-frame env)) 'add-assertion!)
  981. assertion))
  982. (define (set-assertions! assertions env)
  983. (((assertions-frame (first-frame env)) 'set-assertions!)
  984. assertions))
  985. (define (add-to-rules! rule env)
  986. (((rules-frame (first-frame env)) 'add-rule!)
  987. rule))
  988. (define (set-rules! rules env)
  989. (((rules-frame (first-frame env)) 'set-rules!)
  990. rules))
  991. (define (binding-in-env var env)
  992. (if (empty-env? env)
  993. false
  994. (or (binding-in-frame var (first-frame env))
  995. (binding-in-env var (enclosing-env env)))))
  996. ;;;; Frame ADT
  997. (define (binding-in-frame variable frame)
  998. (assoc variable (bindings frame)))
  999. (define (extend variable value frame)
  1000. (make-frame (cons (make-binding variable value) (bindings frame))
  1001. (assertions-frame frame)
  1002. (rules-frame frame)
  1003. (table-frame frame)))
  1004. ;; Frame -> List<Binding>
  1005. (define (bindings frame)
  1006. (car frame))
  1007. (define (assertions-frame frame)
  1008. (cadr frame))
  1009. (define (rules-frame frame)
  1010. (caddr frame))
  1011. (define (table-frame frame)
  1012. (cadddr frame))
  1013. ;; List<Binding> -> Frame
  1014. (define (make-frame bindings assertions rules table)
  1015. (list bindings assertions rules table))
  1016. (define (empty-frame)
  1017. (make-frame empty-bindings (make-assertions) (make-rules) (make-table-amb)))
  1018. (define (empty-frame? frame)
  1019. (equal? frame (empty-frame)))
  1020. ;; Assertions ADT
  1021. (define (make-assertions)
  1022. (let ((assertions '()))
  1023. (lambda (m)
  1024. (cond ((eq? m 'assertions) assertions)
  1025. ((eq? m 'add-assertion!)
  1026. (lambda (a)
  1027. (set! assertions
  1028. (cons a assertions))))
  1029. ((eq? m 'set-assertions!)
  1030. (lambda (as)
  1031. (set! assertions as)))))))
  1032. (define (make-rules)
  1033. (let ((rules '()))
  1034. (lambda (m)
  1035. (cond ((eq? m 'rules) rules)
  1036. ((eq? m 'add-rule!)
  1037. (lambda (r)
  1038. (set! rules
  1039. (cons r rules))))
  1040. ((eq? m 'set-rules!)
  1041. (lambda (rs)
  1042. (set! rules rs)))))))
  1043. ;;;; binding ADT
  1044. (define (make-binding variable value)
  1045. (cons variable value))
  1046. (define empty-bindings '())
  1047. (define empty-bindings? null?)
  1048. (define (binding-variable binding)
  1049. (car binding))
  1050. (define (binding-value binding)
  1051. (cdr binding))
  1052. ;;;;Table support from Chapter 3, Section 3.3.3 (local tables)
  1053. ;;;;without using any of set-cdr or set-car version.
  1054. (define (make-table-amb)
  1055. (let ((local-table (make-local-table)))
  1056. (define (lookup key-1 key-2)
  1057. (let ((subtable (assoc key-1 (local-table 'get))))
  1058. (if subtable
  1059. (let ((record (assoc key-2 ((cdr subtable) 'get))))
  1060. (if record
  1061. ((cdr record) 'get)
  1062. false))
  1063. false)))
  1064. (define (insert! key-1 key-2 value)
  1065. (let ((subtable (assoc key-1 (local-table 'get))))
  1066. (if subtable
  1067. (let ((record (assoc key-2 ((cdr subtable) 'get))))
  1068. (if record
  1069. (((cdr record) 'set!) value)
  1070. (add-element!
  1071. (cdr subtable)
  1072. (cons key-2
  1073. (make-value-table value)))))
  1074. (add-element!
  1075. local-table
  1076. (cons key-1
  1077. (let ((sub-tbl (make-local-table)))
  1078. (add-element!
  1079. sub-tbl
  1080. (cons key-2
  1081. (make-value-table value))))))))
  1082. 'ok)
  1083. (define (dispatch m)
  1084. (cond ((eq? m 'lookup-proc) lookup)
  1085. ((eq? m 'insert-proc!) insert!)
  1086. (else (error "Unknown operation -- TABLE" m))))
  1087. dispatch))
  1088. (define (make-local-table)
  1089. (let ((lst '()))
  1090. (lambda (m)
  1091. (cond ((eq? m 'get) lst)
  1092. ((eq? m 'set!)
  1093. (lambda (new)
  1094. (set! lst new)))))))
  1095. (define (make-value-table value)
  1096. (let ((val-tbl (make-local-table)))
  1097. ((val-tbl 'set!) value)
  1098. val-tbl))
  1099. (define (add-element! table value)
  1100. ((table 'set!)
  1101. (cons value
  1102. (table 'get)))
  1103. table)
  1104. ```
  1105. Unfortunately this naive approach would not work since we only lookup the first
  1106. frame in the environment. We need to look through all the frame in the
  1107. environment:
  1108. ```scheme
  1109. (define (get-indexed-assertions pattern env)
  1110. (fold-right
  1111. (lambda (frame appended)
  1112. (append (get-list (table-frame frame) (index-key-of pattern) 'assertion-list)
  1113. appended))
  1114. '()
  1115. (env->frame-list env)))
  1116. (define (get-indexed-rules pattern env)
  1117. (append
  1118. (fold-right
  1119. (lambda (frame appended)
  1120. (append (get-list (table-frame frame) (index-key-of pattern) 'rule-list)
  1121. appended))
  1122. '()
  1123. (env->frame-list env))
  1124. (fold-right
  1125. (lambda (frame appended)
  1126. (append (get-list (table-frame frame) '? 'rule-list)
  1127. appended))
  1128. '()
  1129. (env->frame-list env))))
  1130. (define (assertions env)
  1131. (fold-right
  1132. (lambda (frame appended)
  1133. (append ((assertions-frame frame) 'assertions)
  1134. appended))
  1135. '() (env->frame-list env)))
  1136. (define (rules env)
  1137. (fold-right
  1138. (lambda (frame appended)
  1139. (append ((rules-frame frame) 'rules)
  1140. appended))))
  1141. (define (fold-right proc initial lst)
  1142. (if (null? lst)
  1143. initial
  1144. (proc
  1145. (car lst)
  1146. (fold-right proc initial (cdr lst)))))
  1147. ```
  1148. And our rule should capture the environment:
  1149. ```scheme
  1150. (define (make-captured-rule rule env)
  1151. (list rule env))
  1152. (define (rule-contents captured-rule)
  1153. (car captured-rule))
  1154. (define (rule-env captured-rule)
  1155. (cadr captured-rule))
  1156. (define (apply-a-rule rule query-pattern)
  1157. (let ((instantiated-env
  1158. (extend-if-unbound-variable (rule-contents rule) (rule-env rule)))
  1159. (rule (rule-contents rule)))
  1160. (let ((subed-rule
  1161. (instantiate
  1162. rule instantiated-env
  1163. (lambda (v env)
  1164. 'ignore)
  1165. (lambda (var env)
  1166. var))))
  1167. (unify-match query-pattern
  1168. (conclusion subed-rule))
  1169. (qeval (rule-body rule)
  1170. instantiated-env))))
  1171. (define (store-rule-in-index rule env)
  1172. (let ((pattern (conclusion rule)))
  1173. (if (indexable? pattern)
  1174. (let ((key (index-key-of pattern))
  1175. (index-table (index-table env)))
  1176. (let ((current-rule-list
  1177. (get-list
  1178. index-table key 'rule-list)))
  1179. (put
  1180. index-table key 'rule-list
  1181. (cons (make-captured-rule rule env)
  1182. current-rule-list)))))))
  1183. (define (add-to-rules! rule env)
  1184. (((rules-frame (first-frame env)) 'add-rule!)
  1185. (make-captured-rule rule env)))
  1186. ```
  1187. Then test:
  1188. ```scheme
  1189. ;;; Query input:
  1190. (outranked-by ?p1 ?p2)
  1191. ;;; Query results:
  1192. (outranked-by (cratchet robert) (warbucks oliver))
  1193. ;;; Query input:
  1194. (andthen (outranked-by ?p1 ?p2) (supervisor ?p2 (Bitdiddle Ben)))
  1195. ;;; Query results:
  1196. (andthen (outranked-by (reasoner louis) (hacker alyssa p)) (supervisor (hacker alyssa p) (bitdiddle ben)))
  1197. ```
  1198. Now we should be able to define rule that has block structure. First let's test
  1199. our new feature with following modification:
  1200. ```scheme
  1201. *** in initialize-data-base
  1202. (put-op 'assert! 'qeval add-rule-or-assertion!)
  1203. *** end of that procedure
  1204. (define (add-assertion-body exp)
  1205. (car exp))
  1206. (define (add-rule-or-assertion! exp env)
  1207. (let ((assertion (add-assertion-body exp)))
  1208. (if (rule? assertion)
  1209. (add-rule! assertion env)
  1210. (add-assertion! assertion env))))
  1211. ```
  1212. But this won't work unexpectedly:
  1213. ```scheme
  1214. ;;; Query input:
  1215. (assert! (test 5))
  1216. ;Aborting!: maximum recursion depth exceeded
  1217. ```
  1218. After some debugging process, we find out the following one is the source of bug:
  1219. ```scheme
  1220. ;;; Amb-Eval input:
  1221. (define x (extend-if-unbound-variable '(assert! (test 5)) query-initial-environment))
  1222. ;;; Starting a new problem
  1223. ;Aborting!: maximum recursion depth exceeded
  1224. ```
  1225. Let's start another debugging:
  1226. ```scheme
  1227. (define (extend-if-unbound-variable exp env)
  1228. ;; Pattern, Frame -> Frame
  1229. (define (extend-frame exp frame)
  1230. (newline)
  1231. (display "in the extend-frame")
  1232. (cond ((var? exp)
  1233. (let ((binding (binding-in-env exp env)))
  1234. (if (not binding)
  1235. (extend
  1236. exp (make-var-obj) frame)
  1237. frame)))
  1238. ((pair? exp)
  1239. (extend-frame (cdr exp)
  1240. (extend-frame (car exp) frame)))
  1241. (else frame)))
  1242. (let ((extended
  1243. (extend-frame exp (empty-frame))))
  1244. (newline)
  1245. (display "after execution of extend-frame")
  1246. (if (empty-frame? extended)
  1247. env
  1248. (extend-env extended env))))
  1249. ;;; Amb-Eval input:
  1250. (extend-if-unbound-variable '(assert! (test 5)) query-initial-environment)
  1251. ;;; Starting a new problem
  1252. in the extend-frame
  1253. in the extend-frame
  1254. in the extend-frame
  1255. in the extend-frame
  1256. in the extend-frame
  1257. in the extend-frame
  1258. in the extend-frame
  1259. in the extend-frame
  1260. in the extend-frame
  1261. after execution of extend-frame
  1262. ;Aborting!: maximum recursion depth exceeded
  1263. ```
  1264. Now we narrowed down to the execution of `empty-frame?`:
  1265. ```scheme
  1266. ;;; Amb-Eval input:
  1267. (empty-frame? (empty-frame))
  1268. ;;; Starting a new problem
  1269. ;Aborting!: maximum recursion depth exceeded
  1270. ```
  1271. The definition of `empty-frame?` was
  1272. ```scheme
  1273. (define (empty-frame? frame)
  1274. (equal? frame (empty-frame)))
  1275. ```
  1276. I've used `equal?` since I've looked up the manual after the unexpected result
  1277. from `unify-match` and `depends-on?` `equal?` ,more specifically, `eqv?` can
  1278. deal with procedure objects by inspecting the local state variables.
  1279. But I've not considered the depth it can search through. Since our
  1280. `make-table-amb` use nested procedure object even for the local variables
  1281. `equal?` fails due to the limitation on the recursion depth set.
  1282. So we need to our own definition not relying on `equal?`:
  1283. ```scheme
  1284. (define (empty-frame? frame)
  1285. (and (empty-bindings? (bindings frame))
  1286. (null? ((assertions-frame frame) 'assertions))
  1287. (null? ((rules-frame frame) 'rules))
  1288. ((table-frame frame) 'empty?)))
  1289. (define (make-table-amb)
  1290. (let ((local-table (make-local-table)))
  1291. (define (lookup key-1 key-2)
  1292. (let ((subtable (assoc key-1 (local-table 'get))))
  1293. (if subtable
  1294. (let ((record (assoc key-2 ((cdr subtable) 'get))))
  1295. (if record
  1296. ((cdr record) 'get)
  1297. false))
  1298. false)))
  1299. (define (insert! key-1 key-2 value)
  1300. (let ((subtable (assoc key-1 (local-table 'get))))
  1301. (if subtable
  1302. (let ((record (assoc key-2 ((cdr subtable) 'get))))
  1303. (if record
  1304. (((cdr record) 'set!) value)
  1305. (add-element!
  1306. (cdr subtable)
  1307. (cons key-2
  1308. (make-value-table value)))))
  1309. (add-element!
  1310. local-table
  1311. (cons key-1
  1312. (let ((sub-tbl (make-local-table)))
  1313. (add-element!
  1314. sub-tbl
  1315. (cons key-2
  1316. (make-value-table value))))))))
  1317. 'ok)
  1318. (define (dispatch m)
  1319. (cond ((eq? m 'lookup-proc) lookup)
  1320. ((eq? m 'insert-proc!) insert!)
  1321. ((eq? m 'empty?) (null? (local-table 'get)))
  1322. (else (error "Unknown operation -- TABLE" m))))
  1323. dispatch))
  1324. ```
  1325. Now we get what we wanted from our implementation:
  1326. ```scheme
  1327. ;;; Query input:
  1328. (assert! (test 5))
  1329. ;;; Query results:
  1330. (assert! (test 5))
  1331. ;;; Query input:
  1332. (test ?x)
  1333. ;;; Query results:
  1334. (test 5)
  1335. ```
  1336. The block structure can be achieved as
  1337. ```scheme
  1338. (assert! (rule (append-to-form ?x ?y ?z)
  1339. (andthen (assert! (rule (append-local () ?y ?y)))
  1340. (assert! (rule (append-local (?u . ?v) ?y (?u . ?z))
  1341. (append-local ?v ?y ?z)))
  1342. (append-local ?x ?y ?z))))
  1343. ```
  1344. But this won't work!
  1345. ```scheme
  1346. ;;; Query input:
  1347. (append-to-form ?x ?y (1 2 3 4))
  1348. ;;; Query results:
  1349. ;;; There are no more result of
  1350. (append-to-form ?x ?y (1 2 3 4))
  1351. ```
  1352. Since we process the unbound variable before evaluating (asserting) the rule
  1353. expression, it goes away as soon as it defined; never can be reached after
  1354. that.
  1355. It means our preprocessing wasn't appropriate one to do. We should revert that
  1356. change as before. Combined with treating `assert!` specially:
  1357. ```scheme
  1358. (define (qeval query env)
  1359. (if (assertion-to-be-added? query)
  1360. (add-rule-or-assertion! (add-assertion-body query))
  1361. (let ((qproc (get-op (type query) 'qeval))
  1362. (extended-env (extend-if-unbound-variable query env)))
  1363. (if qproc
  1364. (qproc (contents query) extended-env)
  1365. (simple-query query extended-env))
  1366. extended-env)))
  1367. ```
  1368. Also we need another procedure that is analogous to `extend-if-unbound-variable`
  1369. and the application of compound procedure.
  1370. Even I found the bug that lurks in the `extend-if-unbound-variable`, that is we
  1371. have to check whether the found variable already bound in current frame:
  1372. ```scheme
  1373. ;; Pattern, Env -> Env
  1374. (define (extend-if-unbound-variable exp env)
  1375. ;; Pattern, Frame -> Frame
  1376. (define (extend-frame exp frame)
  1377. (cond ((var? exp)
  1378. (let ((binding (binding-in-env exp env)))
  1379. (if (not (or (binding-in-frame exp frame)
  1380. (binding-in-env exp env)))
  1381. (extend
  1382. exp (make-var-obj) frame)
  1383. frame)))
  1384. ((pair? exp)
  1385. (extend-frame (cdr exp)
  1386. (extend-frame (car exp) frame)))
  1387. (else frame)))
  1388. (let ((extended
  1389. (extend-frame exp (empty-frame))))
  1390. (if (empty-frame? extended)
  1391. env
  1392. (extend-env extended env))))
  1393. (define (extend-if-variable exp env)
  1394. ;; Pattern, Frame -> Frame
  1395. (define (extend-frame exp frame)
  1396. (cond ((var? exp)
  1397. (let ((binding (binding-in-frame exp frame)))
  1398. (if (not binding)
  1399. (extend
  1400. exp (make-var-obj) frame)
  1401. frame)))
  1402. ((pair? exp)
  1403. (extend-frame (cdr exp)
  1404. (extend-frame (car exp) frame)))
  1405. (else frame)))
  1406. (let ((extended
  1407. (extend-frame exp (empty-frame))))
  1408. (if (empty-frame? extended)
  1409. env
  1410. (extend-env extended env))))
  1411. ```
  1412. Then
  1413. ```scheme
  1414. (define (apply-a-rule rule query-pattern)
  1415. (let ((instantiated-env
  1416. (extend-if-variable (rule-contents rule) (rule-env rule)))
  1417. (rule (rule-contents rule)))
  1418. (let ((subed-rule
  1419. (instantiate
  1420. rule instantiated-env
  1421. (lambda (v env)
  1422. 'ignore)
  1423. (lambda (var env)
  1424. var))))
  1425. (unify-match query-pattern
  1426. (conclusion subed-rule))
  1427. (qeval (rule-body rule)
  1428. instantiated-env))))
  1429. ```
  1430. Now it finally works:
  1431. ```scheme
  1432. ;;; Starting a new problem
  1433. ;;; Query input:
  1434. (assert! (rule (append-to-form ?x ?y ?z)
  1435. (andthen (assert! (rule (append-local () ?y ?y)))
  1436. (assert! (rule (append-local (?u . ?v) ?y (?u . ?z))
  1437. (append-local ?v ?y ?z)))
  1438. (append-local ?x ?y ?z))))
  1439. ;;; Query results:
  1440. (assert! (rule (append-to-form ignore ignore ignore) (andthen (assert! (rule (append-local () ignore ignore))) (assert! (rule (append-local (ignore . ignore) ignore (ignore . ignore)) (append-local ignore ignore ignore))) (append-local ignore ignore ignore))))
  1441. ;;; Query input:
  1442. (append-to-form ?x ?y (1 2 3 4))
  1443. ;;; Query results:
  1444. (append-to-form (1 2 3 4) () (1 2 3 4))
  1445. ;;; Query input:
  1446. next-result
  1447. ;;; Query results:
  1448. (append-to-form (1 2 3) (4) (1 2 3 4))
  1449. ;;; Query input:
  1450. next-result
  1451. ;;; Query results:
  1452. (append-to-form (1 2) (3 4) (1 2 3 4))
  1453. ;;; Query input:
  1454. next-result
  1455. ;;; Query results:
  1456. (append-to-form (1) (2 3 4) (1 2 3 4))
  1457. ;;; Query input:
  1458. next-result
  1459. ;;; Query results:
  1460. (append-to-form () (1 2 3 4) (1 2 3 4))
  1461. ;;; Query input:
  1462. next-result
  1463. ;;; Query results:
  1464. ;;; There are no more result of
  1465. (append-to-form ?x ?y (1 2 3 4))
  1466. ;;; Query input:
  1467. ```
  1468. We got block structure definition.
  1469. - Conclusion
  1470. Last word: Although in this query language we couldn't implement the general
  1471. idea, say "If *P* is true then we can deduce *A* and *B*," in predicate logic
  1472. sense but we could have told about the specific instance of *P*, that is whether
  1473. the *P* has no variable in it or we capture one of them with appropriate
  1474. assignment and we want to say *A* and *B* about that specific instance. For deal
  1475. with the general scheme, as I noted above, we need to make means of abstraction
  1476. also to be multi directional.

Chapter 5: Computing with Register Machines

So far we have learned the meanings of procedures using a succession of models
of evaluation: The substitution model, the environment model, the metacircular
evaluator. But none of these evaluation model did not dispel how the evaluation
of subexpression manages to return a value to the expression that uses this
value, nor does the evaluator explain how some recursive procedures generate
iterative processes.

In this chapter we are going to learn so called “linking” process, which link
the callee and caller with the returning value.

Further more, as it is the last chapter of this text book, this chapter
organized as the canonical structure in that we’ve learned so far: By wishful
thinking,

  1. Design or specify what we want to implement or the behavior that we want from
    the application – section 5.1;
  2. implement the high level procedures using the low level procedure’s
    specifications, by wishful thinking – we haven’t yet implemented the low
    level procedure but we can specify what we want from the perspective of high
    level procedure implementor –, section 5.2;
  3. then we implement the very low procedures using only the primitives –
    section 5.3;
  4. a variation or application of what we implemented – section 5.4;
  5. deep dive what we have assumed as primitive or “as given” – section 5.5;

So by learning through this chapter, we can learn not just the contents it
contains but also the general strategy to embody the ideas into the real world
program. This general strategy would be applied to any of design process one
would encounter with when try to create something that isn’t yet in the world
that solves the given specific kind of problem.

We’d better to be engraved in our mind the general strategy through out this chapter.

Designing Register Machines

  • Exercise 5.1

    I’ve drawn this with my digital paper. But you should be careful that our switch
    is not just simple switch, which we probably used in electrical experiment in
    middle or like that age, but a switch that has memorizing feature so that one
    can clobber the contents of given register.

    So we can use the result signal or value of operator – multiply, addition, etc.
    – to clobber the operand register directly without intervening the
    intermediate register.

    It is not deducible from the contents before this exercise, that is, from the
    gcd machine; but we can do from the later example machines. It is quite unfair
    they did not specify what feature the switches possesses exactly – only in a
    context that even not appear until this exercise.

A Language for Describing Register Machines

So far our language for the design of machines was diagrams – one for data
paths and one for controller; now through this chapter we extends our tools for
designing register machines to more powerful programming like language that we
can simulate easily by restricting the freedom of design – only the finite
number of instructions can be used in the designing.

  • Exercise 5.2

    1. (controller
    2. (assign p (const 1))
    3. (assign c (const 1))
    4. test-c
    5. (test (op >) (reg c) (reg n))
    6. (branch (label fact-done))
    7. (assign p (op *) (reg p) (reg c))
    8. (assign c (op +) (reg c) (const 1))
    9. (goto (label test-c))
    10. fact-done)

Abstraction in Machine Design

  • Exercise 5.3

    The first version that assume the internal definitions are available as
    primitive procedure in the sqrt-iter.

    1. (controller
    2. (assign g (const 1.0))
    3. test-g
    4. (test (op g-e?) (reg g))
    5. (branch (label sqrt-done))
    6. (assign g (op imp) (reg g))
    7. (goto (label test-g))
    8. sqrt-done)

    Note that we haven’t even mentioned the x in above controller description.

    Now we are going to implement the second task – unwind the internal definitions
    into the primitive ones.

    1. ;;; Version 2
    2. ;;; middle level procedures
    3. ;;;; good-enough?
    4. (assign s-q (op sq) (reg g))
    5. (assign d (op -) (reg s-q) (reg x))
    6. (assign ad (op abs) (reg d))
    7. (test (op <) (reg ad) (const 0.001))
    8. ;;;; improve
    9. (assign x/g (op /) (reg x) (reg g))
    10. (assign imped (op avg) (reg g) (reg x/g))
    11. ;;; low level procedures
    12. ;;;; square
    13. (assign cp (reg g))
    14. (assign squared (op *) (reg cp) (reg g))
    15. ;;;; abs
    16. (test (op <) (reg d) (const 0))
    17. (branch (label abs-fetch))
    18. (assign absed (op neg) (reg d))
    19. (goto (label abs-done))
    20. abs-fetch
    21. (assign absed (reg d))
    22. abs-done
    23. ;;;; average
    24. (assign s (op +) (reg x) (reg g))
    25. (assing aved (op /) (reg s) (const 2))
    26. ;;; And linking all together
    27. (controller
    28. (assign g (const 1.0))
    29. test-g
    30. ;;; good-enough? {
    31. ;; sq {
    32. (assign d (reg g))
    33. (assign t (op *) (reg d) (reg g))
    34. ;; }
    35. (assign d (op -) (reg t) (reg x))
    36. ;; abs {
    37. (test (op <) (reg d) (const 0))
    38. (branch (label abs-fetch))
    39. (assign t (op neg) (reg d))
    40. (goto (label abs-done))
    41. abs-fetch
    42. (assign t (reg d))
    43. abs-done
    44. ;; }
    45. (test (op <) (reg t) (const 0.001))
    46. ;;; }
    47. (branch (label sqrt-done))
    48. ;;; improve {
    49. (assign d (op /) (reg x) (reg g))
    50. ;; average {
    51. (assign t (op +) (reg x) (reg g))
    52. (assing g (op /) (reg t) (const 2))
    53. ;; }
    54. ;;; }
    55. (goto (label test-g))
    56. sqrt-done)

    For the data paths diagrams, I’ve drawn in digital paper.

Subroutines

Using a Stack to Implement Recursion

  • Exercise 5.4

    • a.

      1. (controller
      2. (assign continue (label expt-done))
      3. expt-loop
      4. (test (op =) (reg n) (const 0))
      5. (branch (label base-case))
      6. ;; setup to compute b^{n-1}
      7. (save continue)
      8. (assign continue (label after-expt-recur))
      9. (assign n (op -) (reg n) (const 1))
      10. (goto (label expt-loop))
      11. after-expt-recur
      12. (restore continue)
      13. (assign val (op *) (reg b) (reg val))
      14. (goto (reg continue))
      15. base-case
      16. (assign val (const 1))
      17. (goto (reg continue))
      18. expt-done)
    • b.

      1. (controller
      2. (assign c (reg n))
      3. (assign p (const 1))
      4. expt-loop
      5. (test (op =) (reg c) (const 0))
      6. (branch (label expt-done))
      7. (assign c (op -) (reg c) (const 1))
      8. (assign p (op *) (reg b) (reg p))
      9. (goto (label expt-loop))
      10. expt-done)
  • Exercise 5.5

    I’ve done this in our text book (with digital paper).

  • Exercise 5.6

    We can remove the pair before and after

    1. ;; set up to compute Fib(n - 2)
    2. (assign n (op -) (reg n) (const 2))

Instruction Summary

In this section, the specifications we need to implement are summarized.

A Register-Machine Simulator

Now we implement or learn what they implemented step by step for the
specifications of preceding design. Here they implement through by using wishful
thinking as we outlined in the beginning of this chapter.

  • Exercise 5.7

    1. (define expt-recur-machine
    2. (make-machine
    3. '(n b val)
    4. `((= ,=) (- ,-) (* ,*))
    5. '(
    6. (assign continue (label expt-done))
    7. expt-loop
    8. (test (op =) (reg n) (const 0))
    9. (branch (label base-case))
    10. ;; setup to compute b^{n-1}
    11. (save continue)
    12. (assign continue (label after-expt-recur))
    13. (assign n (op -) (reg n) (const 1))
    14. (goto (label expt-loop))
    15. after-expt-recur
    16. (restore continue)
    17. (assign val (op *) (reg b) (reg val))
    18. (goto (reg continue))
    19. base-case
    20. (assign val (const 1))
    21. (goto (reg continue))
    22. expt-done)))

    And

    1. (define expt-iter-machine
    2. (make-machine
    3. '(n b c p)
    4. `((= ,=) (- ,-) (* ,*))
    5. '(
    6. (assign c (reg n))
    7. (assign p (const 1))
    8. expt-loop
    9. (test (op =) (reg c) (const 0))
    10. (branch (label expt-done))
    11. (assign c (op -) (reg c) (const 1))
    12. (assign p (op *) (reg b) (reg p))
    13. (goto (label expt-loop))
    14. expt-done)))

    Then run:

    1. (set-register-contents! expt-recur-machine 'b 2)
    2. ;Value: done
    3. (set-register-contents! expt-recur-machine 'n 5)
    4. ;Value: done
    5. (start expt-recur-machine)
    6. ;Value: done
    7. (get-register-contents expt-recur-machine 'val)
    8. ;Value: 32

    And

    1. ;; Run expt-iter-machine
    2. (set-register-contents! expt-iter-machine 'b 2)
    3. (set-register-contents! expt-iter-machine 'n 5)
    4. (start expt-iter-machine)
    5. (get-register-contents expt-iter-machine 'p)
    6. ;Value: 32

The Machine Model

This subsection implements the middle-high level procedures.

The Assembler

The assembler is like the analyzer in section 4.1.7. It transforms the text
instructions (expressions) into execution procedures. The idea behind this
process is much of the works the simulator would do otherwise can be processed
without knowing the actual contents of machine registers. For example, they
replaced the references to registers by pointers to the register objects – as
we did in the last exercise in the previous chapter, and replace references to
labels by pointers to the place in the instruction sequence that the label
designates – like the variable bound to pair actually bound to pointer to that
pair.

  • Exercise 5.8

    Since we construct the label entries from the very end to the start point (using
    continuation), when we lookup-label with label here, the first label here
    in the controller text returned by the contract of assoc.

    So a would be 3 when it reaches there.

    To modify this behavior as specified in the statement, we need to construct ADT
    barrier for adding the newly constructed label entry to the given labels.

    Specifically it should lookup-label to check whether the given label-name is
    already in the labels; if it is, signal error.

    1. (define (add-label-entry entry labels)
    2. (if (get-label labels (label-name entry))
    3. (error "Given label name already exists in the labels" (list entry labels))
    4. (cons entry labels)))
    5. (define (label-name entry) (car entry))
    6. (define (get-label labels label-name)
    7. (let ((val (assoc label-name labels)))
    8. (if val
    9. (cdr val)
    10. false)))

    Then

    1. (define (extract-labels text receive)
    2. (if (null? text)
    3. (receive '() '())
    4. (extract-labels (cdr text)
    5. (lambda (insts labels)
    6. (let ((next-inst (car text)))
    7. (if (symbol? next-inst)
    8. (receive insts (add-label-entry
    9. (make-label-entry next-inst
    10. insts)
    11. labels))
    12. (receive (cons (make-instruction next-inst)
    13. insts)
    14. labels)))))))

    And we can test this feature:

    1. (define test-5.8-machine
    2. (make-machine
    3. '(a)
    4. '()
    5. '(start
    6. (goto (label here))
    7. here
    8. (assign a (const 3))
    9. (goto (label there))
    10. here
    11. (assign a (const 4))
    12. (goto (label there))
    13. there)
    14. ))
    15. ;Given label name already exists in the labels ((here ((assign a (const 3))) ((goto (label there))) . #0=(((assign a (const 4))) ((goto (label there))))) ((here . #0#) (there)))
    16. ;To continue, call RESTART with an option number:
    17. ; (RESTART 1) => Return to read-eval-print level 1.

Generating Execution Procedures for Instructions

Assemble procedure uses make-execution-procedure. This is has many analogy
with analyze procedure in section 4.1.7; these uses dispatch on data type, and
produce execution procedure that is analyzed using informations other than
actual contents.

  • Exercise 5.9

    We can use either of following strategies:

    • Define the procedure analogous to make-primitive-exp; or
    • filter the label expression before calling the make-primitive-exp.

    Here we are going to use the latter since it elicit what we meant more
    specifically than the former.

    1. (define (make-operation-exp exp machine labels operations)
    2. (let ((op (lookup-prim (operation-exp-op exp) operations))
    3. (aprocs
    4. (map (lambda (e)
    5. (if (label-exp? e)
    6. (error "Operation can not operate on label expression" e))
    7. (make-primitive-exp e machine labels))
    8. (operation-exp-operands exp))))
    9. (lambda ()
    10. (apply op (map (lambda (p) (p)) aprocs)))))
  • Exercise 5.10

    We can do that other than the instructions should have type notation in its
    car part since make-execution-procedure depends on this fact directly. For the
    rest we are good to modify whatever we like since there are no restriction at all.

    It means we should fence around dispatch on data type in the make-execution-procedure.

    But for now let we stick what granted for us.

    What we requested is not to create new syntax that has new semantic per se but
    change one of the existing syntax to new one. We could change the register
    syntax as post-fix notation like (n reg) by modifying as

    1. (define (register-exp? exp)
    2. (and (pair? exp)
    3. (pair? (cdr exp))
    4. (eq? (cadr exp) 'reg)))
    5. (define (register-exp-reg exp) (car exp))

    Then it works as expected:

    1. (define expt-iter-machine
    2. (make-machine
    3. '(n b c p)
    4. `((= ,=) (- ,-) (* ,*))
    5. '(
    6. (assign c (n reg))
    7. (assign p (const 1))
    8. expt-loop
    9. (test (op =) (c reg) (const 0))
    10. (branch (label expt-done))
    11. (assign c (op -) (c reg) (const 1))
    12. (assign p (op *) (b reg) (p reg))
    13. (goto (label expt-loop))
    14. expt-done)))
    15. ;Value: expt-iter-machine
    16. (set-register-contents! expt-iter-machine 'b 2)
    17. ;Value: done
    18. (set-register-contents! expt-iter-machine 'n 5)
    19. ;Value: done
    20. (start expt-iter-machine)
    21. ;Value: done
    22. (get-register-contents expt-iter-machine 'p)
    23. ;Value: 32
  • Exercise 5.11

    First let us code the test machine:

    1. (define test-5.11-machine
    2. (make-machine
    3. '(x y)
    4. '()
    5. '((save y)
    6. (save x)
    7. (restore y))))
    • a.

      We can reduce the following two lines

      1. *** in Fibonacci controller
      2. (assign n (reg val)) ;n now contains Fib(n - 2)
      3. (restore val) ;val now contains Fib(n - 1)

      into one line:

      1. (restore n) ;n now contains Fib(n - 1)
    • b.

      Now save has to construct new data structure that associate value with the
      name of register. For now let’s just use pair for to do this work:

      1. (define (make-save inst machine stack pc)
      2. (let ((reg-name (stack-inst-reg-name inst)))
      3. (let ((reg (get-register machine reg-name)))
      4. (lambda ()
      5. (push stack (cons reg-name (get-contents reg)))
      6. (advance-pc pc)))))
      7. (define (make-restore inst machine stack pc)
      8. (let* ((reg-name (stack-inst-reg-name inst))
      9. (reg (get-register machine reg-name)))
      10. (lambda ()
      11. (let* ((assoc-entry (pop stack))
      12. (assoc-name (car assoc-entry)))
      13. (if (eq? assoc-name reg-name)
      14. (set-contents! reg (pop stack))
      15. (error "Tried to restore value from register which is not one saved -- MAKE-RESTORE"))
      16. (advance-pc pc)))))

      And test:

      1. (set-register-contents! test-5.11-machine 'y 6)
      2. (set-register-contents! test-5.11-machine 'x 3)
      3. (start test-5.11-machine)
      4. ;Tried to restore value from register which is not one saved -- MAKE-RESTORE
      5. ;To continue, call RESTART with an option number:
      6. ; (RESTART 1) => Return to read-eval-print level 1.
    • c.

      Whenever allocate-register to machine it should also add new stack to the
      stacks – Alist<reg-name x stack>; then save & restore first lookup the
      specific stack associated with given register and do usual stack manipulations.

      To initialize, it sends initialize messages each of stack in stacks.

      1. (define (make-new-machine)
      2. (let ((pc (make-register 'pc))
      3. (flag (make-register 'flag))
      4. (stacks '())
      5. (the-instruction-sequence '()))
      6. (let ((the-ops
      7. (list (list 'initialize-stacks
      8. (lambda () (for-each (lambda (stack) (stack 'initialize))
      9. stacks)))
      10. ...
      11. ((eq? message 'stacks) stacks)
      12. ...
      13. ))))))
      14. (define (update-insts! insts labels machine)
      15. (let ((pc (get-register machine 'pc))
      16. (flag (get-register machine 'flag))
      17. (stacks (machine 'stacks))
      18. (ops (machine 'operations)))
      19. ...)
      20. (define (make-save inst machine stacks pc)
      21. (let* ((reg-name (stack-inst-reg-name inst))
      22. (reg (get-register machine reg-name)))
      23. (lambda ()
      24. (let ((stack (cadr (assoc reg-name stacks))))
      25. (push stack (get-contents reg)))
      26. (advance-pc pc))))
      27. (define (make-restore inst machine stacks pc)
      28. (let* ((reg-name (stack-inst-reg-name inst))
      29. (reg (get-register machine
      30. (stack-inst-reg-name inst))))
      31. (lambda ()
      32. (let ((stack (cadr (assoc reg-name stacks))))
      33. (set-contents! reg (pop stack)))
      34. (advance-pc pc))))

      Then test:

      1. (set-register-contents! test-5.11-machine 'y 6)
      2. (set-register-contents! test-5.11-machine 'x 3)
      3. (start test-5.11-machine)
      4. ;Value: done
      5. (get-register-contents test-5.11-machine 'y)
      6. ;Value: 6
      7. (get-register-contents test-5.11-machine 'x)
      8. ;Value: 3

      Works as expected.

  • Exercise 5.12

    • a list of all instructions without duplicates, sorted by instruction type;

      We need to implement constructing procedure that is analogous to
      merge-weighted or ordered. We could have chosen to sort the instructions
      in alphabetical order without bothered by the instruction type, but I felt it
      is more canonical to order the result in the order of dispatch on type in
      make-execution-procedure.

      Here is one possible solution:

      1. (define type-dict
      2. '((assign 1) (test 2) (branch 3) (goto 4)
      3. (save 5) (restore 6) (perform 7)))
      4. (define (adjoin-ordered precede? item ordered)
      5. (if (null? ordered)
      6. (list item)
      7. (let ((first (first ordered)))
      8. (cond ((precede? item first)
      9. (cons item ordered))
      10. ((precede? first item)
      11. (cons first
      12. (adjoin-ordered
      13. precede? item (cdr ordered))))
      14. (else
      15. ;; given item already in the ordered
      16. ordered)))))
      17. (define (inst-precede? inst1 inst2)
      18. (let ((type1 (car inst1))
      19. (type2 (car inst2)))
      20. (let ((mapped1 (cadr (assoc type1 type-dict)))
      21. (mapped2 (cadr (assoc type2 type-dict))))
      22. (cond ((< mapped1 mapped2) true)
      23. ((> mapped1 mapped2) false)
      24. (else
      25. ;; same number
      26. (not (equal? inst1 inst2)) ;just exclude the same one
      27. )))))
      28. ;; First request
      29. (define (assemble controller-text machine receive)
      30. (extract-labels controller-text
      31. (lambda (insts labels)
      32. (update-insts! insts labels machine)
      33. (receive
      34. insts
      35. (fold-right (lambda (inst ordered)
      36. (adjoin-ordered inst-precede? inst ordered))
      37. '()
      38. (map (lambda (inst) (instruction-text inst))
      39. insts))))))

      Here we used the continuation to hand over the updated instructions with the
      newly constructed a list of all instructions:

      1. (define (make-machine register-names ops controller-text)
      2. (let ((machine (make-new-machine)))
      3. (for-each (lambda (register-name)
      4. ((machine 'allocate-register) register-name))
      5. register-names)
      6. ((machine 'install-operations) ops)
      7. (assemble controller-text machine
      8. (lambda (instructions all-instructions)
      9. ((machine 'install-instruction-sequence)
      10. instructions)
      11. ((machine 'install-all-instructions)
      12. all-instructions)))
      13. machine))

      And modify the make-new-machine accordingly. Then test with Fibonacci
      machine:

      1. (pp (fib-machine 'all-instructions))
      2. ((assign continue (label fib-done))
      3. (assign continue (label afterfib-n-1))
      4. (assign n (op -) (reg n) (const 1))
      5. (assign n (op -) (reg n) (const 2))
      6. (assign continue (label afterfib-n-2))
      7. (assign n (reg val))
      8. (assign val (op +) (reg val) (reg n))
      9. (assign val (reg n))
      10. (test (op <) (reg n) (const 2))
      11. (branch (label immediate-answer))
      12. (goto (label fib-loop))
      13. (goto (reg continue))
      14. (save continue)
      15. (save n)
      16. (save continue)
      17. (save val)
      18. (restore n)
      19. (restore continue)
      20. (restore val)
      21. (restore continue))
      22. ;Unspecified return value

      Unfortunately, our implementation was wrong! the duplicates appear in the
      result. So we need to arrange in strict order:

      ```scheme
      (define (inst-precede? inst1 inst2)
      (let ((type1 (car inst1))

      1. (type2 (car inst2)))
      2. (let ((mapped1 (cadr (assoc type1 type-dict)))
      3. (mapped2 (cadr (assoc type2 type-dict))))
      4. (cond ((< mapped1 mapped2) true)
      5. ((> mapped1 mapped2) false)
      6. (else
      7. ;; same number
      8. ;; (not (equal? inst1 inst2)) ;just exclude the same one
      9. (symbol<? (hash-symbol-list (cdr inst1))
      10. (hash-symbol-list (cdr inst2)))
      11. )))))

      (define (hash-symbol-list slst)
      (fold-right
      (lambda (s appended)

      1. (symbol-append s appended))

      ‘||
      (flatten slst)))

      ;; Test hash-symbol-list
      ;; (hash-symbol-list ‘(a b (c d e) f g))
      ;; ;Value: abcdefg

  1. ;; Tree<A> -> List<A>
  2. (define (flatten tree)
  3. (tree-map list append '() tree))
  4. ;; Test flatten
  5. ;; (flatten '(1 (2 3) (4 (5 6) 7)))
  6. ;; ;Value: (1 2 3 4 5 6 7)
  7. ;; (Leaf<A> -> B), (B, B -> B), B, Tree<A>
  8. ;; -> B
  9. (define (tree-map leaf-op combine-op initial tree)
  10. (cond ((null? tree) initial)
  11. ((not (pair? tree)) (leaf-op tree))
  12. (else ;pair
  13. (combine-op
  14. (tree-map leaf-op combine-op initial
  15. (car tree))
  16. (tree-map leaf-op combine-op initial
  17. (cdr tree))))))
  18. ```
  19. For the reminding purpose, we implemented other high order procedure also.
  20. Then let's re-test
  21. ```scheme
  22. (pp (fib-machine 'all-instructions))
  23. ((assign continue (label afterfib-n-1))
  24. (assign continue (label afterfib-n-2))
  25. (assign continue (label fib-done))
  26. (assign n (op -) (reg n) (const 1))
  27. (assign n (op -) (reg n) (const 2))
  28. (assign n (reg val))
  29. (assign val (op +) (reg val) (reg n))
  30. (assign val (reg n))
  31. (test (op <) (reg n) (const 2))
  32. (branch (label immediate-answer))
  33. (goto (label fib-loop))
  34. (goto (reg continue))
  35. (save continue)
  36. (save n)
  37. (save val)
  38. (restore continue)
  39. (restore n)
  40. (restore val))
  41. ;Unspecified return value
  42. ```
  43. Now it returns what we expected.
  44. - a list (without duplicates) of the registers used to hold entry points (these
  45. are the registers referenced by `goto` instructions);
  46. To do this task, we reuse what the result from the previous task. Here is the
  47. strategy:
  48. 1. Filter the all instructions with `goto-exp?`;
  49. 2. using `map`, extract the `goto-dest` part;
  50. 3. filter that with `register-exp?`;
  51. 4. and lastly, extract the `register-exp-reg` part and that is what we wanted.
  52. Here is the code do this idea:
  53. ```scheme
  54. *** in assemble
  55. (let* ((all-insts
  56. (fold-right (lambda (inst ordered)
  57. (adjoin-ordered inst-precede? inst ordered))
  58. '()
  59. (map (lambda (inst) (instruction-text inst))
  60. insts)))
  61. (entry-regs
  62. (filter-map
  63. (lambda (goto-inst)
  64. (let ((dest (goto-dest goto-inst)))
  65. (and (register-exp? dest)
  66. (register-exp-reg dest))))
  67. (take-while
  68. (lambda (inst)
  69. (eq? (car inst) 'goto))
  70. (drop-while
  71. (lambda (inst)
  72. (not (eq? (car inst) 'goto)))
  73. all-insts)))))
  74. (accept
  75. insts
  76. all-insts
  77. entry-regs))
  78. ```
  79. We've integrate **2.** to **4.** part using `filter-map`; and for the first one,
  80. we used `take-while` and `drop-while` exploiting the fact that all
  81. instructions are sorted in types.
  82. Then test:
  83. ```scheme
  84. (pp (fib-machine 'registers-with-entry))
  85. (continue)
  86. ;Unspecified return value
  87. ```
  88. - a list (without duplicates) of the registers that are `save` d or `restore` d;
  89. This is analogous to the preceding one:
  90. ```scheme
  91. *** in let expression of assemble
  92. (stack-related-regs
  93. (map
  94. (lambda (inst)
  95. (stack-inst-reg-name inst))
  96. (take-while
  97. (lambda (inst)
  98. (or (eq? (car inst) 'save)
  99. (eq? (car inst) 'restore)))
  100. (drop-while
  101. (lambda (inst)
  102. (not (eq? (car inst) 'save)))
  103. all-insts))))
  104. ```
  105. Then test:
  106. ```scheme
  107. (pp (fib-machine 'stack-instruction-registers))
  108. (continue n val continue n val)
  109. ;Unspecified return value
  110. ```
  111. Huh! It's not what we expected. It is due to that we count one for the `save`
  112. and one for the `restore`, which usually end up with duplicates names.
  113. So let's fix it using the `adjoin-ordered`:
  114. ```scheme
  115. (stack-related-regs
  116. (fold-right
  117. (lambda (reg-name regs)
  118. (adjoin-ordered
  119. symbol<?
  120. reg-name
  121. regs))
  122. '()
  123. (map (lambda (inst)
  124. (stack-inst-reg-name inst))
  125. (take-while
  126. (lambda (inst)
  127. (or (eq? (car inst) 'save)
  128. (eq? (car inst) 'restore)))
  129. (drop-while
  130. (lambda (inst)
  131. (not (eq? (car inst) 'save)))
  132. all-insts)))))
  133. ```
  134. Then we got
  135. ```scheme
  136. (pp (fib-machine 'stack-instruction-registers))
  137. (continue n val)
  138. ;Unspecified return value
  139. ```
  140. - for each register, a list (without duplicates) of the sources from which it is
  141. assigned.
  142. Notice that the register source relations can be deduced directly from the
  143. `assign` expressions. So here we attempt to solve this task using the
  144. following strategy:
  145. 1. Assume we have table data structure that has key *values* entries. That is,
  146. if one insert key value pair into the given table, the value adjoined into
  147. the value list of given key.
  148. 2. Then filter out the all instructions into `assign` expressions, and then
  149. insert the `assign-reg-name` as key and the `assign-value-exp` as value
  150. into the above table.
  151. 3. The rest is interfacing this with the machine object; one can retrieve the
  152. sources of given register using the message, `sources-of`.
  153. Here is the code:
  154. ```scheme
  155. *** in assemble let expression
  156. (reg-sources-table
  157. (let ((tbl (make-multivalues-table)))
  158. (for-each
  159. (lambda (inst)
  160. ((tbl 'insert!)
  161. (assign-reg-name inst)
  162. (assign-value-exp inst)))
  163. (take-while
  164. (lambda (inst)
  165. (eq? (car inst) 'assign))
  166. all-insts))
  167. tbl))
  168. ```
  169. Then what assumed:
  170. ```scheme
  171. (define (make-multivalues-table)
  172. (let ((local-table '(*table*)))
  173. (define (lookup-vals key)
  174. (cond ((assoc key (cdr local-table)) => cdr)
  175. (else false)))
  176. (define (insert-value! key value)
  177. (let ((entry (assoc key (cdr local-table))))
  178. (if entry
  179. (set-cdr! entry
  180. (cons value (cdr entry)))
  181. (set-cdr! local-table
  182. (cons (cons key (list value))
  183. (cdr local-table))))))
  184. (lambda (m)
  185. (case m
  186. ((lookup) lookup-vals)
  187. ((insert!) insert-value!)
  188. (else
  189. (error "Unknown request -- MAKE-MULTIVALUES-TABLE" m))))))
  190. ;; Test for make-multivalues-table
  191. ;; (define x (make-multivalues-table))
  192. ;; ((x 'insert!) 'a 5)
  193. ;; ((x 'lookup) 'a)
  194. ;; ;; (5)
  195. ;; ((x 'insert!) 'a 2)
  196. ;; ((x 'lookup) 'a)
  197. ;; ;; (2 5)
  198. ;; ((x 'insert!) 'b 2)
  199. ;; ((x 'lookup) 'b)
  200. ;; ;; (2)
  201. ```
  202. Then test:
  203. ```scheme
  204. (pp ((fib-machine 'sources-of) 'n))
  205. (((reg val)) ((op -) (reg n) (const 2)) ((op -) (reg n) (const 1)))
  206. ;Unspecified return value
  207. ```
  • Exercise 5.13

    We can what we want by modifying get-register procedure, which called in the
    make-execution-procedure to reference the register object allocated in given
    machine.

    But it is not good idea to alter the existing get-register procedure since it
    may interface with other process also as its contract is not just for the
    make-execution-procedure.

    So it would be better to make procedure that is analogous to get-register but
    it allocates given register name whenever it has not been allocated in given
    machine. If given register name already allocated in given machine, then it
    should works like get-register.

    With this idea, we can do the task what we requested. After implementing this
    idea, we are good to discard the preallocating process in the make-machine.

    1. *** within make-new-machine
    2. (define (try-allocate-and-return-register name)
    3. (let ((val (assoc name register-table)))
    4. (if val
    5. (cadr val)
    6. (begin (allocate-register name)
    7. (lookup-register name)))))
    8. *** in the dispatch
    9. ((eq? message 'try-allocate-and-get-register)
    10. try-allocate-and-return-register)

    Then

    1. (define (make-sure-allocate-register-and-get machine reg-name)
    2. ((machine 'try-allocate-and-get-register) reg-name))

    And we should replace all the subprocesses of assemble to use
    make-sure-allocate-register-and-get instead of get-register:

    1. (define (make-assign inst machine labels operations pc)
    2. (let ((target
    3. (make-sure-allocate-register-and-get machine (assign-reg-name inst)))
    4. (value-exp (assign-value-exp inst)))
    5. (let ((value-proc
    6. (if (operation-exp? value-exp)
    7. (make-operation-exp
    8. value-exp machine labels operations)
    9. (make-primitive-exp
    10. (car value-exp) machine labels))))
    11. (lambda () ; execution procedure for assign
    12. (set-contents! target (value-proc))
    13. (advance-pc pc)))))
    14. (define (make-goto inst machine labels pc)
    15. (let ((dest (goto-dest inst)))
    16. (cond ((label-exp? dest)
    17. (let ((insts
    18. (lookup-label labels
    19. (label-exp-label dest))))
    20. (lambda () (set-contents! pc insts))))
    21. ((register-exp? dest)
    22. (let ((reg
    23. (make-sure-allocate-register-and-get machine
    24. (register-exp-reg dest))))
    25. (lambda ()
    26. (set-contents! pc (get-contents reg)))))
    27. (else (error "Bad GOTO instruction -- ASSEMBLE"
    28. inst)))))
    29. (define (make-save inst machine stack pc)
    30. (let ((reg (make-sure-allocate-register-and-get machine
    31. (stack-inst-reg-name inst))))
    32. (lambda ()
    33. (push stack (get-contents reg))
    34. (advance-pc pc))))
    35. (define (make-restore inst machine stack pc)
    36. (let ((reg (make-sure-allocate-register-and-get machine
    37. (stack-inst-reg-name inst))))
    38. (lambda ()
    39. (set-contents! reg (pop stack))
    40. (advance-pc pc))))
    41. (define (make-primitive-exp exp machine labels)
    42. (cond ((constant-exp? exp)
    43. (let ((c (constant-exp-value exp)))
    44. (lambda () c)))
    45. ((label-exp? exp)
    46. (let ((insts
    47. (lookup-label labels
    48. (label-exp-label exp))))
    49. (lambda () insts)))
    50. ((register-exp? exp)
    51. (let ((r (make-sure-allocate-register-and-get machine
    52. (register-exp-reg exp))))
    53. (lambda () (get-contents r))))
    54. (else
    55. (error "Unknown expression type -- ASSEMBLE" exp))))

    Now make-machine gets

    1. (define (make-machine ops controller-text)
    2. (let ((machine (make-new-machine)))
    3. ((machine 'install-operations) ops)
    4. (assemble controller-text machine
    5. (lambda (instructions
    6. all-instructions registers-with-entry
    7. stack-inst-regs reg-sources-table)
    8. ((machine 'install-instruction-sequence)
    9. instructions)
    10. ((machine 'install-all-instructions)
    11. all-instructions)
    12. ((machine 'install-registers-with-entry)
    13. registers-with-entry)
    14. ((machine 'install-stack-instruction-registers)
    15. stack-inst-regs)
    16. ((machine 'install-register-sources-table)
    17. reg-sources-table)))
    18. machine))

    And let’s test:

    1. (define fib-machine
    2. (make-machine
    3. `((< ,<) (- ,-) (+ ,+))
    4. '((assign continue (label fib-done))
    5. fib-loop
    6. (test (op <) (reg n) (const 2))
    7. (branch (label immediate-answer))
    8. ;; set up to compute Fib(n-1)
    9. (save continue)
    10. (assign continue (label afterfib-n-1))
    11. (save n) ; save old value of n
    12. (assign n (op -) (reg n) (const 1)) ; clobber n to n-1
    13. (goto (label fib-loop)) ; perform recursive call
    14. afterfib-n-1 ; upon return, val contains Fib(n-1)
    15. (restore n)
    16. (restore continue)
    17. ;; set up to compute Fib(n-2)
    18. (assign n (op -) (reg n) (const 2))
    19. (save continue)
    20. (assign continue (label afterfib-n-2))
    21. (save val) ; save Fib(n-1)
    22. (goto (label fib-loop))
    23. afterfib-n-2 ; upon return, val contains Fib(n-2)
    24. (assign n (reg val)) ; n now contains Fib(n-2)
    25. (restore val) ; val now contains Fib(n-1)
    26. (restore continue)
    27. (assign val ; Fib(n-1)+Fib(n-2)
    28. (op +) (reg val) (reg n))
    29. (goto (reg continue)) ; return to caller, answer is in val
    30. immediate-answer
    31. (assign val (reg n)) ; base case: Fib(n)=n
    32. (goto (reg continue))
    33. fib-done)))
    34. ;Value: fib-machine
    35. (set-register-contents! fib-machine 'n 4)
    36. ;Value: done
    37. (start fib-machine)
    38. ;Value: done
    39. (get-register-contents fib-machine 'val)
    40. ;Value: 3

    Works well.

Monitoring Machine Performance

  • Exercise 5.14

    1. (define fact-machine
    2. (make-machine
    3. `((= ,=) (- ,-) (* ,*))
    4. '(
    5. (perform (op initialize-stack))
    6. (assign continue (label fact-done)) ; set up final return address
    7. fact-loop
    8. (test (op =) (reg n) (const 1))
    9. (branch (label base-case))
    10. ;; Set up for the recursive call by saving n and continue.
    11. ;; Set up continue so that the computation will continue
    12. ;; at after-fact when the subroutine returns.
    13. (save continue)
    14. (save n)
    15. (assign n (op -) (reg n) (const 1))
    16. (assign continue (label after-fact))
    17. (goto (label fact-loop))
    18. after-fact
    19. (restore n)
    20. (restore continue)
    21. (assign val (op *) (reg n) (reg val)) ; val now contains n(n-1)!
    22. (goto (reg continue)) ; return to caller
    23. base-case
    24. (assign val (const 1)) ; base case: 1!=1
    25. (goto (reg continue)) ; return to caller
    26. fact-done
    27. (perform (op print-stack-statistics)))))

    Then here is the experiment cases:

    1. (set-register-contents! fact-machine 'n 10)
    2. ;Value: done
    3. (start fact-machine)
    4. (total-pushes = 18 maximum-depth = 18)
    5. ;Value: done
    6. (set-register-contents! fact-machine 'n 20)
    7. ;Value: done
    8. (start fact-machine)
    9. (total-pushes = 38 maximum-depth = 38)
    10. ;Value: done
    11. (set-register-contents! fact-machine 'n 2)
    12. ;Value: done
    13. (start fact-machine)
    14. (total-pushes = 2 maximum-depth = 2)
    15. ;Value: done

    From this data, we can deduce that total-pushes and maximum-depth are same
    and the general formula for this in terms of n is (T(n) = 2n-2).

    Or using read:

    1. (start fact-machine)
    2. 5
    3. (total-pushes = 8 maximum-depth = 8)
    4. ;Value: done
    5. (start fact-machine)
    6. 35
    7. (total-pushes = 68 maximum-depth = 68)
    8. ;Value: done
  • Exercise 5.15

    We need to modify the make-new-machine procedure to include number-execs to
    accommodate new feature specified in the statement.

    And define as internal definition of that procedure the following print method:

    1. (define (print-statistics)
    2. (newline)
    3. (display
    4. `(tatal-executions = ,number-execs)))

    And modify execute:

    1. (define (execute)
    2. (let ((insts (get-contents pc)))
    3. (if (null? insts)
    4. 'done
    5. (begin
    6. ((instruction-execution-proc (car insts)))
    7. (set! number-execs (1+ number-execs))
    8. (execute)))))

    And interface with the rest:

    1. *** in dispatch
    2. ((eq? message 'print-statistics) (print-statistics))
    3. ((eq? message 'initialize-statistics) (set! number-execs 0))

    Now we can use as follows:

    1. (set-register-contents! fib-machine 'n 10)
    2. ;Value: done
    3. (start fib-machine)
    4. ;Value: done
    5. (fib-machine 'print-statistics)
    6. (tatal-executions = 2029)
    7. ;Unspecified return value
    8. (fib-machine 'initialize-statistics)
    9. ;Value: 2029
    10. (set-register-contents! fib-machine 'n 20)
    11. ;Value: done
    12. (start fib-machine)
    13. ;Value: done
    14. (fib-machine 'print-statistics)
    15. (tatal-executions = 251740)
    16. ;Unspecified return value

    Exponential growth verified!

  • Exercise 5.16

    1. Make make-new-machine have trace? as state variable, which can be on and
      off by the message trace-on and trace-off.
    2. Then in the execute procedure, if trace? then it should print out the
      instruction text – by using the instruction-text selector.

    Then test

    1. (fib-machine 'trace-on)
    2. ;Value: #f
    3. (set-register-contents! fib-machine 'n 3)
    4. ;Value: done
    5. (start fib-machine)
    6. (assign continue (label fib-done))
    7. (test (op <) (reg n) (const 2))
    8. (branch (label immediate-answer))
    9. (save continue)
    10. (assign continue (label afterfib-n-1))
    11. (save n)
    12. (assign n (op -) (reg n) (const 1))
    13. (goto (label fib-loop))
    14. (test (op <) (reg n) (const 2))
    15. (branch (label immediate-answer))
    16. (save continue)
    17. (assign continue (label afterfib-n-1))
    18. (save n)
    19. (assign n (op -) (reg n) (const 1))
    20. (goto (label fib-loop))
    21. (test (op <) (reg n) (const 2))
    22. (branch (label immediate-answer))
    23. (assign val (reg n))
    24. (goto (reg continue))
    25. (restore n)
    26. (restore continue)
    27. (assign n (op -) (reg n) (const 2))
    28. (save continue)
    29. (assign continue (label afterfib-n-2))
    30. (save val)
    31. (goto (label fib-loop))
    32. (test (op <) (reg n) (const 2))
    33. (branch (label immediate-answer))
    34. (assign val (reg n))
    35. (goto (reg continue))
    36. (assign n (reg val))
    37. (restore val)
    38. (restore continue)
    39. (assign val (op +) (reg val) (reg n))
    40. (goto (reg continue))
    41. (restore n)
    42. (restore continue)
    43. (assign n (op -) (reg n) (const 2))
    44. (save continue)
    45. (assign continue (label afterfib-n-2))
    46. (save val)
    47. (goto (label fib-loop))
    48. (test (op <) (reg n) (const 2))
    49. (branch (label immediate-answer))
    50. (assign val (reg n))
    51. (goto (reg continue))
    52. (assign n (reg val))
    53. (restore val)
    54. (restore continue)
    55. (assign val (op +) (reg val) (reg n))
    56. (goto (reg continue))
    57. ;Value: done
  • Exercise 5.17

    Here we should think of what abstraction level we are going to modify. Since the
    tracing things is about in the make-new-machine procedure, we may need to the
    machine object to possess label information.

    But we do not want break the ADT about the label data, it would be better to
    offer lookup method with the label data from outside of machine object.

    And also construct additional ADT:

    1. ;;; ADT for labels package which going to be installed into machine object
    2. (define (make-labels-package find-from-key find-from-val labels)
    3. (list find-from-key find-from-val labels))
    4. (define (find-from-key-proc labels-pack)
    5. (car labels-pack))
    6. (define (find-from-val-proc labels-pack)
    7. (cadr labels-pack))
    8. (define (labels-data labels-pack)
    9. (caddr labels-pack))
    10. ;; operate on labels-package
    11. (define (find-from-val labels-pack)
    12. (lambda (val)
    13. ((find-from-val-proc labels-pack)
    14. val
    15. (labels-data labels-pack))))
    16. (define (find-from-key labels-pack)
    17. (lambda (key)
    18. ((find-from-key-proc labels-pack)
    19. key
    20. (labels-data labels-pack))))

    Then define the retriever:

    1. ;; selector of labels
    2. (define retrive-label-from-insts
    3. (association-procedure eq? cdr))

    The rest is interface with the others:

    1. *** in assemble
    2. (accept
    3. insts
    4. (make-labels-package
    5. get-label
    6. retrive-label-from-insts
    7. labels)
    8. all-insts
    9. entry-regs
    10. stack-related-regs
    11. reg-sources-table)
    12. *** in make-machine
    13. ((machine 'install-labels-package)
    14. labels-package)
    15. *** in execute in the make-new-machine
    16. (let ((inst (car insts)))
    17. (if trace?
    18. (let ((label-entry ((find-from-val labels-package)
    19. insts)))
    20. (if label-entry
    21. (begin (newline)
    22. (display (label-name label-entry))))
    23. (begin (newline)
    24. (display (instruction-text inst)))))
    25. ((instruction-execution-proc inst))
    26. (set! number-execs (1+ number-execs))
    27. (execute))

    Label-name and get-label are defined in exercise 5.8.

    Then test

    1. (start fib-machine)
    2. (assign continue (label fib-done))
    3. fib-loop
    4. (test (op <) (reg n) (const 2))
    5. (branch (label immediate-answer))
    6. (save continue)
    7. (assign continue (label afterfib-n-1))
    8. (save n)
    9. (assign n (op -) (reg n) (const 1))
    10. (goto (label fib-loop))
    11. fib-loop
    12. (test (op <) (reg n) (const 2))
    13. (branch (label immediate-answer))
    14. (save continue)
    15. (assign continue (label afterfib-n-1))
    16. (save n)
    17. (assign n (op -) (reg n) (const 1))
    18. (goto (label fib-loop))
    19. fib-loop
    20. (test (op <) (reg n) (const 2))
    21. (branch (label immediate-answer))
    22. immediate-answer
    23. (assign val (reg n))
    24. (goto (reg continue))
    25. afterfib-n-1
    26. (restore n)
    27. (restore continue)
    28. (assign n (op -) (reg n) (const 2))
    29. (save continue)
    30. (assign continue (label afterfib-n-2))
    31. (save val)
    32. (goto (label fib-loop))
    33. fib-loop
    34. (test (op <) (reg n) (const 2))
    35. (branch (label immediate-answer))
    36. immediate-answer
    37. (assign val (reg n))
    38. (goto (reg continue))
    39. afterfib-n-2
    40. (assign n (reg val))
    41. (restore val)
    42. (restore continue)
    43. (assign val (op +) (reg val) (reg n))
    44. (goto (reg continue))
    45. afterfib-n-1
    46. (restore n)
    47. (restore continue)
    48. (assign n (op -) (reg n) (const 2))
    49. (save continue)
    50. (assign continue (label afterfib-n-2))
    51. (save val)
    52. (goto (label fib-loop))
    53. fib-loop
    54. (test (op <) (reg n) (const 2))
    55. (branch (label immediate-answer))
    56. immediate-answer
    57. (assign val (reg n))
    58. (goto (reg continue))
    59. afterfib-n-2
    60. (assign n (reg val))
    61. (restore val)
    62. (restore continue)
    63. (assign val (op +) (reg val) (reg n))
    64. (goto (reg continue))
    65. ;Value: done
  • Exercise 5.18

    It is analogous to the previous one:

    1. (define (make-register name)
    2. (let ((contents '*unassigned*)
    3. (trace? #f))
    4. (define (dispatch message)
    5. (cond ((eq? message 'get) contents)
    6. ((eq? message 'set)
    7. (lambda (value)
    8. (if trace?
    9. (begin (newline)
    10. (display `(Register ,name gets ,value from ,contents))))
    11. (set! contents value)))
    12. ((eq? message 'trace-on)
    13. (set! trace? #t))
    14. ((eq? message 'trace-off)
    15. (set! trace? #f))
    16. (else
    17. (error "Unknown request -- REGISTER" message))))
    18. dispatch))

    Then unit test:

    1. (define x (make-register 'x))
    2. (x 'trace-on)
    3. ((x 'set) 5)
    4. (register x gets 5 from *unassigned*)
    5. ;Value: *unassigned*

    All the rest is to interface in the make-new-machine:

    1. *** in make-new-machine
    2. ((eq? message 'trace-on-register)
    3. (lambda (reg-name)
    4. ((lookup-register reg-name) 'trace-on)))
    5. ((eq? message 'trace-off-register)
    6. (lambda (reg-name)
    7. ((lookup-register reg-name) 'trace-off)))

    Then test:

    1. ((fib-machine 'trace-on-register) 'n)
    2. ;Value: #f
    3. (set-register-contents! fib-machine 'n 3)
    4. (register n gets 3 from *unassigned*)
    5. ;Value: done
    6. (start fib-machine)
    7. (register n gets 2 from 3)
    8. (register n gets 1 from 2)
    9. (register n gets 2 from 1)
    10. (register n gets 0 from 2)
    11. (register n gets 0 from 0)
    12. (register n gets 3 from 0)
    13. (register n gets 1 from 3)
    14. (register n gets 1 from 1)
    15. ;Value: done
  • Exercise 5.19

    This is a analogous feature to start procedure; but use label data. The
    specifications on the procedures that manipulate break points indicates we need
    to define new data structure that keeps track of all the break points.

    As we noted before, our machine now has the information about the label as its
    state variable, we can transform the specifications of where the break point
    should be into the actual instruction. So here we are going to store this
    transformed information as the break points data structure – so called internal
    data.

    For the break points data structure, we need to remove the specified break point
    from the structure; it means we should mutate the data structure. So we’d better
    to give the data structure identity. For this purpose, we’ll implement the break
    points as headed list to possess identity.

    And more, we should keep track of the current break point since we should
    support the proceed-machine procedure. We can exploit dummy head of the headed
    list possessing this additional information. Or

    Then here is the code

    1. *** as state variable of make-new-machine
    2. (break-points '(#f))
    3. ...
    4. *** internal defintions of make-new-machine
    5. (define (cdring-down lst n)
    6. (if (= n 1)
    7. lst
    8. (cdring-down (cdr lst)
    9. (-1+ n))))
    10. (define (add-break-point label n)
    11. (let ((break-point
    12. (cdring-down ((find-from-key labels-package) label)
    13. n)))
    14. (if (memq break-point (cdr break-points))
    15. (error "Given break point already in break points -- ADD-BREAK-POINT"
    16. (list label n))
    17. (set-cdr! break-points (cons break-point
    18. (cdr break-points))))))
    19. (define (remove-break-point label n)
    20. (let ((break-point
    21. (cdring-down ((find-from-key labels-package) label)
    22. n)))
    23. (let loop ((items break-points))
    24. (cond ((null? (cdr items))
    25. (error "Given break point not in break points -- REMOVE-BREAK-POINT"
    26. (list label n)))
    27. ((eq? (cadr items) break-point)
    28. (set-cdr! items (cddr items)))
    29. (else (loop (cdr items)))))))

    Then execute gets complicated than before:

    1. (define (execute)
    2. (let ((insts (get-contents pc)))
    3. (cond ((null? insts) 'done)
    4. ((car break-points) ;the right after instruction from the broken
    5. (set-car! break-points #f)
    6. (execute))
    7. ((and (not (eq? (car break-points) insts)) ;ensure this is not the point broken
    8. (memq insts (cdr break-points)))
    9. (set-car! break-points insts) ;save the broken point
    10. 'broken)
    11. (else
    12. (let ((inst (car insts)))
    13. (if trace?
    14. (let ((label-entry ((find-from-val labels-package)
    15. insts)))
    16. (if label-entry
    17. (begin (newline)
    18. (display (label-name label-entry))))
    19. (begin (newline)
    20. (display (instruction-text inst)))))
    21. ((instruction-execution-proc inst))
    22. (set! number-execs (1+ number-execs))
    23. (execute))))))

    And interfaces:

    1. *** in dispatch
    2. ((eq? message 'proceed-machine)
    3. (if (car break-points)
    4. (begin (set-contents! pc (car break-points))
    5. (execute))
    6. (error "There in no broken point to proceed from -- PROCEED-MACHINE")))
    7. ((eq? message 'set-breakpoint)
    8. add-break-point)
    9. ((eq? message 'cancel-breakpoint)
    10. remove-break-point)
    11. ((eq? message 'cancel-all-breakpoints)
    12. (set! break-points '(#f)))

    To procedural syntax:

    1. (define (set-breakpoint machine label n)
    2. ((machine 'set-breakpoint) label n))
    3. (define (cancel-breakpoint machine label n)
    4. ((machine 'cancel-breakpoint) label n))
    5. (define (proceed-machine machine)
    6. (machine 'proceed-machine))
    7. (define (cancel-all-breakpoints machine)
    8. (machine 'cancel-all-breakpoints))

    Then let’s test:

    1. (set-breakpoint gcd-machine 'test-b 4)
    2. ;The object test-b, passed as an argument to assoc, is not the correct type.
    3. ;To continue, call RESTART with an option number:
    4. ; (RESTART 1) => Return to read-eval-print level 1.

    It is due to that the find-from-val and find-from-key does not agree in
    where the labels should position. Let us make them consist along with
    loopup-label.

    Then re-run the test:

    1. (set-register-contents! gcd-machine 'a 202)
    2. ;Value: done
    3. (set-register-contents! gcd-machine 'b 43)
    4. ;Value: done
    5. (set-breakpoint gcd-machine 'test-b 4)
    6. ;Unspecified return value
    7. (gcd-machine 'trace-on)
    8. ;Value: #f
    9. (start gcd-machine)
    10. test-b
    11. (test (op =) (reg b) (const 0))
    12. (branch (label gcd-done))
    13. (assign t (op rem) (reg a) (reg b))
    14. ;Value: broken
    15. (proceed-machine gcd-machine)
    16. ;Value: broken

    Unfortunately our proceed-machine does not work as expected.

    We should have defined as follows

    1. (define (execute)
    2. (let ((insts (get-contents pc)))
    3. (cond ((null? insts) 'done)
    4. ((and (car break-points) ;the right after instruction from the broken
    5. (not (eq? (car break-points) insts)))
    6. (set-car! break-points #f)
    7. (execute))
    8. ((and (not (eq? (car break-points) insts)) ;ensure this is not the point broken
    9. (memq insts (cdr break-points)))
    10. (set-car! break-points insts) ;save the broken point
    11. 'broken)
    12. (else
    13. (let ((inst (car insts)))
    14. (if trace?
    15. (let ((label-entry ((find-from-val labels-package)
    16. insts)))
    17. (if label-entry
    18. (begin (newline)
    19. (display (label-name label-entry))))
    20. (begin (newline)
    21. (display (instruction-text inst)))))
    22. ((instruction-execution-proc inst))
    23. (set! number-execs (1+ number-execs))
    24. (execute))))))

    Now it works as expected:

    1. (start gcd-machine)
    2. test-b
    3. (test (op =) (reg b) (const 0))
    4. (branch (label gcd-done))
    5. (assign t (op rem) (reg a) (reg b))
    6. ;Value: broken
    7. (proceed-machine gcd-machine)
    8. (assign a (reg b))
    9. (assign b (reg t))
    10. (goto (label test-b))
    11. test-b
    12. (test (op =) (reg b) (const 0))
    13. (branch (label gcd-done))
    14. (assign t (op rem) (reg a) (reg b))
    15. ;Value: broken
    16. (cancel-breakpoint gcd-machine 'test-b 4)
    17. ;Unspecified return value
    18. (proceed-machine gcd-machine)
    19. (assign a (reg b))
    20. (assign b (reg t))
    21. (goto (label test-b))
    22. test-b
    23. (test (op =) (reg b) (const 0))
    24. (branch (label gcd-done))
    25. (assign t (op rem) (reg a) (reg b))
    26. (assign a (reg b))
    27. (assign b (reg t))
    28. (goto (label test-b))
    29. test-b
    30. (test (op =) (reg b) (const 0))
    31. (branch (label gcd-done))
    32. (assign t (op rem) (reg a) (reg b))
    33. (assign a (reg b))
    34. (assign b (reg t))
    35. (goto (label test-b))
    36. test-b
    37. (test (op =) (reg b) (const 0))
    38. (branch (label gcd-done))
    39. (assign t (op rem) (reg a) (reg b))
    40. (assign a (reg b))
    41. (assign b (reg t))
    42. (goto (label test-b))
    43. test-b
    44. (test (op =) (reg b) (const 0))
    45. (branch (label gcd-done))
    46. ;Value: done

Storage Allocation and Garbage Collection

Memory as Vectors

  • Exercise 5.20

    I’ve drawn this diagram with my digital paper.

  • Exercise 5.21

    • a.

      1. (controller
      2. (assign continue (label count-done))
      3. count-leaves-loop
      4. (test (op null?) (reg tree))
      5. (branch (label base-case))
      6. ;; (test (op pair?) (reg tree))
      7. ;; (test (op not) (reg flag))
      8. (assign t (op pair?) (reg tree))
      9. (test (op not) (reg t))
      10. (branch (label count-case))
      11. (save continue) ;setup recursive call -- (count-leaves (car tree))
      12. (assign continue (label after-car-tree))
      13. (save tree)
      14. (assign tree (op car) (reg tree))
      15. (goto (label count-leaves-loop))
      16. after-car-tree
      17. (assign continue (label after-cdr-tree))
      18. (restore tree) ;setup recursive call -- (count-leaves (cdr tree))
      19. (save val)
      20. (assign tree (op cdr) (reg tree))
      21. (goto (label count-leaves-loop))
      22. after-cdr-tree
      23. (restore t)
      24. (assign val (op +) (reg val) (reg t))
      25. (restore continue)
      26. (goto (reg continue))
      27. base-case
      28. (assign val (const 0))
      29. (goto (reg continue))
      30. count-case
      31. (assign val (const 1))
      32. (goto (reg continue))
      33. count-done)

      To implement above register machine program, you need to use the wishful
      thinking as we did in higher level procedure. For the commented code, it would
      work if we use the commented code other than using intermediate register t, but
      in anyway we will need t in later in the code, so it would be better not to
      use the flag directly.

      Then test:

      1. (define count-leaves-machine-a
      2. (make-machine
      3. `((null? ,null?)
      4. (pair? ,pair?)
      5. (not ,not)
      6. (+ ,+)
      7. (car ,car)
      8. (cdr ,cdr))
      9. '(
      10. (assign continue (label count-done))
      11. count-leaves-loop
      12. (test (op null?) (reg tree))
      13. (branch (label base-case))
      14. ;; (test (op pair?) (reg tree))
      15. ;; (test (op not) (reg flag))
      16. (assign t (op pair?) (reg tree))
      17. (test (op not) (reg t))
      18. (branch (label count-case))
      19. (save continue) ;setup recursive call -- (count-leaves (car tree))
      20. (assign continue (label after-car-tree))
      21. (save tree)
      22. (assign tree (op car) (reg tree))
      23. (goto (label count-leaves-loop))
      24. after-car-tree
      25. (assign continue (label after-cdr-tree))
      26. (restore tree) ;setup recursive call -- (count-leaves (cdr tree))
      27. (save val)
      28. (assign tree (op cdr) (reg tree))
      29. (goto (label count-leaves-loop))
      30. after-cdr-tree
      31. (restore t)
      32. (assign val (op +) (reg val) (reg t))
      33. (restore continue)
      34. (goto (reg continue))
      35. base-case
      36. (assign val (const 0))
      37. (goto (reg continue))
      38. count-case
      39. (assign val (const 1))
      40. (goto (reg continue))
      41. count-done)))

      Then test:

      1. (set-register-contents! count-leaves-machine-a 'tree '(1 2 (3 4 (5 6) (7))))
      2. (start count-leaves-machine-a)
      3. ;Value: done
      4. (get-register-contents count-leaves-machine-a 'val)
      5. ;Value: 7
    • b.

      The next one:

      1. (controller
      2. (assign n (const 0))
      3. (assign continue (label count-done))
      4. count-loop
      5. (test (op null?) (reg tree))
      6. (branch (label base-case))
      7. (test (op pair?) (reg tree))
      8. (test (op not) (reg flag))
      9. (branch (label count-case))
      10. (save continue) ;else clause
      11. (assign continue (label after-car))
      12. (save tree)
      13. (assign tree (op car) (reg tree))
      14. (goto (label count-loop))
      15. after-car
      16. (restore tree)
      17. (assign tree (op cdr) (reg tree))
      18. (assign continue (label after-cdr))
      19. (goto (label count-loop))
      20. after-cdr
      21. (restore continue)
      22. (goto (reg continue))
      23. base-case
      24. (goto (reg continue))
      25. count-case
      26. (assign n (op 1+) (reg n))
      27. (goto (reg continue))
      28. count-done)

      Note that here we done need to use any additional register than n for
      returning value.

      And test:

      1. (set-register-contents! count-leaves-machine-b 'tree '(1 2 (3 4 (5) (6 (7)))))
      2. ;Value: done
      3. (start count-leaves-machine-b)
      4. ;Value: done
      5. (get-register-contents count-leaves-machine-b 'n)
      6. ;Value: 7
  • Exercise 5.22

    The functional version of append:

    1. (define (append x y)
    2. (if (null? x)
    3. y
    4. (cons (car x)
    5. (append (cdr x) y))))

    Then the trasformed version:

    1. (define append-machine
    2. (make-machine
    3. '(x continue y)
    4. `((null? ,null?) (pair? ,pair?)
    5. (car ,car) (cdr ,cdr) (cons ,cons))
    6. '((assign continue (label append-done))
    7. append-loop
    8. (test (op null?) (reg x))
    9. (branch (label base-case))
    10. (save continue)
    11. (assign continue (label after-recur))
    12. (save x)
    13. (assign x (op cdr) (reg x))
    14. (goto (label append-loop))
    15. after-recur
    16. (restore x)
    17. (assign x (op car) (reg x))
    18. (assign y (op cons) (reg x) (reg y))
    19. (restore continue)
    20. (goto (reg continue))
    21. base-case
    22. (goto (reg continue))
    23. append-done)))

    Now let’s test this machine in this machine – the result in the y register:

    1. (set-register-contents! append-machine 'x '(1 2 3))
    2. ;Value: done
    3. (set-register-contents! append-machine 'y '(4 5 6))
    4. ;Value: done
    5. (start append-machine)
    6. ;Value: done
    7. (get-register-contents append-machine 'x)
    8. ;Value: 1
    9. (get-register-contents append-machine 'y)
    10. ;Value: (1 2 3 4 5 6)

    Then imperative version:

    1. (define (append! x y)
    2. (set-cdr! (last-pair x) y)
    3. x)
    4. (define (last-pair x)
    5. (if (null? (cdr x))
    6. x
    7. (last-pair (cdr x))))

    Then here is the transformation in step:

    1. ;; High level description -- assume last-pair as primitive
    2. '(
    3. (assign l-p (op last-pair) (reg x))
    4. (perform (op set-cdr!) (reg l-p) (reg y))
    5. )
    6. ;;; last-pair unwinded
    7. '(
    8. test-l-p
    9. (assign t (op cdr) (reg x))
    10. (test (op null?) (reg t))
    11. (branch (label last-pair-done))
    12. (assign x (reg t))
    13. (goto (label test-l-p))
    14. last-pair-done
    15. )
    16. ;; Then link together
    17. '(
    18. test-l-p
    19. (assign t (op cdr) (reg l-p))
    20. (test (op null?) (reg t))
    21. (branch (label last-pair-done))
    22. (assign l-p (reg t))
    23. (goto (label test-l-p))
    24. last-pair-done
    25. (perform (op set-cdr!) (reg l-p) (reg y))
    26. )

    Now we can construct machine we do this simulation; the result in x register.

    1. (define append!-machine
    2. (make-machine
    3. '(x y l-p t)
    4. `((set-cdr! ,set-cdr!) (cdr ,cdr) (null? ,null?))
    5. '(
    6. (assign l-p (reg x))
    7. test-l-p
    8. (assign t (op cdr) (reg l-p))
    9. (test (op null?) (reg t))
    10. (branch (label last-pair-done))
    11. (assign l-p (reg t))
    12. (goto (label test-l-p))
    13. last-pair-done
    14. (perform (op set-cdr!) (reg l-p) (reg y))
    15. )))
    1. (set-register-contents! append!-machine 'x '(1 2 3))
    2. ;Value: done
    3. (set-register-contents! append!-machine 'y '(4 5 6))
    4. ;Value: done
    5. (start append!-machine)
    6. ;Value: done
    7. (get-register-contents append!-machine 'x)
    8. ;Value: (1 2 3 4 5 6)

    Note that in this version, we haven’t used any stack operation, that means this
    is iterative process unlike the preceding implementation.

Maintaining the Illusion of Infinite Memory

In this section, we are going to learn how our evaluator manage the memory under
the hood. This is quite simple and compact algorithm that we are going to see;
but it resolves a lot of difficulties otherwise we encountered with (or should
concern about).

To keep track of the contents of this section, you should draw diagram along with
emerging controller sequence as if you were a machine; otherwise you’ll get feel
like you understood the core concept of this simple and compact algorithm but
only in spurious manner.

The Explicit-Control Evaluator

Now we turn into the application of this new language. Actually this is not an
application but in more presentation about what’s going on under the hood –
this is meant to explain what they assumed or omitted to describe in preceding
chapter, Metacircular Evaluator.

Through this material, we will get the ability to implement meta circular
evaluator or even the other variants in hardware in principal. That is, if we
are patient enough to gathering all the stuffs to implement what we planned to
do, say the query language we developed in preceding chapter, we got the machine
in real world only for that purpose.

So for start, here they are going to unwind the metacircular evaluator in
sections 4.1.1 through 4.1.4.

From this point, we can get some intuition behind this concept through the
lectures available in online. So for recapitulation or to understand what concepts
the authors consider as core, before to go through this section, we’d better to
go through all the material before this section – Register Machines.

Actually we can deduce a lot of useful informations using contraction as we did
in procedural definition (or module) in static sense – analogous to the type
contraction rather than behavior specifications. The lecture explains this
concept consistently through this term of lectures.

So using the static information (deduced by static analysis by human not the
machine; thus for human), we can put together all the relevant parts into one
chuck along with the contracts not only in the high level language also in this
very low level assembly language uniformly.

The Core of the Explicit-Control Evaluator

Sequence Evaluation and Tail Recursion

To deal with the tail recursion, we need to handle the last expression in the
evaluation of sequence differently.

Conditionals, Assignments, and Definitions

  • Exercise 5.23

    ```scheme
    * in explicit control evaluator controller

    1. ;; Exercise 5.23 -- derived forms
    2. (test (op cond?) (reg exp))
    3. (branch (label ev-cond))
    4. (test (op let?) (reg exp))
    5. (branch (label ev-let))
    6. (test (op let*?) (reg exp))
    7. (branch (label ev-let*))
    8. ;; end of exercise 5.23

    1. (branch (label ev-application))
    2. (goto (label unknown-expression-type))
  1. ;; derived forms
  2. ev-cond
  3. (assign exp (op cond->if) (reg exp))
  4. (goto (label ev-if))
  5. ev-let
  6. (assign exp (op let->combination) (reg exp))
  7. (goto (label ev-application))
  8. ev-let*
  9. (assign exp (op let*->let) (reg exp))
  10. (goto (label ev-let))
  11. ;;
  12. ```
  13. Here we used the assumption or "cheat," which isn't actually cheat according to
  14. the footnote in the text. This is due to the analysis phase in our interpreter
  15. as noted in section 4.1.4; all the expression analyzed statically and the source
  16. level transformation is one of the static process, it is done at that phase. We
  17. can verify this fact – our interpreter do the static analysis before execution
  18. – with the following example:
  19. ```scheme
  20. (lambda (x) (define y 5) (define y 2) x)
  21. ;duplicate internal definitions for (#[uninterned-symbol 38 y]) in |#[unnamed-procedure]|
  22. ;To continue, call RESTART with an option number:
  23. ; (RESTART 1) => Return to read-eval-print level 1.
  24. ```
  25. If it were not doing the static analysis, it couldn't the duplicated internal
  26. definitions since it is the body of lambda expression, which is not supposed to
  27. be evaluated until it is applied.
  28. Then test:
  29. ```scheme
  30. (start eceval)
  31. ;;; EC-Eval input:
  32. (cond (false 5) (false 2) (true 3) (else 1))
  33. (total-pushes = 9 maximum-depth = 3)
  34. ;;; EC-Eval value:
  35. 3
  36. ;;; EC-Eval input:
  37. (let* ((x 5) (y (+ x 2))) (+ x y))
  38. (total-pushes = 26 maximum-depth = 8)
  39. ;;; EC-Eval value:
  40. 12
  41. ```
  • Exercise 5.24

    Cond special form is like combination of ev-if and ev-application. That is
    it need to loop through the clauses and for each clause like evl-application
    loop through in ev-appl-operand-loop, and within that loop, it need to branch
    depending on the evaluation of cond-predicate of each clause as ev-if did,
    if it were true, it need to setup to call ev-sequence as
    ev-appl-accum-last-arg did.

    Here is the register code do the works:

    1. ev-cond
    2. ;; Input exp env continue
    3. ;; Output val
    4. ;; Write all (call the ev-sequence)
    5. ;; Stack unchanged
    6. (assign unev (op cond-clauses) (reg exp))
    7. (save continue)
    8. cond-clause-loop
    9. ;; Input unev env stack (top as return point)
    10. ;; Output val
    11. ;; Write all
    12. ;; Stack top value removed
    13. (assign exp (op cond-first-clause) (reg unev))
    14. (test (op cond-else-clause?) (reg exp))
    15. (branch (label cond-else-clause))
    16. (test (op cond-last-clause?) (reg unev))
    17. (branch (label cond-last-clause))
    18. cond-pred
    19. (save unev)
    20. (save exp)
    21. (save env)
    22. (assign exp (op cond-predicate) (reg exp))
    23. (assign continue (label cond-pred-decide))
    24. (goto (label eval-dispatch))
    25. cond-pred-decide
    26. (restore env)
    27. (restore exp)
    28. (restore unev)
    29. (test (op true?) (reg val))
    30. (branch (label cond-actions))
    31. (assign unev (op cond-rest-clauses) (reg unev))
    32. (goto (label cond-clause-loop))
    33. cond-actions
    34. ;; Input exp, env
    35. ;; Output val
    36. ;; Write all
    37. ;; Stack top removed
    38. (assign unev (op cond-actions) (reg exp))
    39. (goto (label ev-sequence))
    40. cond-else-clause
    41. (test (op cond-last-clause?) (reg unev))
    42. (test (op not) (reg flag))
    43. (branch (label bad-cond-syntax))
    44. (goto (label cond-actions))
    45. cond-last-clause
    46. (save exp)
    47. (save env)
    48. (assign exp (op cond-predicate) (reg exp))
    49. (assign continue (label cond-last-decide))
    50. (goto (label eval-dispatch))
    51. cond-last-decide
    52. (restore env)
    53. (restore exp)
    54. (test (op true?) (reg val))
    55. (branch (label cond-actions))
    56. (restore continue)
    57. (goto (reg continue))

    Now it works as expected:

    ```scheme
    (start eceval)

  1. ;;; EC-Eval input:
  2. (cond (false 5) (false 2) (true 3) (else 1))
  3. (total-pushes = 10 maximum-depth = 4)
  4. ;;; EC-Eval value:
  5. 3
  6. ```
  7. Now test the tail recursion problem:
  8. ```scheme
  9. ;;; from recitation 26 of 2004
  10. (define (list? x)
  11. (cond ((null? x) true)
  12. ((pair? x) (list? (cdr x)))))
  13. (define z (list 1))
  14. (set-cdr! z z)
  15. (list? z)
  16. ```
  17. Then
  18. ```scheme
  19. ;;; EC-Eval input:
  20. (define z (list 1))
  21. (total-pushes = 8 maximum-depth = 6)
  22. ;;; EC-Eval value:
  23. ok
  24. ;;; EC-Eval input:
  25. (set-cdr! z z)
  26. (total-pushes = 8 maximum-depth = 5)
  27. ;;; EC-Eval value:
  28. #!unspecific
  29. ;;; EC-Eval input:
  30. (list? z)
  31. C-c C-c;Quit!
  32. ```
  33. Works as expected.
  • Exercise 5.25

    As we learned from section 4.2, the only change is the application from the
    applicative Scheme.

    That is, rather than evaluating all the operands before application, we need to
    hand over the operands without evaluating since the operands needed are
    different in primitive procedure’s from compound procedure’s.

    We also need to support additional data structure called thunk since it is
    needed to distinguish the delayed object from the normal values.

    Also we need to handle the if expression somewhat differently from the
    applicative one since it need actual-value for value of its predicate part.

    Note that we can not assume the actual-value (implicitly force-it too) is
    available as machine operations as the text did; we need to unwind those
    procedure explicitly into register subroutines since actual-value and
    force-it wrapping around the eval process in it. So we need to unwrap those
    until we got the procedures that does not inherit the any other process that is
    implemented as subroutines in our register machine (but we are good to use the
    delay-it procedure since it is just syntactic procedure or representation of
    thunk ADT).

    So to summarize, we can assume the following procedures as machine operators:

    1. ;; Exercise 5.25 delayed one
    2. (define (delay-it exp env)
    3. (list 'thunk exp env))
    4. (define (thunk? obj)
    5. (tagged-list? obj 'thunk))
    6. (define (thunk-exp thunk) (cadr thunk))
    7. (define (thunk-env thunk) (caddr thunk))
    8. ;; "thunk" that has been forced and is storing its (memoized) value
    9. (define (evaluated-thunk? obj)
    10. (tagged-list? obj 'evaluated-thunk))
    11. (define (thunk-value evaluated-thunk) (cadr evaluated-thunk))

    But we should implement the following ones as subroutines:

    1. (define (actual-value exp env)
    2. (force-it (eval exp env)))
    3. (define (force-it obj)
    4. (cond ((thunk? obj)
    5. (let ((result (actual-value
    6. (thunk-exp obj)
    7. (thunk-env obj))))
    8. (set-car! obj 'evaluated-thunk)
    9. (set-car! (cdr obj) result) ; replace exp with its value
    10. (set-cdr! (cdr obj) '()) ; forget unneeded env
    11. result))
    12. ((evaluated-thunk? obj)
    13. (thunk-value obj))
    14. (else obj)))

    Here is the code translated. Observe the similarity of the accumulation argument
    loop between the primitive and compound one – it is more easy to see in above
    high level language description; if we want to do, we could have modulate the
    duplicate code into one general subroutine which will accept more arguments as
    input registers or on stack. We will come back this issue in later.

    1. ev-application
    2. (save continue)
    3. (save env)
    4. (assign unev (op operands) (reg exp))
    5. (save unev)
    6. (assign exp (op operator) (reg exp))
    7. (assign continue (label ev-appl-did-operator))
    8. (goto (label actual-value))
    9. ev-appl-did-operator
    10. (restore unev)
    11. (restore env)
    12. (assign proc (reg val))
    13. (branch (label apply-dispatch))
    14. apply-dispatch
    15. (assign argl (op empty-arglist))
    16. ;; Input proc, unev, env, stack -- top value is return point
    17. ;; Output val
    18. ;; Write all
    19. ;; Stack top value removed
    20. (test (op primitive-procedure?) (reg proc))
    21. (branch (label primitive-apply))
    22. (test (op compound-procedure?) (reg proc))
    23. (branch (label compound-apply))
    24. (goto (label unknown-procedure-type))
    25. primitive-apply
    26. (test (op no-operands?) (reg unev))
    27. (branch (label exec-primitive-apply))
    28. (save proc)
    29. primitive-operand-loop
    30. (save argl)
    31. (assign exp (op first-operand) (reg unev))
    32. (test (op last-operand?) (reg unev))
    33. (branch (label prim-last-arg))
    34. (save env)
    35. (save unev)
    36. (assign continue (label prim-accumulate-arg))
    37. (goto (label actual-value))
    38. prim-accumulate-arg
    39. (restore unev)
    40. (restore env)
    41. (restore argl)
    42. (assign argl (op adjoin-arg) (reg val) (reg argl))
    43. (assign unev (op rest-operands) (reg unev))
    44. (goto (label primitive-operand-loop))
    45. prim-last-arg
    46. (assign continue (label prim-accum-last-arg))
    47. (goto (label actual-value))
    48. prim-accum-last-arg
    49. (restore argl)
    50. (assign argl (op adjoin-arg) (reg val) (reg argl))
    51. (restore proc)
    52. (goto (label exec-primitive-apply))
    53. exec-primitive-apply
    54. (assign val (op apply-primitive-procedure)
    55. (reg proc)
    56. (reg argl))
    57. (restore continue)
    58. (goto (reg continue))
    59. compound-apply
    60. (test (op no-operands?) (reg unev))
    61. (branch (label exec-compound-apply))
    62. compound-operand-loop
    63. (assign exp (op first-operand) (reg unev))
    64. (test (op last-operand?) (reg unev))
    65. (branch (label compound-last-arg))
    66. (assign val (op delay-it) (reg exp) (reg env))
    67. (assign argl (op adjoin-arg) (reg val) (reg argl))
    68. (assign unev (op rest-operands) (reg unev))
    69. (goto (label compound-operand-loop))
    70. compound-last-arg
    71. (assign val (op delay-it) (reg exp) (reg env))
    72. compound-accum-last-arg
    73. (assign argl (op adjoin-arg) (reg val) (reg argl))
    74. (goto (label exec-compound-apply))
    75. exec-compound-apply
    76. (assign unev (op procedure-parameters) (reg proc))
    77. (assign env (op procedure-environment) (reg proc))
    78. (assign env (op extend-environment)
    79. (reg unev) (reg argl) (reg env))
    80. (assign unev (op procedure-body) (reg proc))
    81. (goto (label ev-sequence))
    82. ev-if
    83. (save exp)
    84. (save env)
    85. (save continue)
    86. (assign continue (label ev-if-decide))
    87. (assign exp (op if-predicate) (reg exp))
    88. (goto (label actual-value))
    89. ev-if-decide
    90. (restore continue)
    91. (restore env)
    92. (restore exp)
    93. (test (op true?) (reg val))
    94. (branch (label ev-if-consequent))
    95. ev-if-alternative
    96. (assign exp (op if-alternative) (reg exp))
    97. (goto (label eval-dispatch))
    98. ev-if-consequent
    99. (assign exp (op if-consequent) (reg exp))
    100. (goto (label eval-dispatch))
    101. actual-value
    102. ;; contract is same as eval-dispatch
    103. (save continue)
    104. (assign continue (label after-eval))
    105. (goto (label eval-dispatch))
    106. after-eval
    107. (restore continue)
    108. (goto (label force-it))
    109. force-it
    110. ;; Input val continue
    111. ;; Output val
    112. ;; Write all
    113. ;; Stack unchanged
    114. (test (op thunk?) (reg val))
    115. (branch (label force-thunk))
    116. (test (op evaluated-thunk?) (reg val))
    117. (branch (label force-evaluated))
    118. (goto (reg continue))
    119. force-thunk
    120. (save continue)
    121. (save val) ;need later -- obj
    122. (assign continue (label force-result))
    123. (assign exp (op thunk-exp) (reg val))
    124. (assign env (op thunk-env) (reg val))
    125. (goto (label actual-value))
    126. force-result
    127. (restore exp) ;clobbering the exp as obj
    128. (restore continue)
    129. (perform (op set-car!) (reg exp) (const evaluated-thunk))
    130. (assign exp (op cdr) (reg exp))
    131. (perform (op set-car!) (reg exp) (reg val))
    132. (perform (op set-cdr!) (reg exp) (const ()))
    133. (goto (reg continue))
    134. force-evaluated
    135. (assign val (op thunk-value) (reg val))
    136. (goto (reg continue))

    Now let’s test:

    ```scheme
    (start eceval)

  1. ;;; EC-Eval input:
  2. (define (try a b)
  3. (if (= a 0) 1 b))
  4. (total-pushes = 3 maximum-depth = 3)
  5. ;;; EC-Eval value:
  6. ok
  7. ;;; EC-Eval input:
  8. (try 0 (/ 1 0))
  9. (total-pushes = 22 maximum-depth = 12)
  10. ;;; EC-Eval value:
  11. 1
  12. ```
  13. It works as expected.

Running the Evaluator

  • Exercise 5.26

    • a.

      Since the defined procedure captured iterative process, we expect, if our
      register machine handles the tail recursion correctly, the maximum stack depth
      would not change:

      1. ;;; EC-Eval input:
      2. (factorial 5)
      3. (total-pushes = 204 maximum-depth = 10)
      4. ;;; EC-Eval value:
      5. 120
      6. ;;; EC-Eval input:
      7. (factorial 3)
      8. (total-pushes = 134 maximum-depth = 10)
      9. ;;; EC-Eval value:
      10. 6
      11. ;;; EC-Eval input:
      12. (factorial 30)
      13. (total-pushes = 1079 maximum-depth = 10)
      14. ;;; EC-Eval value:
      15. 265252859812191058636308480000000

      And it turns out 10.

    • b.

      From those data, and as it is linear, we can deduce the formular as (P(n) = 35n+29).

  • Exercise 5.27

    Here is the data:

    1. ;;; EC-Eval input:
    2. (factorial 3)
    3. (total-pushes = 80 maximum-depth = 18)
    4. ;;; EC-Eval value:
    5. 6
    6. ;;; EC-Eval input:
    7. (factorial 10)
    8. (total-pushes = 304 maximum-depth = 53)
    9. ;;; EC-Eval value:
    10. 3628800
    11. ;;; EC-Eval input:
    12. (factorial 30)
    13. (total-pushes = 944 maximum-depth = 153)
    14. ;;; EC-Eval value:
    15. 265252859812191058636308480000000

    From this fact, we can deduce the maximum-depth as (5n + 3), and
    total-pushes as (32n - 16).

    So the table gets as

    | | Maximum depth | Number of pushes |
    |————- |——————- |———————— |
    | Recursive | 5n + 3 | 32n - 16 |
    | factorial | | |
    | Iterative | 10 | 35n + 29 |
    | factorial | | |

  • Exercise 5.28

    Let’s modify our register machine not to be tail recursive. Then let’s re-run
    exercise 5.26‘s factorial – iterative one:

    1. ;;; EC-Eval input:
    2. (factorial 5)
    3. (total-pushes = 218 maximum-depth = 29)
    4. ;;; EC-Eval value:
    5. 120
    6. ;;; EC-Eval input:
    7. (factorial 3)
    8. (total-pushes = 144 maximum-depth = 23)
    9. ;;; EC-Eval value:
    10. 6
    11. ;;; EC-Eval input:
    12. (factorial 30)
    13. (total-pushes = 1143 maximum-depth = 104)
    14. ;;; EC-Eval value:
    15. 265252859812191058636308480000000

    Now the maximum-depth gets (3n+14) not the constant 10 and total-pushes gets
    (37n + 33), which is definitely large number than previous one.

    How about the recurvise definition?

    1. ;;; EC-Eval input:
    2. (factorial 3)
    3. (total-pushes = 86 maximum-depth = 27)
    4. ;;; EC-Eval value:
    5. 6
    6. ;;; EC-Eval input:
    7. (factorial 10)
    8. (total-pushes = 324 maximum-depth = 83)
    9. ;;; EC-Eval value:
    10. 3628800
    11. ;;; EC-Eval input:
    12. (factorial 30)
    13. (total-pushes = 1004 maximum-depth = 243)
    14. ;;; EC-Eval value:
    15. 265252859812191058636308480000000

    As expected this one also has involved more stack operations than previous since
    this version of evaluator execute instructions that is useless.

  • Exercise 5.29

    • a.

      From the experiments, we extracted the following data:

      1. ;;; EC-Eval input:
      2. (fib 5)
      3. (total-pushes = 408 maximum-depth = 28)
      4. ;;; EC-Eval value:
      5. 5
      6. ;;; EC-Eval input:
      7. (fib 10)
      8. (total-pushes = 4944 maximum-depth = 53)
      9. ;;; EC-Eval value:
      10. 55
      11. ;;; EC-Eval input:
      12. (fib 15)
      13. (total-pushes = 55232 maximum-depth = 78)
      14. ;;; EC-Eval value:
      15. 610

      So we can deduce that maximum-depth is given as (5n + 3).

    • b.

      From the experiments conducted in a., we can inspect that the total-pushes
      increase proportionally to the value of (fib n). As we reasoned in the
      exercise of 1.2.2 section, we know the (fib n) is exponential; so as to
      total-pushes.

      As we did in that exercise to deduce the exact number of (fib n), here we
      analyze the total-pushes using the induction on the definition of fib. We
      can do this analysis since from the controller sequence of eceval should
      evaluate the subproblems in exact same manner with the entry calling of (fib n).

      From this argument, we can formulate total-pushes as
      [S(n) = S(n - 1) + S(n - 2) + k]
      , where S denotes the total-pushes of input number n.

      Now let’s deduce the k from the experiments:

      1. ;;; EC-Eval input:
      2. (fib 3)
      3. (total-pushes = 128 maximum-depth = 18)
      4. ;;; EC-Eval value:
      5. 2
      6. ;;; EC-Eval input:
      7. (fib 4)
      8. (total-pushes = 240 maximum-depth = 23)
      9. ;;; EC-Eval value:
      10. 3

      From (fib 3), (fib 4), (fib 5), we can get (k = 40). Let’s verify this on
      different data:

      1. ;;; EC-Eval input:
      2. (fib 14)
      3. (total-pushes = 34120 maximum-depth = 73)
      4. ;;; EC-Eval value:
      5. 377
      6. ;;; EC-Eval input:
      7. (fib 16)
      8. (total-pushes = 89392 maximum-depth = 83)
      9. ;;; EC-Eval value:
      10. 987

      From these set of data, we also get (k = 40), as expected.

      Now let’s try to answer final question of this exercise. So far we deduced that
      S also can be formulated “Fibonacci” number like equation – the doubly
      recursive equation.

      So now we turn to what is the initial conditions of that equation, that is, like
      the ones from fib – the base cases (fib 0) equals 0, (fib 1) equals 1.

      After that, we can relate these numbers with the numbers of Fibonacci as we
      required.

      Here is the base cases:

      1. ;;; EC-Eval input:
      2. (fib 0)
      3. (total-pushes = 16 maximum-depth = 8)
      4. ;;; EC-Eval value:
      5. 0
      6. ;;; EC-Eval input:
      7. (fib 1)
      8. (total-pushes = 16 maximum-depth = 8)
      9. ;;; EC-Eval value:
      10. 1
      11. ;;; EC-Eval input:
      12. (fib 2)
      13. (total-pushes = 72 maximum-depth = 13)
      14. ;;; EC-Eval value:
      15. 1

      So S(0) = 16 and S(1) = 16; and for verification, S(2) = S(1) + S(0) + 40 =
      72
      as expected. From these data, it seems like we can equate as S(n) = a
      Fib(n + 1) + b
      . Let’s verify by plugging the right hand side formula into the
      left hand side in the S(n) formula:
      a Fib(n + 1) + b = a Fib (n) + a Fib (n - 1) + 2 b + 40, which reduced to
      b = - 40 and if we plug this result into the base case, we got S(1) = a Fib
      (2) - 40
      , which, in turn, means a = 56.

      So the solution is S(n) = 56 Fib(n + 1) - 40.

  • Exercise 5.30

    • a.

      We need to use flag register to signal that these erroneous process occurred.
      Before modifying the loopkup operation as outlined in the text, we need to
      inspect what the value of flag can be in user defined process. And then we
      should design the special value that signal error, which are set in flag
      register.

      For the current version of register machine, we set flag as the result of
      execution of condition operation; we need to fix this since we are going to
      define special value that can not be set in user defined process but the current
      one there is no restriction on the value of flag user can cause.

      Moreover, as we are using the flag register only in the branch to determine
      where to proceed, we don’t need flag to be other than boolean. So it is
      reasonable to restrict the condition operation in test can only set the
      value of flag to true or false.

      We can make contract informally to the user of our register machine but the
      implementor of eceval machine is one of the user of this. And this is the
      restriction eceval implementor to impose to the user of his machine. So maybe
      it is none of the business for the implementor of our register machine but for
      the eceval evaluator; that means, if the eceval implementor is along with
      the contract that the value of flag should be boolean in processing user
      defined process – the unwinding process, it is good to go.

      So now, we restricted the flag value to be boolean in normal process, we are
      good to define whatever value we want to use the signaling error.

      So for presentation let’s change the lookup-variable-value to support the idea
      outlined above:

      1. (define (lookup-variable-value var env)
      2. (define (env-loop env)
      3. (define (scan vars vals)
      4. (cond ((null? vars)
      5. (env-loop (enclosing-environment env)))
      6. ((eq? var (car vars))
      7. (car vals))
      8. (else (scan (cdr vars) (cdr vals)))))
      9. (if (eq? env the-empty-environment)
      10. (error "Unbound variable" var)
      11. (let ((frame (first-frame env)))
      12. (scan (frame-variables frame)
      13. (frame-values frame)))))
      14. (env-loop env))

      And then

      1. ev-variable
      2. ;; (assign val (op lookup-variable-value) (reg exp) (reg env))
      3. (assign val (op lookup-variable-value-with-error) (reg exp) (reg env) (reg flag))
      4. (test (op error-flag?) (reg flag))
      5. ;; unbound-variable
      6. ;; Input: exp -- given unbound variable
      7. (branch (label unbound-variable))
      8. (goto (reg continue))
      9. unbound-variable
      10. (assign val (op list) (const "Unbound variable") (reg exp))
      11. (goto (label signal-error))

      With this configuration, let’s test it:

      1. ;;; EC-Eval input:
      2. x
      3. (total-pushes = 0 maximum-depth = 0)
      4. ;;; EC-Eval value:
      5. #t
      6. ;;; EC-Eval input:
      7. y
      8. (total-pushes = 0 maximum-depth = 0)
      9. ;;; EC-Eval value:
      10. #t

      Unfortunately this idea won’t work since we can’t change the actual contents of
      flag register unless assign it directly; there is a way to embody this idea,
      that is, to unwind this machine operation into subroutine. Then we are able to
      mutate the flag register as we wanted; but it thought as would be better to goto
      signal-error entry point rather than to mutate the flag register and then go
      back to caller. However there is some issues around this problem like the make
      the stack or other registers to be consist with the contract – this is the
      responsibilities to the caller not the lookup-variable itself.

      Whatever it be, for now, we are not going to implement the
      lookup-variable-value procedure as subroutine.

      So it would be better to get back to the original idea suggested in the text –
      use the value that can not be set by user, that is using the type-tag I think.

      Here is the code:

      1. (define (error-exp? exp)
      2. (tagged-list? exp 'error))
      3. (define (make-error-exp exp) `(error ,exp))
      4. (define (lookup-variable-value-with-error var env)
      5. (define (env-loop env)
      6. (define (scan vars vals)
      7. (cond ((null? vars)
      8. (env-loop (enclosing-environment env)))
      9. ((eq? var (car vars))
      10. (car vals))
      11. (else (scan (cdr vars) (cdr vals)))))
      12. (if (eq? env the-empty-environment)
      13. (make-error-exp `("Unbound variable" ,var))
      14. (let ((frame (first-frame env)))
      15. (scan (frame-variables frame)
      16. (frame-values frame)))))
      17. (env-loop env))
      1. ev-variable
      2. ;; (assign val (op lookup-variable-value) (reg exp) (reg env))
      3. (assign val (op lookup-variable-value-with-error) (reg exp) (reg env))
      4. (test (op error-exp?) (reg val))
      5. ;; unbound-variable
      6. ;; Input: val -- Error message
      7. (branch (label signal-error))
      8. (goto (reg continue))

      Let’s test:

      1. ;;; EC-Eval input:
      2. y
      3. (error (Unbound variable y))
      4. ;;; EC-Eval input:
      5. x
      6. (total-pushes = 0 maximum-depth = 0)
      7. ;;; EC-Eval value:
      8. 5

      Now it runs as expected. Here we chose to delegate the duty producing error
      message to the procedure who signal this error rather than to the caller – just
      hand over this to the signal-error subroutine.

      Now let’s convert all the error expression into the one outlined above:

      1. (define (extend-environment vars vals base-env)
      2. (if (= (length vars) (length vals))
      3. (cons (make-frame vars vals) base-env)
      4. (if (< (length vars) (length vals))
      5. ;; (error "Too many arguments supplied" vars vals)
      6. (make-error-exp `("Too many arguments supplied" ,vars ,vals))
      7. ;; (error "Too few arguments supplied" vars vals)
      8. (make-error-exp `("Too few arguments supplied" ,vars ,vals))
      9. )))
      10. (define (set-variable-value! var val env)
      11. (define (env-loop env)
      12. (define (scan vars vals)
      13. (cond ((null? vars)
      14. (env-loop (enclosing-environment env)))
      15. ((eq? var (car vars))
      16. (set-car! vals val))
      17. (else (scan (cdr vars) (cdr vals)))))
      18. (if (eq? env the-empty-environment)
      19. ;; (error "Unbound variable -- SET!" var)
      20. (make-error-exp `("Unbound variable -- SET!" ,var))
      21. (let ((frame (first-frame env)))
      22. (scan (frame-variables frame)
      23. (frame-values frame)))))
      24. (env-loop env))

      Then in ev-assignment,

      1. ;; (perform
      2. ;; (op set-variable-value!) (reg unev) (reg val) (reg env))
      3. (assign val (op set-variable-value!) (reg unev) (reg val) (reg env))
      4. (test (op error-exp?) (reg val))
      5. (branch (label signal-error))
      6. (assign val (const ok))
      7. (goto (reg continue))

      And in compound-apply,

      ```scheme
      (assign env (op extend-environment)

      1. (reg unev) (reg argl) (reg env))

      (test (op error-exp?) (reg env))
      (branch (label unmatched-argument-number-error))
      (assign unev (op procedure-body) (reg proc))
      (goto (label ev-sequence))

  1. unmatched-argument-number-error
  2. (restore continue)
  3. (assign val (reg env))
  4. (goto (label signal-error))
  5. ```
  6. These are all the procedure signal error in [support file](Exercise/ch5-eceval-support.scm).
  7. Let's test:
  8. ```scheme
  9. ;;; EC-Eval input:
  10. (set! y 3)
  11. (error (Unbound variable -- SET! y))
  12. ;;; EC-Eval input:
  13. (define (test x y) 5)
  14. (total-pushes = 3 maximum-depth = 3)
  15. ;;; EC-Eval value:
  16. ok
  17. ;;; EC-Eval input:
  18. (test 2 3 1 3)
  19. (error (Too many arguments supplied (x y) (2 3 1 3)))
  20. ;;; EC-Eval input:
  21. (test 1)
  22. (error (Too few arguments supplied (x y) (1)))
  23. ```
  24. Great! But there is also problem in this approach, that is, user can produce
  25. this tagged data as they want. Like this:
  26. ```scheme
  27. ;;; EC-Eval input:
  28. (define x '(error "This should be forbidden by user"))
  29. (total-pushes = 3 maximum-depth = 3)
  30. ;;; EC-Eval value:
  31. ok
  32. ;;; EC-Eval input:
  33. x
  34. (error This should be forbidden by user)
  35. ```
  36. Or
  37. ```scheme
  38. ;;; EC-Eval input:
  39. (define y '(primitive "this also should be forbiddend"))
  40. (total-pushes = 3 maximum-depth = 3)
  41. ;;; EC-Eval value:
  42. ok
  43. ;;; EC-Eval input:
  44. (y 5)
  45. ;The object "this also should be forbiddend" is not applicable.
  46. ;To continue, call RESTART with an option number:
  47. ; (RESTART 2) => Specify a procedure to use in its place.
  48. ; (RESTART 1) => Return to read-eval-print level 1.
  49. ```
  50. We need to cope with this situation by noting that user only can construct pair
  51. data – thus list data – via the `cons`. So if we make change the pair
  52. structure to the tagged data like `(pair x y)` then we are free from this kind
  53. of error:
  54. ```scheme
  55. *** in primitive-procedures
  56. (list (list 'car cadr)
  57. (list 'cdr caddr)
  58. (list 'cons (lambda (x y) `(pair ,x ,y)))
  59. (list 'null? (lambda (p) (and (tagged-list? p 'pair) (null? (cdr p)))))
  60. (list 'pair? (lambda (p) (tagged-list? p 'pair)))
  61. ...)
  62. *** in setup-environment
  63. (define-variable! 'nil '(pair) initial-env)
  64. ```
  65. Then
  66. ```scheme
  67. ;;; EC-Eval input:
  68. (define x (cons 'error (cons "This should be forbidden by user" nil)))
  69. (total-pushes = 19 maximum-depth = 11)
  70. ;;; EC-Eval value:
  71. ok
  72. ;;; EC-Eval input:
  73. x
  74. (total-pushes = 0 maximum-depth = 0)
  75. ;;; EC-Eval value:
  76. (pair error (pair This should be forbidden by user (pair)))
  77. ```
  78. Here we used `cons` rather than `quote`; but it should work if we used `quote`
  79. instead – we need to change the `test-of-quotation` procedure as subroutine
  80. that converts underlying pair structure into that of `eceval`:
  81. ```scheme
  82. (define (text-of-quotation exp)
  83. (tree-map identity-procedure
  84. (lambda (x y) `(pair ,x ,y))
  85. '(pair)
  86. (cadr exp)))
  87. ```
  88. `tree-map` is the one implemented in [exercise 5.12](#orgaf0b374).
  89. Then
  90. ```scheme
  91. ;;; EC-Eval input:
  92. (define x '(error "This should be forbidden by user"))
  93. (total-pushes = 3 maximum-depth = 3)
  94. ;;; EC-Eval value:
  95. ok
  96. ;;; EC-Eval input:
  97. x
  98. (total-pushes = 0 maximum-depth = 0)
  99. ;;; EC-Eval value:
  100. (pair error (pair This should be forbidden by user (pair)))
  101. ;;; EC-Eval input:
  102. (define y '(primitive "this also should be forbiddend"))
  103. (total-pushes = 3 maximum-depth = 3)
  104. ;;; EC-Eval value:
  105. ok
  106. ;;; EC-Eval input:
  107. (y 5)
  108. unknown-procedure-type-error
  109. ```
  110. Now we don't need any additional definition of `nil`:
  111. ```scheme
  112. ;;; EC-Eval input:
  113. '()
  114. (total-pushes = 0 maximum-depth = 0)
  115. ;;; EC-Eval value:
  116. (pair)
  117. ;;; EC-Eval input:
  118. (null? '())
  119. (total-pushes = 5 maximum-depth = 3)
  120. ;;; EC-Eval value:
  121. #t
  122. ```
  123. For completeness, we should define print procedure for our pair structure:
  124. ```scheme
  125. (define (user-print object)
  126. (cond ((compound-procedure? object)
  127. (display (list 'compound-procedure
  128. (procedure-parameters object)
  129. (procedure-body object)
  130. '<procedure-env>)))
  131. ((pair?* object)
  132. (print-pair* object))
  133. (else (display object))))
  134. ;; Structural induction on pair
  135. (define (print-pair* p)
  136. (define (iter p)
  137. (cond ((null?* p))
  138. ((pair?* p)
  139. (display " ")
  140. (display (car* p))
  141. (iter (cdr* p)))
  142. (else
  143. ;; not pair -- atomic expression
  144. (display " . ")
  145. (display p))))
  146. (display "(")
  147. (display (car* p))
  148. (iter (cdr* p))
  149. (display ")"))
  150. ```
  151. Then
  152. ```scheme
  153. ;;; EC-Eval input:
  154. (define x '(error "This should be forbidden by user"))
  155. (total-pushes = 3 maximum-depth = 3)
  156. ;;; EC-Eval value:
  157. ok
  158. ;;; EC-Eval input:
  159. x
  160. (total-pushes = 0 maximum-depth = 0)
  161. ;;; EC-Eval value:
  162. (error This should be forbidden by user)
  163. ;;; EC-Eval input:
  164. (car x)
  165. (total-pushes = 5 maximum-depth = 3)
  166. ;;; EC-Eval value:
  167. error
  168. ```
  169. Works as expected. For efficiency, professional product would use bit-wise typed
  170. data rather than this version – typed using pair structure, as noted in text book.
  171. Now we are good to apply our strategy to the rest of instructions that may
  172. corrupt in some condition other than the ones in the support file, which
  173. signaling with the `error` procedure. To do that, we first look into the
  174. `eceval` code to figure out.
  175. Here is the list:
  176. - `self-eval`: It is good as it is
  177. - `variable`: We handled this in `lookup-variable-value` procedure.
  178. - `quote`:
  179. We need to handle
  180. ```scheme
  181. (quote)
  182. ;Ill-formed syntax: (quote)
  183. ;To continue, call RESTART with an option number:
  184. ; (RESTART 1) => Return to read-eval-print level 1.
  185. (quote x . y)
  186. ;Ill-formed syntax: (quote x . y)
  187. ;To continue, call RESTART with an option number:
  188. ; (RESTART 1) => Return to read-eval-print level 1.
  189. ```
  190. - `lambda`:
  191. We need to handle
  192. ```scheme
  193. (lambda)
  194. ;Ill-formed syntax: (lambda)
  195. ;To continue, call RESTART with an option number:
  196. ; (RESTART 1) => Return to read-eval-print level 1.
  197. (lambda x)
  198. ;Ill-formed syntax: (lambda x)
  199. ;To continue, call RESTART with an option number:
  200. ; (RESTART 1) => Return to read-eval-print level 1.
  201. (lambda x y . z)
  202. ;Ill-formed syntax: (lambda x y . z)
  203. ;To continue, call RESTART with an option number:
  204. ; (RESTART 1) => Return to read-eval-print level 1.
  205. ```
  206. - `application`: `extend-environment` handles the error case. For the error
  207. during the application of primitive procedure, we will handle this **b.** part
  208. of this exercise.
  209. And should handle
  210. ```scheme
  211. (x 1 . 4)
  212. ;Combination must be a proper list: (x 1 . 4)
  213. ;To continue, call RESTART with an option number:
  214. ; (RESTART 1) => Return to read-eval-print level 1.
  215. ```
  216. - `begin` or `ev-sequence`:
  217. Should it accept empty sequence?
  218. ```scheme
  219. (begin)
  220. ;Unspecified return value
  221. ```
  222. For consistency with the `lambda` expression, let us choose above expression
  223. as ill-formed one.
  224. So we can say explicitly that the contraction of `ev-sequence`'s input should
  225. not be empty.
  226. - `if`:
  227. Should handle
  228. ```scheme
  229. (if)
  230. ;Ill-formed syntax: (if)
  231. ;To continue, call RESTART with an option number:
  232. ; (RESTART 1) => Return to read-eval-print level 1.
  233. (if x)
  234. ;Ill-formed syntax: (if x)
  235. ;To continue, call RESTART with an option number:
  236. ; (RESTART 1) => Return to read-eval-print level 1.
  237. (if 1 2 3 4)
  238. ;Ill-formed syntax: (if 1 2 3 4)
  239. ;To continue, call RESTART with an option number:
  240. ; (RESTART 1) => Return to read-eval-print level 1.
  241. (if 1 . 2)
  242. ;Ill-formed syntax: (if 1 . 2)
  243. ;To continue, call RESTART with an option number:
  244. ; (RESTART 1) => Return to read-eval-print level 1.
  245. ```
  246. - `assignment`:
  247. Should handle
  248. ```scheme
  249. (set! x y z)
  250. ;Variable required in this context: x
  251. ;To continue, call RESTART with an option number:
  252. ; (RESTART 1) => Return to read-eval-print level 1.
  253. (set! x . y)
  254. ;Variable required in this context: x
  255. ;To continue, call RESTART with an option number:
  256. ; (RESTART 1) => Return to read-eval-print level 1.
  257. ```
  258. But should we?
  259. ```scheme
  260. (define x 2)
  261. ;Value: x
  262. (set! x)
  263. ;Value: 2
  264. x
  265. ;Unassigned variable: x
  266. ;To continue, call RESTART with an option number:
  267. ; (RESTART 3) => Specify a value to use instead of x.
  268. ; (RESTART 2) => Set x to a given value.
  269. ; (RESTART 1) => Return to read-eval-print level 1.
  270. ```
  271. This is just like `unset!` if it exists; but it is "Unassigned" not "Unbound"
  272. as follows:
  273. ```scheme
  274. y
  275. ;Unbound variable: y
  276. ;To continue, call RESTART with an option number:
  277. ; (RESTART 3) => Specify a value to use instead of y.
  278. ; (RESTART 2) => Define y to a given value.
  279. ; (RESTART 1) => Return to read-eval-print level 1.
  280. ```
  281. The difference is that we can re-set the value:
  282. ```scheme
  283. (set! x 3)
  284. ;Unspecified return value
  285. ```
  286. Maybe it is used internally, when we want to handle expression such as
  287. `letrec`. So it is not needed for now; let us choose to define such expression
  288. as ill-formed one.
  289. - `definition`:
  290. Should handle
  291. ```scheme
  292. (define)
  293. ;Ill-formed syntax: (define)
  294. ;To continue, call RESTART with an option number:
  295. ; (RESTART 1) => Return to read-eval-print level 1.
  296. (define x y z)
  297. ;Ill-formed syntax: (define x y z)
  298. ;To continue, call RESTART with an option number:
  299. ; (RESTART 1) => Return to read-eval-print level 1.
  300. (define x . y)
  301. ;Ill-formed syntax: (define x . y)
  302. ;To continue, call RESTART with an option number:
  303. ; (RESTART 1) => Return to read-eval-print level 1.
  304. ```
  305. As I've implemented those, let's test them:
  306. ```scheme
  307. ;;; EC-Eval input:
  308. (quote x . y)
  309. (error (Ill-formed syntax (quote x . y)))
  310. ;;; EC-Eval input:
  311. (quote)
  312. (error (Ill-formed syntax (quote)))
  313. ;;; EC-Eval input:
  314. (lambda)
  315. (error (Ill-formed syntax (lambda)))
  316. ;;; EC-Eval input:
  317. (lambda x)
  318. (error (Ill-formed syntax (lambda x)))
  319. ;;; EC-Eval input:
  320. (lambda x y . z)
  321. (error (Ill-formed syntax (lambda x y . z)))
  322. ;;; EC-Eval input:
  323. (x 1 . 4)
  324. (error (Ill-formed syntax (x 1 . 4)))
  325. ;;; EC-Eval input:
  326. (begin)
  327. (error (Ill-formed syntax (begin)))
  328. ;;; EC-Eval input:
  329. (begin x . y)
  330. (error (Ill-formed syntax (begin x . y)))
  331. ;;; EC-Eval input:
  332. (if)
  333. (error (Ill-formed syntax (if)))
  334. ;;; EC-Eval input:
  335. (if x)
  336. (error (Ill-formed syntax (if x)))
  337. ;;; EC-Eval input:
  338. (if 1 2 3 4)
  339. (error (Ill-formed syntax (if 1 2 3 4)))
  340. ;;; EC-Eval input:
  341. (if 1 . 2)
  342. (error (Ill-formed syntax (if 1 . 2)))
  343. ;;; EC-Eval input:
  344. (set!)
  345. (error (Ill-formed syntax (set!)))
  346. ;;; EC-Eval input:
  347. (set! x)
  348. (error (Ill-formed syntax (set! x)))
  349. ;;; EC-Eval input:
  350. (set! x y z)
  351. (error (Ill-formed syntax (set! x y z)))
  352. ;;; EC-Eval input:
  353. (set! x . y)
  354. (error (Ill-formed syntax (set! x . y)))
  355. ;;; EC-Eval input:
  356. (define)
  357. (error (Ill-formed syntax (define)))
  358. ;;; EC-Eval input:
  359. (define x y z)
  360. (error (Ill-formed syntax (define x y z)))
  361. ;;; EC-Eval input:
  362. (define x . y)
  363. (error (Ill-formed syntax (define x . y)))
  364. ;;; EC-Eval input:
  365. (define x 5)
  366. (total-pushes = 3 maximum-depth = 3)
  367. ;;; EC-Eval value:
  368. ok
  369. ;;; EC-Eval input:
  370. (set! x 3)
  371. (total-pushes = 3 maximum-depth = 3)
  372. ;;; EC-Eval value:
  373. ok
  374. ;;; EC-Eval input:
  375. x
  376. (total-pushes = 0 maximum-depth = 0)
  377. ;;; EC-Eval value:
  378. 3
  379. ```
  380. Of course the proper syntax expression works normally as expected.
  381. Note that as we depends on the `read` procedure for the parsing expression,
  382. unfortunately we cannot cope with the such errors handled by `read`:
  383. ```scheme
  384. ;;; EC-Eval input:
  385. (1 . 2 . 3)
  386. ;Ill-formed dotted list: (1 |.| 2 |.| 3)
  387. ;To continue, call RESTART with an option number:
  388. ; (RESTART 1) => Return to read-eval-print level 1.
  389. ```
  390. - b.
  391. Actually this is analogous task we have done prior to the task of **a.** to
  392. prevent from user producing our `error` ADT.
  393. The goal would be followings
  394. ```scheme
  395. (car 1)
  396. ;The object 1, passed as the first argument to car, is not the correct type.
  397. ;To continue, call RESTART with an option number:
  398. ; (RESTART 2) => Specify an argument to use in its place.
  399. ; (RESTART 1) => Return to read-eval-print level 1.
  400. (cdr 1)
  401. ;The object 1, passed as the first argument to cdr, is not the correct type.
  402. ;To continue, call RESTART with an option number:
  403. ; (RESTART 2) => Specify an argument to use in its place.
  404. ; (RESTART 1) => Return to read-eval-print level 1.
  405. (car 1 2 3)
  406. ;The procedure #[compiled-procedure 18 ("list" #x1) #x1a #x104588fc2] has been called with 3 arguments; it requires exactly 1 argument.
  407. ;To continue, call RESTART with an option number:
  408. ; (RESTART 1) => Return to read-eval-print level 1.
  409. ```
  410. Or more specifically we can learn from the "professional" product:
  411. ```scheme
  412. (pp cadr)
  413. (named-lambda (cadr x)
  414. ((named-lambda (safe-car x)
  415. (if (pair? x)
  416. (car x)
  417. (error:not-a pair? x 'safe-car)))
  418. ((named-lambda (safe-cdr x)
  419. (if (pair? x)
  420. (cdr x)
  421. (error:not-a pair? x 'safe-cdr)))
  422. x)))
  423. ```
  424. Or for the division case,
  425. ```scheme
  426. (/ 5 0)
  427. ;Division by zero signalled by /.
  428. ;To continue, call RESTART with an option number:
  429. ; (RESTART 1) => Return to read-eval-print level 1.
  430. ```
  431. We got
  432. ```scheme
  433. (pp /)
  434. (case number-of-arguments
  435. ((1)
  436. (named-lambda (complex:invert z)
  437. (if (object-type? 60 z)
  438. (let ((zr (system-pair-car z)) (zi (system-pair-cdr z)))
  439. (let ((d (real:+ (real:square zr) (real:square zi))))
  440. (system-pair-cons 60 (real:/ zr d) (real:/ (real:negate zi) d))))
  441. ((named-lambda (real:invert x) (if (object-type? 6 x) (let ((x x)) (flonum-divide 1. x)) (rat:invert x))) z))))
  442. ((2) (named-lambda (binary-/ z1 z2) (&/ z1 z2)))
  443. (else (named-lambda (/ self z1 . zs) (complex:/ z1 (reduce complex:* 1 zs)))))
  444. ;Unspecified return value
  445. ```
  446. We can test this feature with following definition:
  447. ```scheme
  448. (define test
  449. (lambda x
  450. (let ((number-of-arguments (length x)))
  451. (case number-of-arguments
  452. ((0) 'nullary)
  453. ((1) 'unary)
  454. ((2) 'binary)
  455. (else 'recursive)))))
  456. ```
  457. Then we got
  458. ```scheme
  459. (test 1)
  460. ;Value: unary
  461. (test 1 2)
  462. ;Value: binary
  463. (test 1 2 3)
  464. ;Value: recursive
  465. (test)
  466. ;Value: nullary
  467. ```
  468. From this experiment, we learned that to cope with the arity error, we need to
  469. take the argument as list and then check.
  470. Here is the half of that – for the fixed arity procedures:
  471. ```scheme
  472. (define cons*
  473. (check-error-with
  474. (named-lambda (cons x y) `(pair ,x ,y))
  475. 2))
  476. (define null?*
  477. (check-error-with
  478. (named-lambda (null? p)
  479. (and (tagged-list? p 'pair) (null? (cdr p))))
  480. 1))
  481. (define pair?*
  482. (check-error-with
  483. (named-lambda (pair? p) (tagged-list? p 'pair))
  484. 1))
  485. ;; (define (car* p)
  486. ;; (cadr p))
  487. (define car*
  488. (check-error-with
  489. (named-lambda (car p)
  490. (if (pair?* p)
  491. (cadr p)
  492. (make-error-exp `("The object " ,p " is not a pair -- car"))))
  493. 1))
  494. (define (check-error-with proc fixed-arity)
  495. (lambda x
  496. (let ((number-of-arguments (length x)))
  497. (if (= number-of-arguments fixed-arity)
  498. (apply proc x)
  499. (make-error-exp `("The procedure "
  500. ,proc
  501. " has been called with "
  502. ,number-of-arguments
  503. " arguments; it requires exactly "
  504. ,fixed-arity
  505. " argument."))))))
  506. (define cdr*
  507. (check-error-with
  508. (named-lambda (cdr p)
  509. (if (pair?* p)
  510. (caddr p)
  511. (make-error-exp `("The object " ,p " is not a pair -- cdr"))))
  512. 1))
  513. ;; (define (cdr* p)
  514. ;; (caddr p))
  515. ```
  516. Then `primitive-apply` subroutine got
  517. ```scheme
  518. primitive-apply
  519. (assign val (op apply-primitive-procedure)
  520. (reg proc)
  521. (reg argl))
  522. (test (op error-exp?) (reg val))
  523. (branch (label signal-error))
  524. (restore continue)
  525. (goto (reg continue))
  526. ```
  527. Let's test what we have done so far:
  528. ```scheme
  529. ;;; EC-Eval input:
  530. (car 1)
  531. (error (The object 1 is not a pair -- car))
  532. ;;; EC-Eval input:
  533. (cdr 1)
  534. (error (The object 1 is not a pair -- cdr))
  535. ;;; EC-Eval input:
  536. (car 1 2 3)
  537. (error (The procedure #[compound-procedure 24 car] has been called with 3 arguments; it requires exactly 1 argument.))
  538. ;;; EC-Eval input:
  539. (pair? (cons 1 2) 2 3)
  540. (error (The procedure #[compound-procedure 25 pair?] has been called with 3 arguments; it requires exactly 1 argument.))
  541. ;;; EC-Eval input:
  542. (cons 1 2 3)
  543. (error (The procedure #[compound-procedure 26 cons] has been called with 3 arguments; it requires exactly 2 argument.))
  544. ```
  545. So far so good!
  546. Let's turn to the rest. Our goals are
  547. ```scheme
  548. (pp <)
  549. (case number-of-arguments
  550. ((0) (named-lambda (nullary-<) #t))
  551. ((1) (named-lambda (unary-< z) (if (not (complex:real? z)) (error:wrong-type-argument z "real number" '<)) #t))
  552. ((2) (named-lambda (binary-< z1 z2) (&< z1 z2)))
  553. (else (named-lambda (< self . zs) (reduce-comparator complex:< zs '<))))
  554. ;Unspecified return value
  555. (pp +)
  556. (case number-of-arguments
  557. ((0) (named-lambda (nullary-+) 0))
  558. ((1) (named-lambda (unary-+ z) (if (not (complex:complex? z)) (error:wrong-type-argument z "number" '+)) z))
  559. ((2) (named-lambda (binary-+ z1 z2) (&+ z1 z2)))
  560. (else (named-lambda (+ self . zs) (reduce complex:+ 0 zs))))
  561. ;Unspecified return value
  562. (pp -)
  563. (case number-of-arguments
  564. ((1)
  565. (named-lambda (complex:negate z)
  566. (if (object-type? 60 z)
  567. (system-pair-cons 60 (real:negate (system-pair-car z)) (real:negate (system-pair-cdr z)))
  568. ((named-lambda (real:negate x)
  569. (if (object-type? 6 x)
  570. (flonum-negate x)
  571. ((named-lambda (rat:negate v/v*) (if (object-type? 58 v/v*) (system-pair-cons 58 (integer-negate (system-pair-car v/v*)) (system-pair-cdr v/v*)) (integer-negate v/v*))) x)))
  572. z))))
  573. ((2) (named-lambda (binary-- z1 z2) (&- z1 z2)))
  574. (else (named-lambda (- self z1 . zs) (complex:- z1 (reduce complex:+ 0 zs)))))
  575. ;Unspecified return value
  576. (pp *)
  577. (case number-of-arguments
  578. ((0) (named-lambda (nullary-*) 1))
  579. ((1) (named-lambda (unary-* z) (if (not (complex:complex? z)) (error:wrong-type-argument z "number" '*)) z))
  580. ((2) (named-lambda (binary-* z1 z2) (&* z1 z2)))
  581. (else (named-lambda (* self . zs) (reduce complex:* 1 zs))))
  582. ;Unspecified return value
  583. (pp =)
  584. (case number-of-arguments
  585. ((0) (named-lambda (nullary-=) #t))
  586. ((1) (named-lambda (unary-= z) (if (not (complex:complex? z)) (error:wrong-type-argument z "complex number" '=)) #t))
  587. ((2) (named-lambda (binary-= z1 z2) (&= z1 z2)))
  588. (else (named-lambda (= self . zs) (reduce-comparator complex:= zs '=))))
  589. ;Unspecified return value
  590. (pp /)
  591. (case number-of-arguments
  592. ((1)
  593. (named-lambda (complex:invert z)
  594. (if (object-type? 60 z)
  595. (let ((zr (system-pair-car z)) (zi (system-pair-cdr z)))
  596. (let ((d (real:+ (real:square zr) (real:square zi))))
  597. (system-pair-cons 60 (real:/ zr d) (real:/ (real:negate zi) d))))
  598. ((named-lambda (real:invert x) (if (object-type? 6 x) (let ((x x)) (flonum-divide 1. x)) (rat:invert x))) z))))
  599. ((2) (named-lambda (binary-/ z1 z2) (&/ z1 z2)))
  600. (else (named-lambda (/ self z1 . zs) (complex:/ z1 (reduce complex:* 1 zs)))))
  601. ;Unspecified return value
  602. ```
  603. So to summarize,
  604. - All of the arguments of above operations should be `number?`.
  605. - In addition to that, for the comparing operations – `<` and `>` – should be `real?`.
  606. - Especially for the division, except for the first dividend, 0 should not be
  607. passed; if the number of argument were 1, that number shouldn't be 0.
  608. - Except division and subtraction, it is okay with any arity; for those, it
  609. should be at least 1 argument.
  610. Here is the code:
  611. ```scheme
  612. (define (ensure-arity-number-gt minimum-arity proc)
  613. (lambda x
  614. (let ((number-of-arguments (length x)))
  615. (if (< number-of-arguments minimum-arity)
  616. (make-error-exp `("The procedure "
  617. ,proc
  618. " has been called with "
  619. ,number-of-arguments
  620. " arguments; it requires at least "
  621. ,minimum-arity
  622. " argument."))
  623. (apply proc x)))))
  624. (define (all-arguments-to-be? pred? args)
  625. (let loop
  626. ((args args))
  627. (if (null? args)
  628. true
  629. (and (pred? (car args))
  630. (loop (cdr args))))))
  631. (define -*
  632. (ensure-arity-number-gt
  633. 1
  634. (named-lambda (- . as)
  635. (if (all-arguments-to-be? number? as)
  636. (apply - as)
  637. (make-error-exp
  638. `("The object " ,as
  639. ", passed as an argument to -, is not a number."))))))
  640. (define /*
  641. (ensure-arity-number-gt
  642. 1
  643. (named-lambda (/ a . as)
  644. (if (all-arguments-to-be? number? (cons a as))
  645. (if (or (and (null? as) (zero? a))
  646. (not (all-arguments-to-be?
  647. (lambda (a) (not (zero? a)))
  648. as)))
  649. (make-error-exp
  650. `("Division by zero signalled by / from arguments "
  651. ,(cons a as)))
  652. (apply / (cons a as)))
  653. (make-error-exp
  654. `("The object " ,as
  655. ", passed as an argument to -, is not a number."))))))
  656. (define =*
  657. (named-lambda (= . as)
  658. (if (all-arguments-to-be? number? as)
  659. (apply = as)
  660. (make-error-exp
  661. `("The object " ,as
  662. ", passed as an argument to =, is not a number.")))))
  663. (define +*
  664. (named-lambda (+ . as)
  665. (if (all-arguments-to-be? number? as)
  666. (apply + as)
  667. (make-error-exp
  668. `("The object " ,as
  669. ", passed as an argument to +, is not a number.")))))
  670. (define **
  671. (named-lambda (* . as)
  672. (if (all-arguments-to-be? number? as)
  673. (apply * as)
  674. (make-error-exp
  675. `("The object " ,as
  676. ", passed as an argument to *, is not a number.")))))
  677. (define <*
  678. (named-lambda (< . as)
  679. (if (all-arguments-to-be? real? as)
  680. (apply < as)
  681. (make-error-exp
  682. `("The object " ,as
  683. ", passed as an argument to <, is not a real number.")))))
  684. (define >*
  685. (named-lambda (> . as)
  686. (if (all-arguments-to-be? real? as)
  687. (apply > as)
  688. (make-error-exp
  689. `("The object " ,as
  690. ", passed as an argument to >, is not a real number.")))))
  691. ```
  692. Now let's test it:
  693. ```scheme
  694. ;;; EC-Eval input:
  695. (+ (cons 2 3))
  696. (error (The object ((pair 2 3)) , passed as an argument to +, is not a number.))
  697. ;;; EC-Eval input:
  698. (/ 1 0)
  699. (error (Division by zero signalled by / from arguments (1 0)))
  700. ;;; EC-Eval input:
  701. (-)
  702. (error (The procedure #[compound-procedure 30 -] has been called with 0 arguments; it requires at least 1 argument.))
  703. ;;; EC-Eval input:
  704. (+)
  705. (total-pushes = 3 maximum-depth = 3)
  706. ;;; EC-Eval value:
  707. 0
  708. ;;; EC-Eval input:
  709. (*)
  710. (total-pushes = 3 maximum-depth = 3)
  711. ;;; EC-Eval value:
  712. 1
  713. ;;; EC-Eval input:
  714. (> (cons 2 3))
  715. (error (The object ((pair 2 3)) , passed as an argument to >, is not a real number.))
  716. ;;; EC-Eval input:
  717. (/ 0)
  718. (error (Division by zero signalled by / from arguments (0)))
  719. ```
  720. If we want to hide the `pair` representation we are good to use the following
  721. helper procedures:
  722. ```scheme
  723. (define (tree-map* leaf-op combine-op initial tree)
  724. (cond ((null?* tree) initial)
  725. ((not (pair?* tree)) (leaf-op tree))
  726. (else ;pair
  727. (combine-op
  728. (tree-map* leaf-op combine-op initial
  729. (car* tree))
  730. (tree-map* leaf-op combine-op initial
  731. (cdr* tree))))))
  732. (define (pair*->pair p)
  733. (tree-map* identity-procedure cons '() p))
  734. (define (represent-object* o)
  735. (if (pair?* o)
  736. (pair*->pair o)
  737. o))
  738. ```
  739. Then
  740. ```scheme
  741. (define >*
  742. (named-lambda (> . as)
  743. (if (all-arguments-to-be? real? as)
  744. (apply > as)
  745. (make-error-exp
  746. `("The object" ,(represent-object* as)
  747. ", passed as an argument to >, is not a real number.")))))
  748. ```
  749. Now test this new behavior:
  750. ```scheme
  751. ;;; EC-Eval input:
  752. (> (cons 2 3))
  753. (error (The object ((pair 2 3)) , passed as an argument to >, is not a real number.))
  754. ```
  755. Turns out we need to map over the argument list:
  756. ```scheme
  757. (define -*
  758. (ensure-arity-number-gt
  759. 1
  760. (named-lambda (- . as)
  761. (if (all-arguments-to-be? number? as)
  762. (apply - as)
  763. (make-error-exp
  764. `("The object" ,(map represent-object* as)
  765. ", passed as an argument to -, is not a number."))))))
  766. ```
  767. Then re-run!
  768. ```scheme
  769. ;;; EC-Eval input:
  770. (> (cons 2 3))
  771. (error (The object ((2 . 3)) , passed as an argument to >, is not a real number.))
  772. ;;; EC-Eval input:
  773. (* (cons 2 3) (cons 1 (cons 3 2)))
  774. (error (The object ((2 . 3) (1 3 . 2)) , passed as an argument to *, is not a number.))
  775. ```
  776. Now works as expected.

Compilation

Compilation is another strategy that takes opposite to what explicit control
evaluator takes: It lowers the higher level language into the very lower level
language, natural language or assembly language. And this translated program
would be interpreted by the machine language interpreter – CPU – after
assembled.

This different strategy causes the great deal of changes of properties from the
evaluator. In evaluator, as it loads all the library – the collection of
subroutines that implements the primitive operations of source language – since
it does not what subroutines are going to be needed in interpreting, we can
develop the program dynamically. On the other hand, in the compile method, only the
required subroutines are going to be loaded in execution, since it knows what
the source code used in priori.

This is analogous situation from section 4.1.7; the compiler analyze or optimize
the instructions using the informations of the source code that can be deducted
in static manner. We should come to understand why modern computer programming
language especially the one for system program, use the strongly typed language:
The compiler can deduce more information from the code than weakly typed one
since it knows what going to be returned and what would be passed, and so on in
compile time in addition to the type inference for assisting the programmer.

As Scheme is the dynamically typed language, the compiler can not do the clever
things done by the compiler of strongly typed language like Java or Scala or
Clojure; although this fact, the compiler can works a lot than the evaluator.

The main idea that is implemented in this section is annotate between the
instruction sequences; and using that information, compiler can save additional
stack operations around instruction sequences. It is embodied by structural
definition over the compiler clause structure: Defining manually for all the
primitive clauses and inductive combination rule.

In addition to this quite “clever” idea, we can eliminates all the evaluator
specific instructions – e.g. the instructions involving the exp and unev
registers and the continue to go to next entry point, and so on. Since our
compiler is not the program in the machine language but in the Scheme – higher
level language, it can manipulate the given expression directly using the
pointer to the data structure allocated heap (or list structured memory).

Structure of the Compiler

  • Exercise 5.31

    You should look through the lecture, 10A of SICP, since the relevant and useful
    diagram appears in the lecture. I’ve drawn that diagram based on the given
    examples with my digital paper.

    For the conclusion, the first one and second one can be eliminated all the stack
    operations otherwise involved, and third one results in pair of env save and
    store, that of proc, and that of argl register. For the last one, pair of
    proc and argl survived.

  • Exercise 5.32

    • a.

      1. ev-application
      2. ;; Error handling
      3. (assign val (op error-exp-if-ill-formed-combination) (reg exp))
      4. (test (op error-exp?) (reg val))
      5. (branch (label signal-error))
      6. ;;
      7. (save continue)
      8. (assign unev (op operands) (reg exp))
      9. (assign exp (op operator) (reg exp))
      10. ;; Exercise 5.32a
      11. (test (op variable?) (reg exp))
      12. (branch (label ev-appl-sym-op))
      13. (save env)
      14. (save unev)
      15. (assign continue (label ev-appl-did-operator))
      16. (goto (label eval-dispatch))
      17. ev-appl-sym-op
      18. (assign continue (label ev-appl-did-sym-op))
      19. (goto (label ev-variable))
      20. ev-appl-did-operator
      21. (restore unev)
      22. (restore env)
      23. ev-appl-did-sym-op
      24. (assign argl (op empty-arglist))
      25. (assign proc (reg val))
      26. (test (op no-operands?) (reg unev))
      27. (branch (label apply-dispatch))
      28. (save proc)
      29. ...

      Using the information that the operator expression is symbol, we could reduce
      the 2 pair of stack operation from previous version.

    • b.

      Alyssa P. Hacker ignores the time it takes to analyze the instructions to
      optimize the code. As we already have seen in section 4.1.7, the analysis times
      can not be ignored especially if it trying to extract great deal of information
      from the code, and then exploit that fact to optimize.

      So if we do analyze in the execution time, it will have overhead when the code
      executed for the first time; well, it can achieve efficient execution after that
      evaluation but the overhead would not the one user ever expected.

      On the other hand, the compiler can analyze the code in compile time, which is
      not the execution time, so the user get the behavior of the program consistently
      with their expectation – it takes same time for execution. So compiler can
      introduce more sophisticated algorithm to improve the efficiency of resulting
      code since it is done once and compile time.

Compiling Expressions

Compiling Combinations

Combining Instruction Sequences

An Example of Compiled Code

  • Exercise 5.33

    Before compiling this altered one, we can expect what difference it would make
    from the informal (or the code) from this section. We expect this altered one
    and original one different in view point of efficiency. Here is why:

    Since this altered version place the recursive call to the last argument of last
    combination expression, it needs to preserve the env register around the
    compilation of last argument (since in our compiler, last argument would be
    compiled first and the first to be last); since it alter env and the first
    code compiled needs env, env should be wrapped by save and restore
    around the compilation of last argument – recursive call.

    However the original version compile the recursive call lastly (or put
    in other way, appended at the very end), and the last argument does not modify
    the env register – lookup-variable-value – it saves pair of save &
    restore stack operation compared with altered version.

    Consequently the original one would be more efficient. Then let’s verify this
    fact:

    1. (compile
    2. '(define (factorial-alt n)
    3. (if (= n 1)
    4. 1
    5. (* n (factorial-alt (- n 1)))))
    6. 'val
    7. 'next)
    8. ((env)
    9. (val)
    10. ((assign val (op make-compiled-procedure) (label entry2) (reg env))
    11. (goto (label after-lambda1))
    12. entry2
    13. (assign env (op compiled-procedure-env) (reg proc))
    14. (assign env (op extend-environment) (const (n)) (reg argl) (reg env))
    15. (save continue)
    16. (save env)
    17. (assign proc (op lookup-variable-value) (const =) (reg env))
    18. (assign val (const 1))
    19. (assign argl (op list) (reg val))
    20. (assign val (op lookup-variable-value) (const n) (reg env))
    21. (assign argl (op cons) (reg val) (reg argl))
    22. (test (op primitive-procedure?) (reg proc))
    23. (branch (label primitive-branch17))
    24. compiled-branch16
    25. (assign continue (label after-call15))
    26. (assign val (op compiled-procedure-entry) (reg proc))
    27. (goto (reg val))
    28. primitive-branch17
    29. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    30. after-call15
    31. (restore env)
    32. (restore continue)
    33. (test (op false?) (reg val))
    34. (branch (label false-branch4))
    35. true-branch5
    36. (assign val (const 1))
    37. (goto (reg continue))
    38. false-branch4
    39. (assign proc (op lookup-variable-value) (const *) (reg env))
    40. (save continue)
    41. (save proc)
    42. (save env) ;this is the major difference
    43. (assign proc (op lookup-variable-value) (const factorial-alt) (reg env))
    44. (save proc)
    45. (assign proc (op lookup-variable-value) (const -) (reg env))
    46. (assign val (const 1))
    47. (assign argl (op list) (reg val))
    48. (assign val (op lookup-variable-value) (const n) (reg env))
    49. (assign argl (op cons) (reg val) (reg argl))
    50. (test (op primitive-procedure?) (reg proc))
    51. (branch (label primitive-branch8))
    52. compiled-branch7
    53. (assign continue (label after-call6))
    54. (assign val (op compiled-procedure-entry) (reg proc))
    55. (goto (reg val))
    56. primitive-branch8
    57. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    58. after-call6
    59. (assign argl (op list) (reg val))
    60. (restore proc)
    61. (test (op primitive-procedure?) (reg proc))
    62. (branch (label primitive-branch11))
    63. compiled-branch10
    64. (assign continue (label after-call9))
    65. (assign val (op compiled-procedure-entry) (reg proc))
    66. (goto (reg val))
    67. primitive-branch11
    68. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    69. after-call9
    70. (assign argl (op list) (reg val))
    71. (restore env) ;***
    72. (assign val (op lookup-variable-value) (const n) (reg env))
    73. (assign argl (op cons) (reg val) (reg argl))
    74. (restore proc)
    75. (restore continue)
    76. (test (op primitive-procedure?) (reg proc))
    77. (branch (label primitive-branch14))
    78. compiled-branch13
    79. (assign val (op compiled-procedure-entry) (reg proc))
    80. (goto (reg val))
    81. primitive-branch14
    82. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    83. (goto (reg continue))
    84. after-call12
    85. after-if3
    86. after-lambda1
    87. (perform (op define-variable!) (const factorial-alt) (reg val) (reg env))
    88. (assign val (const ok))))

    Unfortunately our expectation is invalid: The number of instructions are same in
    both; the original one should argl register rather than env register. So the
    overall efficiency would equal opposed our first expectation since the
    accumulated stack contents equal in both.

  • Exercise 5.34

    Here is the result:

    1. ((env)
    2. (val)
    3. (
    4. ;; construct the procedure and skip over code for the procedure body
    5. (assign val (op make-compiled-procedure) (label entry2) (reg env))
    6. (goto (label after-lambda1))
    7. entry2 ;calls to factorial will enter here
    8. (assign env (op compiled-procedure-env) (reg proc))
    9. (assign env (op extend-environment) (const (n)) (reg argl) (reg env))
    10. ;; begin the procedure body
    11. ;; it actually first define internal procedure
    12. ;; so, it construct the internal procedure and skip over code for the
    13. ;; interanl procedure body
    14. (assign val (op make-compiled-procedure) (label entry7) (reg env))
    15. (goto (label after-lambda6))
    16. entry7 ;internal procedure call will enter here
    17. (assign env (op compiled-procedure-env) (reg proc))
    18. (assign env (op extend-environment) (const (product counter)) (reg argl) (reg env))
    19. ;; actual iterative process starts
    20. (save continue)
    21. (save env)
    22. ;; compute (> counter n)
    23. (assign proc (op lookup-variable-value) (const >) (reg env))
    24. (assign val (op lookup-variable-value) (const n) (reg env))
    25. (assign argl (op list) (reg val))
    26. (assign val (op lookup-variable-value) (const counter) (reg env))
    27. (assign argl (op cons) (reg val) (reg argl))
    28. (test (op primitive-procedure?) (reg proc))
    29. (branch (label primitive-branch22))
    30. compiled-branch21
    31. (assign continue (label after-call20))
    32. (assign val (op compiled-procedure-entry) (reg proc))
    33. (goto (reg val))
    34. primitive-branch22
    35. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    36. after-call20 ;val now contains result of (> counter n)
    37. (restore env)
    38. (restore continue)
    39. (test (op false?) (reg val))
    40. (branch (label false-branch9))
    41. true-branch10 ;return the value of product that bound to (factorial n)
    42. (assign val (op lookup-variable-value) (const product) (reg env))
    43. (goto (reg continue))
    44. false-branch9
    45. ;; compute and return (iter (* counter product) (+ counter 1))
    46. (assign proc (op lookup-variable-value) (const iter) (reg env))
    47. (save continue)
    48. (save proc)
    49. (save env)
    50. ;; compute (+ counter 1)
    51. (assign proc (op lookup-variable-value) (const +) (reg env))
    52. (assign val (const 1))
    53. (assign argl (op list) (reg val))
    54. (assign val (op lookup-variable-value) (const counter) (reg env))
    55. (assign argl (op cons) (reg val) (reg argl))
    56. (test (op primitive-procedure?) (reg proc))
    57. (branch (label primitive-branch16))
    58. compiled-branch15
    59. (assign continue (label after-call14))
    60. (assign val (op compiled-procedure-entry) (reg proc))
    61. (goto (reg val))
    62. primitive-branch16
    63. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    64. after-call14 ;val now contains result of (+ counter 1)
    65. (assign argl (op list) (reg val))
    66. (restore env)
    67. (save argl)
    68. ;; compute (* counter product)
    69. (assign proc (op lookup-variable-value) (const *) (reg env))
    70. (assign val (op lookup-variable-value) (const product) (reg env))
    71. (assign argl (op list) (reg val))
    72. (assign val (op lookup-variable-value) (const counter) (reg env))
    73. (assign argl (op cons) (reg val) (reg argl))
    74. (test (op primitive-procedure?) (reg proc))
    75. (branch (label primitive-branch13))
    76. compiled-branch12
    77. (assign continue (label after-call11))
    78. (assign val (op compiled-procedure-entry) (reg proc))
    79. (goto (reg val))
    80. primitive-branch13
    81. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    82. after-call11 ;val now contains result of (* counter product)
    83. (restore argl)
    84. (assign argl (op cons) (reg val) (reg argl))
    85. (restore proc) ;restore iter
    86. (restore continue)
    87. ;; apply iter
    88. (test (op primitive-procedure?) (reg proc))
    89. (branch (label primitive-branch19))
    90. compiled-branch18
    91. ;; note that a compound procedure here is called tail-recursively
    92. (assign val (op compiled-procedure-entry) (reg proc))
    93. (goto (reg val))
    94. primitive-branch19
    95. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    96. (goto (reg continue))
    97. after-call17 ;val now contains (factorial n)
    98. ;; there is no restore operations after this point
    99. ;; which means there is no defered operations and arguments --
    100. ;; the stack doesn't grow and get reduced before and after the
    101. ;; recursive call; this is why it is iterative process but above.
    102. after-if8
    103. after-lambda6 ;end of procedure body of iter
    104. ;; assign the procedure to the variable iter (internally)
    105. (perform (op define-variable!) (const iter) (reg val) (reg env))
    106. (assign val (const ok))
    107. ;; setup the initial call of iter -- (iter 1 1)
    108. (assign proc (op lookup-variable-value) (const iter) (reg env))
    109. (assign val (const 1))
    110. (assign argl (op list) (reg val))
    111. (assign val (const 1))
    112. (assign argl (op cons) (reg val) (reg argl))
    113. (test (op primitive-procedure?) (reg proc))
    114. (branch (label primitive-branch5))
    115. compiled-branch4
    116. ;; note also that this compound procedure call is tail-recursive
    117. (assign val (op compiled-procedure-entry) (reg proc))
    118. (goto (reg val))
    119. primitive-branch5
    120. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    121. (goto (reg continue))
    122. after-call3
    123. after-lambda1 ;end of procedure body of factorial
    124. ;; assign the procedure to the variable factorial
    125. (perform (op define-variable!) (const factorial) (reg val) (reg env))
    126. (assign val (const ok))))

    We included the analysis about the essential difference within the annotation.

  • Exercise 5.35

    1. (assign val
    2. (op make-compiled-procedure) ;compilation of lambda expression
    3. (label entry16)
    4. (reg env))
    5. (goto (label after-lambda15))
    6. entry16
    7. (assign env (op compiled-procedure-env) (reg proc))
    8. (assign env
    9. (op extend-environment)
    10. (const (x)) ;formal parameter list is (x)
    11. (reg argl)
    12. (reg env))
    13. ;; the actual procedure body of f
    14. (assign proc
    15. (op lookup-variable-value)
    16. (const +) ;compliation of application of (+ ...)
    17. (reg env))
    18. (save continue)
    19. (save proc)
    20. (save env)
    21. ;; the last argument of given application was actually another application expression
    22. ;; (g ...)
    23. (assign proc (op lookup-variable-value) (const g) (reg env))
    24. (save proc)
    25. ;; the last argument of inner application was yet another application (+ ...)
    26. (assign proc (op lookup-variable-value) (const +) (reg env))
    27. ;; the last argument of innermost application was 2
    28. (assign val (const 2))
    29. (assign argl (op list) (reg val))
    30. ;; the next one (the argument in front of 2) was variable x
    31. (assign val (op lookup-variable-value) (const x) (reg env))
    32. (assign argl (op cons) (reg val) (reg argl))
    33. ;; start procedure call
    34. ;; which means argument list was (x 2)
    35. (test (op primitive-procedure?) (reg proc))
    36. (branch (label primitive-branch19))
    37. compiled-branch18
    38. (assign continue (label after-call17))
    39. (assign val (op compiled-procedure-entry) (reg proc))
    40. (goto (reg val))
    41. primitive-branch19
    42. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    43. after-call17 ;val now contains the value of (+ x 2)
    44. (assign argl (op list) (reg val))
    45. ;; turns out (+ x 2) was the only argument of application of procedure g
    46. (restore proc)
    47. (test (op primitive-procedure?) (reg proc))
    48. (branch (label primitive-branch22))
    49. compiled-branch21
    50. (assign continue (label after-call20))
    51. (assign val (op compiled-procedure-entry) (reg proc))
    52. (goto (reg val))
    53. primitive-branch22
    54. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    55. after-call20 ;now val contains (g (+ x 2))
    56. (assign argl (op list) (reg val))
    57. (restore env)
    58. ;; the next argument of outermost application was variable x
    59. (assign val (op lookup-variable-value) (const x) (reg env))
    60. (assign argl (op cons) (reg val) (reg argl))
    61. ;; the outermost application was (+ x (g (+ x 2)))
    62. (restore proc)
    63. (restore continue)
    64. (test (op primitive-procedure?) (reg proc))
    65. (branch (label primitive-branch25))
    66. compiled-branch24
    67. (assign val (op compiled-procedure-entry) (reg proc))
    68. (goto (reg val))
    69. primitive-branch25
    70. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    71. (goto (reg continue))
    72. after-call23 ;now val contains the value of (+ x (g (+ x 2)))
    73. after-lambda15
    74. ;; the whole expression was defintion that binds f to the given lambda expression
    75. (perform (op define-variable!) (const f) (reg val) (reg env))
    76. (assign val (const ok))
    77. ;;end of exercise

    We analyzed given code and annotated it; the result being posted above. From
    this facts we can reconfigure what source code produced this instructions:

    1. (define (f x)
    2. (+ x (g (+ x 2))))

    let’s verify this:

    1. (compile
    2. '(define (f x)
    3. (+ x (g (+ x 2))))
    4. 'val
    5. 'next)
    1. ((env)
    2. (val)
    3. ((assign val (op make-compiled-procedure) (label entry2) (reg env))
    4. (goto (label after-lambda1))
    5. entry2
    6. (assign env (op compiled-procedure-env) (reg proc))
    7. (assign env (op extend-environment) (const (x)) (reg argl) (reg env))
    8. (assign proc (op lookup-variable-value) (const +) (reg env))
    9. (save continue)
    10. (save proc)
    11. (save env)
    12. (assign proc (op lookup-variable-value) (const g) (reg env))
    13. (save proc)
    14. (assign proc (op lookup-variable-value) (const +) (reg env))
    15. (assign val (const 2))
    16. (assign argl (op list) (reg val))
    17. (assign val (op lookup-variable-value) (const x) (reg env))
    18. (assign argl (op cons) (reg val) (reg argl))
    19. (test (op primitive-procedure?) (reg proc))
    20. (branch (label primitive-branch5))
    21. compiled-branch4
    22. (assign continue (label after-call3))
    23. (assign val (op compiled-procedure-entry) (reg proc))
    24. (goto (reg val))
    25. primitive-branch5
    26. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    27. after-call3
    28. (assign argl (op list) (reg val))
    29. (restore proc)
    30. (test (op primitive-procedure?) (reg proc))
    31. (branch (label primitive-branch8))
    32. compiled-branch7
    33. (assign continue (label after-call6))
    34. (assign val (op compiled-procedure-entry) (reg proc))
    35. (goto (reg val))
    36. primitive-branch8
    37. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    38. after-call6
    39. (assign argl (op list) (reg val))
    40. (restore env)
    41. (assign val (op lookup-variable-value) (const x) (reg env))
    42. (assign argl (op cons) (reg val) (reg argl))
    43. (restore proc)
    44. (restore continue)
    45. (test (op primitive-procedure?) (reg proc))
    46. (branch (label primitive-branch11))
    47. compiled-branch10
    48. (assign val (op compiled-procedure-entry) (reg proc))
    49. (goto (reg val))
    50. primitive-branch11
    51. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    52. (goto (reg continue))
    53. after-call9
    54. after-lambda1
    55. (perform (op define-variable!) (const f) (reg val) (reg env))
    56. (assign val (const ok))))
  • Exercise 5.36

    Our compiler produces right-to-left order of evaluation for operands of
    combination in contrast to the explicit control evaluator in preceding section.
    It is determined by the construct-arglist in the compile-application.

    To change this order according to previous left-to-right order, all we need to
    change is the construct-arglist and code-to-get-last-arg not to use
    reverse and to use append instead of cons respectively:

    1. (define (construct-arglist operand-codes) ;no reverse!
    2. (if (null? operand-codes)
    3. (make-instruction-sequence '() '(argl)
    4. '((assign argl (const ()))))
    5. (let ((code-to-get-last-arg
    6. (append-instruction-sequences
    7. (car operand-codes)
    8. (make-instruction-sequence '(val) '(argl)
    9. '((assign argl (op list) (reg val)))))))
    10. (if (null? (cdr operand-codes))
    11. code-to-get-last-arg
    12. (preserving '(env)
    13. code-to-get-last-arg
    14. (code-to-get-rest-args
    15. (cdr operand-codes)))))))
    16. (define (code-to-get-rest-args operand-codes)
    17. (let ((code-for-next-arg
    18. (preserving '(argl)
    19. (car operand-codes)
    20. (make-instruction-sequence
    21. '(val argl) '(val argl) ;updated
    22. '((assign val (op list) (reg val)) ;changed
    23. (assign argl
    24. (op append) (reg argl) (reg val))))))) ;changed
    25. (if (null? (cdr operand-codes))
    26. code-for-next-arg
    27. (preserving '(env)
    28. code-for-next-arg
    29. (code-to-get-rest-args (cdr operand-codes))))))

    Now the instruction appending each argument code to the argument list being
    constructed code got doubled – from cons to make singleton list from newly
    constructed argument and then append that to argument list in constructing.

    Moreover, now the complexity of the constructing the whole argument list got
    quadratic order of growth rather than linear order as previous.

  • Exercise 5.37

    This is the place where the exercise 5.31 can play its role or be verified
    concretely. First, let’s modify the preserving procedure so that it always
    save and restore given registers around the first instruction sequence:

    1. (define (preserving regs seq1 seq2)
    2. (if (null? regs)
    3. (append-instruction-sequences seq1 seq2)
    4. (let ((first-reg (car regs)))
    5. (preserving (cdr regs)
    6. (make-instruction-sequence
    7. (list-union (list first-reg)
    8. (registers-needed seq1))
    9. (list-difference (registers-modified seq1)
    10. (list first-reg))
    11. (append `((save ,first-reg))
    12. (statements seq1)
    13. `((restore ,first-reg))))
    14. seq2))))

    Then let’s run this with the 4 cases of exercise 5.31

    • Case 1: (f 'x 'y)

      Modified version:

      1. ((env continue)
      2. (env proc argl continue val)
      3. ((save continue)
      4. (save env)
      5. (save continue)
      6. (assign proc (op lookup-variable-value) (const f) (reg env))
      7. (restore continue)
      8. (restore env)
      9. (restore continue)
      10. (save continue)
      11. (save proc)
      12. (save env)
      13. (save continue)
      14. (assign val (const y))
      15. (restore continue)
      16. (assign argl (op list) (reg val))
      17. (restore env)
      18. (save argl)
      19. (save continue)
      20. (assign val (const x))
      21. (restore continue)
      22. (restore argl)
      23. (assign argl (op cons) (reg val) (reg argl))
      24. (restore proc)
      25. (restore continue)
      26. (test (op primitive-procedure?) (reg proc))
      27. (branch (label primitive-branch3))
      28. compiled-branch2
      29. (assign continue (label after-call1))
      30. (assign val (op compiled-procedure-entry) (reg proc))
      31. (goto (reg val))
      32. primitive-branch3
      33. (save continue)
      34. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      35. (restore continue)
      36. after-call1))

      Original version:

      1. ((env)
      2. (env proc argl continue val)
      3. ((assign proc (op lookup-variable-value) (const f) (reg env))
      4. (assign val (const y))
      5. (assign argl (op list) (reg val))
      6. (assign val (const x))
      7. (assign argl (op cons) (reg val) (reg argl))
      8. (test (op primitive-procedure?) (reg proc))
      9. (branch (label primitive-branch6))
      10. compiled-branch5
      11. (assign continue (label after-call4))
      12. (assign val (op compiled-procedure-entry) (reg proc))
      13. (goto (reg val))
      14. primitive-branch6
      15. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      16. after-call4))

      34 instructions versus 14 instructions; aren’t impressive?

    • Case 2: ((f) 'x 'y)

      Modified version:

      1. ((env continue)
      2. (env proc argl continue val)
      3. ((save continue)
      4. (save env)
      5. (save continue)
      6. (save env)
      7. (save continue)
      8. (assign proc (op lookup-variable-value) (const f) (reg env))
      9. (restore continue)
      10. (restore env)
      11. (restore continue)
      12. (save continue)
      13. (save proc)
      14. (assign argl (const ()))
      15. (restore proc)
      16. (restore continue)
      17. (test (op primitive-procedure?) (reg proc))
      18. (branch (label primitive-branch9))
      19. compiled-branch8
      20. (assign continue (label proc-return10))
      21. (assign val (op compiled-procedure-entry) (reg proc))
      22. (goto (reg val))
      23. proc-return10
      24. (assign proc (reg val))
      25. (goto (label after-call7))
      26. primitive-branch9
      27. (save continue)
      28. (assign proc (op apply-primitive-procedure) (reg proc) (reg argl))
      29. (restore continue)
      30. after-call7
      31. (restore env)
      32. (restore continue)
      33. (save continue)
      34. (save proc)
      35. (save env)
      36. (save continue)
      37. (assign val (const y))
      38. (restore continue)
      39. (assign argl (op list) (reg val))
      40. (restore env)
      41. (save argl)
      42. (save continue)
      43. (assign val (const x))
      44. (restore continue)
      45. (restore argl)
      46. (assign argl (op cons) (reg val) (reg argl))
      47. (restore proc)
      48. (restore continue)
      49. (test (op primitive-procedure?) (reg proc))
      50. (branch (label primitive-branch13))
      51. compiled-branch12
      52. (assign continue (label after-call11))
      53. (assign val (op compiled-procedure-entry) (reg proc))
      54. (goto (reg val))
      55. primitive-branch13
      56. (save continue)
      57. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      58. (restore continue)
      59. after-call11))

      Original version:

      1. ((env)
      2. (env proc argl continue val)
      3. ((assign proc (op lookup-variable-value) (const f) (reg env))
      4. (assign argl (const ()))
      5. (test (op primitive-procedure?) (reg proc))
      6. (branch (label primitive-branch16))
      7. compiled-branch15
      8. (assign continue (label proc-return17))
      9. (assign val (op compiled-procedure-entry) (reg proc))
      10. (goto (reg val))
      11. proc-return17
      12. (assign proc (reg val))
      13. (goto (label after-call14))
      14. primitive-branch16
      15. (assign proc (op apply-primitive-procedure) (reg proc) (reg argl))
      16. after-call14
      17. (assign val (const y))
      18. (assign argl (op list) (reg val))
      19. (assign val (const x))
      20. (assign argl (op cons) (reg val) (reg argl))
      21. (test (op primitive-procedure?) (reg proc))
      22. (branch (label primitive-branch20))
      23. compiled-branch19
      24. (assign continue (label after-call18))
      25. (assign val (op compiled-procedure-entry) (reg proc))
      26. (goto (reg val))
      27. primitive-branch20
      28. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      29. after-call18))

      57 instructions versus 27 instructions.

    • Case 3: (f (g 'x) y)

      Modified version:

      1. ((env continue)
      2. (env proc argl continue val)
      3. ((save continue)
      4. (save env)
      5. (save continue)
      6. (assign proc (op lookup-variable-value) (const f) (reg env))
      7. (restore continue)
      8. (restore env)
      9. (restore continue)
      10. (save continue)
      11. (save proc)
      12. (save env)
      13. (save continue)
      14. (assign val (op lookup-variable-value) (const y) (reg env))
      15. (restore continue)
      16. (assign argl (op list) (reg val))
      17. (restore env)
      18. (save argl)
      19. (save continue)
      20. (save env)
      21. (save continue)
      22. (assign proc (op lookup-variable-value) (const g) (reg env))
      23. (restore continue)
      24. (restore env)
      25. (restore continue)
      26. (save continue)
      27. (save proc)
      28. (save continue)
      29. (assign val (const x))
      30. (restore continue)
      31. (assign argl (op list) (reg val))
      32. (restore proc)
      33. (restore continue)
      34. (test (op primitive-procedure?) (reg proc))
      35. (branch (label primitive-branch29))
      36. compiled-branch28
      37. (assign continue (label after-call27))
      38. (assign val (op compiled-procedure-entry) (reg proc))
      39. (goto (reg val))
      40. primitive-branch29
      41. (save continue)
      42. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      43. (restore continue)
      44. after-call27
      45. (restore argl)
      46. (assign argl (op cons) (reg val) (reg argl))
      47. (restore proc)
      48. (restore continue)
      49. (test (op primitive-procedure?) (reg proc))
      50. (branch (label primitive-branch32))
      51. compiled-branch31
      52. (assign continue (label after-call30))
      53. (assign val (op compiled-procedure-entry) (reg proc))
      54. (goto (reg val))
      55. primitive-branch32
      56. (save continue)
      57. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      58. (restore continue)
      59. after-call30))

      Original version:

      1. ((env)
      2. (env proc argl continue val)
      3. ((assign proc (op lookup-variable-value) (const f) (reg env))
      4. (save proc)
      5. (assign val (op lookup-variable-value) (const y) (reg env))
      6. (assign argl (op list) (reg val))
      7. (save argl)
      8. (assign proc (op lookup-variable-value) (const g) (reg env))
      9. (assign val (const x))
      10. (assign argl (op list) (reg val))
      11. (test (op primitive-procedure?) (reg proc))
      12. (branch (label primitive-branch23))
      13. compiled-branch22
      14. (assign continue (label after-call21))
      15. (assign val (op compiled-procedure-entry) (reg proc))
      16. (goto (reg val))
      17. primitive-branch23
      18. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      19. after-call21
      20. (restore argl)
      21. (assign argl (op cons) (reg val) (reg argl))
      22. (restore proc)
      23. (test (op primitive-procedure?) (reg proc))
      24. (branch (label primitive-branch26))
      25. compiled-branch25
      26. (assign continue (label after-call24))
      27. (assign val (op compiled-procedure-entry) (reg proc))
      28. (goto (reg val))
      29. primitive-branch26
      30. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      31. after-call24))

      57 versus 29.

    • Case 4: (f (g 'x) 'y)

      Modified version:

      1. ((env continue)
      2. (env proc argl continue val)
      3. ((save continue)
      4. (save env)
      5. (save continue)
      6. (assign proc (op lookup-variable-value) (const f) (reg env))
      7. (restore continue)
      8. (restore env)
      9. (restore continue)
      10. (save continue)
      11. (save proc)
      12. (save env)
      13. (save continue)
      14. (assign val (const y))
      15. (restore continue)
      16. (assign argl (op list) (reg val))
      17. (restore env)
      18. (save argl)
      19. (save continue)
      20. (save env)
      21. (save continue)
      22. (assign proc (op lookup-variable-value) (const g) (reg env))
      23. (restore continue)
      24. (restore env)
      25. (restore continue)
      26. (save continue)
      27. (save proc)
      28. (save continue)
      29. (assign val (const x))
      30. (restore continue)
      31. (assign argl (op list) (reg val))
      32. (restore proc)
      33. (restore continue)
      34. (test (op primitive-procedure?) (reg proc))
      35. (branch (label primitive-branch35))
      36. compiled-branch34
      37. (assign continue (label after-call33))
      38. (assign val (op compiled-procedure-entry) (reg proc))
      39. (goto (reg val))
      40. primitive-branch35
      41. (save continue)
      42. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      43. (restore continue)
      44. after-call33
      45. (restore argl)
      46. (assign argl (op cons) (reg val) (reg argl))
      47. (restore proc)
      48. (restore continue)
      49. (test (op primitive-procedure?) (reg proc))
      50. (branch (label primitive-branch38))
      51. compiled-branch37
      52. (assign continue (label after-call36))
      53. (assign val (op compiled-procedure-entry) (reg proc))
      54. (goto (reg val))
      55. primitive-branch38
      56. (save continue)
      57. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      58. (restore continue)
      59. after-call36))

      57 versus 29.

    • Conclusion

      By using the “annotate” feature of preserving, we can halve the instructions.

  • Exercise 5.38

    • a.

      Based on the problem statement, spread-arguments has form of
      (spread-arguments <operands>); and it should have following skeleton code:

      1. <complie first argument; targeted to arg1; linkage to next>
      2. <complie last (second) argument; targeted to arg2; linkage to next;
      3. preserving arg1 register>

      For the last linkage point is arbitrary; but if we think about the use cases
      followed by this sub-exercise, it implies we should link these code with next.

      In addition to these, we need to preserve the env register around the compiled
      code of first argument since the second argument need to be evaluated in that env.

      Here is the code:

      1. (define (spread-arguments args)
      2. (let ((a1 (car args))
      3. (a2 (cadr args)))
      4. (preserving
      5. '(env)
      6. (compile a1 'arg1 'next)
      7. (preserving
      8. '(arg1)
      9. (compile a2 'arg2 'next)
      10. (make-instruction-sequence
      11. '(arg1) '() '())))))

      Then test:

      1. (pp (spread-arguments '(x y)))
      2. ((env) (arg1 arg2)
      3. ((assign arg1 (op lookup-variable-value) (const x) (reg env))
      4. (assign arg2 (op lookup-variable-value) (const y) (reg env))))

      For the test of recursive case – compiling each argument can alter the
      preceding target register, we delegate after b. since for now we don’t have
      any compile clause that alter the argument registers.

    • b.

      Then we can easily implements the open-code clauses. First let’s implement the
      equal procedure:

      1. (define (compile-= exp target linkage)
      2. (end-with-linkage
      3. linkage
      4. (append-instruction-sequences
      5. (spread-arguments (operands exp))
      6. (make-instruction-sequence
      7. '(arg1 arg2)
      8. (list target)
      9. `((assign ,target (op =) (reg arg1) (reg arg2)))))))

      Then test:

      1. (pp (compile-= '(= 5 2) 'val 'next))
      2. (() (arg1 arg2 val)
      3. ((assign arg1 (const 5))
      4. (assign arg2 (const 2))
      5. (assign val (op =) (reg arg1) (reg arg2))))

      Let’s install it into the compile:

      1. (define (=? exp) (tagged-list? exp '=))

      And then

      1. *** in compile
      2. ((=? exp) (compile-= exp target linkage))
      3. ((application? exp)
      4. (compile-application exp target linkage))

      Then we can test the recursive case, which we couldn’t in a.

      1. (pp (compile '(= (= 5 2) (= 2 1))
      2. 'val 'next))
      3. (()
      4. (arg1 arg2 val)
      5. ((assign arg1 (const 5))
      6. (assign arg2 (const 2))
      7. (assign arg1 (op =) (reg arg1) (reg arg2))
      8. (save arg1)
      9. (assign arg1 (const 2))
      10. (assign arg2 (const 1))
      11. (assign arg2 (op =) (reg arg1) (reg arg2))
      12. (restore arg1)
      13. (assign val (op =) (reg arg1) (reg arg2))))

      As it verified, now let’s add the rest open-code primitives.

      Before implementing each of the primitives, observe the commonalities among the
      open-code things; the only difference is the operation name: Let’s exploit them!
      If we restrict the open code primitives only allowed to be binary, then we can
      exploit that a lot:

      1. (define (compile-open-coded-prim exp target linkage op)
      2. (end-with-linkage
      3. linkage
      4. (append-instruction-sequences
      5. (spread-arguments (operands exp))
      6. (make-instruction-sequence
      7. '(arg1 arg2)
      8. (list target)
      9. `((assign ,target (op ,op) (reg arg1) (reg arg2)))))))
      10. ;; open-code primitive dictionary
      11. (define open-coded-prims '((= =) (* *) (- -) (+ +)))
      12. (define (open-coded-prims? exp)
      13. (assoc (operator exp) open-coded-prims))

      Then

      1. **+ in compile procedure
      2. ...
      3. ;; ((=? exp) (compile-= exp target linkage))
      4. ((open-coded-prims? exp) =>
      5. (lambda (op-binding)
      6. (compile-open-coded-prim exp target linkage (cadr op-binding))))
      7. (application? exp)
      8. ...

      But since in d. part we want to extend particular operations to take arbitrary
      number of argument, they needs special treatment other than this; we’ll comeback
      this issue at d. part.

      Then test:

      1. ;; The previous one works well in this new scheme
      2. (pp (compile '(= (= 5 2) (= 2 1))
      3. 'val 'next))
      4. (()
      5. (arg1 arg2 val)
      6. ((assign arg1 (const 5))
      7. (assign arg2 (const 2))
      8. (assign arg1 (op =) (reg arg1) (reg arg2))
      9. (save arg1)
      10. (assign arg1 (const 2))
      11. (assign arg2 (const 1))
      12. (assign arg2 (op =) (reg arg1) (reg arg2))
      13. (restore arg1)
      14. (assign val (op =) (reg arg1) (reg arg2))))
      15. ;; Complex one also works well
      16. (pp (compile '(= (+ (- 5 2) (* 1 2)) 5)
      17. 'val 'next))
      18. (()
      19. (arg1 arg2 val)
      20. ((assign arg1 (const 5))
      21. (assign arg2 (const 2))
      22. (assign arg1 (op -) (reg arg1) (reg arg2))
      23. (save arg1)
      24. (assign arg1 (const 1))
      25. (assign arg2 (const 2))
      26. (assign arg2 (op *) (reg arg1) (reg arg2))
      27. (restore arg1)
      28. (assign arg1 (op +) (reg arg1) (reg arg2))
      29. (assign arg2 (const 5))
      30. (assign val (op =) (reg arg1) (reg arg2))))

      In addition to this feature, we may want to ensure the given expression has only
      two operands. We can ensure this with following modification:

      1. (define (open-coded-prims? exp)
      2. (and (= (length (operands exp)) 2) ;binary
      3. (assoc (operator exp) open-coded-prims)))
    • c.

      Then now let’s try to compile factorial in this new strategy:

      1. (pp (compile
      2. '(define (factorial n)
      3. (if (= n 1)
      4. 1
      5. (* (factorial (- n 1)) n)))
      6. 'val
      7. 'next))
      8. ((env)
      9. (val)
      10. ((assign val (op make-compiled-procedure) (label entry55) (reg env))
      11. (goto (label after-lambda54))
      12. entry55
      13. (assign env (op compiled-procedure-env) (reg proc))
      14. (assign env (op extend-environment) (const (n)) (reg argl) (reg env))
      15. (assign arg1 (op lookup-variable-value) (const n) (reg env))
      16. (assign arg2 (const 1))
      17. (assign val (op =) (reg arg1) (reg arg2))
      18. (test (op false?) (reg val))
      19. (branch (label false-branch57))
      20. true-branch58
      21. (assign val (const 1))
      22. (goto (reg continue))
      23. false-branch57
      24. (save continue)
      25. (save env)
      26. (assign proc (op lookup-variable-value) (const factorial) (reg env))
      27. (assign arg1 (op lookup-variable-value) (const n) (reg env))
      28. (assign arg2 (const 1))
      29. (assign val (op -) (reg arg1) (reg arg2))
      30. (assign argl (op list) (reg val))
      31. (test (op primitive-procedure?) (reg proc))
      32. (branch (label primitive-branch61))
      33. compiled-branch60
      34. (assign continue (label proc-return62))
      35. (assign val (op compiled-procedure-entry) (reg proc))
      36. (goto (reg val))
      37. proc-return62
      38. (assign arg1 (reg val))
      39. (goto (label after-call59))
      40. primitive-branch61
      41. (assign arg1 (op apply-primitive-procedure) (reg proc) (reg argl))
      42. after-call59
      43. (restore env)
      44. (assign arg2 (op lookup-variable-value) (const n) (reg env))
      45. (assign val (op *) (reg arg1) (reg arg2))
      46. (restore continue)
      47. (goto (reg continue))
      48. after-if56
      49. after-lambda54
      50. (perform (op define-variable!) (const factorial) (reg val) (reg env))
      51. (assign val (const ok))))

      Got way more cleaner than previous.

    • d.

      Since * and + satisfy the associative rule, we may exploit this property to
      the compiler; but for the consistency with possible further extension where
      primitive procedure does not satisfy the associative rule – - , / , and so
      on – here we are going to implement the arithmetic operation with arbitrary
      arity in fold-right manner – calculate the rightmost operands first.

      To deduce the algorithm, we should play with simple examples: Let’s think about
      (+ 1 2 3 4). We would like to produce the code by compiling as follows

      1. (assign arg2 (const 4))
      2. (assign arg1 (const 3))
      3. (assign arg2 (op +) (reg arg1) (reg arg2))
      4. (assign arg1 (const 2))
      5. (assign arg2 (op +) (reg arg1) (reg arg2))
      6. (assign arg1 (const 1))
      7. (assign target (op +) (reg arg1) (reg arg2))

      From this example, we can observe the first and last instruction treated specially.

      As consequence, in general, compiling (+ a1 a2 a3 a4) should produce

      1. <compile a4; target to arg2; linkage to next; preserving env>
      2. <compile a3; target to arg1; linkage to next; preserving env, arg2>
      3. (assign arg2 (op +) (reg arg1) (reg arg2))
      4. <compile a2; target to arg1; linkage to next; preserving env, arg2>
      5. (assign arg2 (op +) (reg arg1) (reg arg2))
      6. <compile a1; target to arg1; linkage to next; preserving arg2>
      7. (assign target (op +) (reg arg1) (reg arg2))
      8. <code for goto linkage point>

      This is very much the same as the construct-arglist. Here is the code for +:

      1. (define (compile-+ exp target linkage)
      2. (define (code-to-rest-+ operands)
      3. (let* ((last-operand? (no-operands? (rest-operands operands)))
      4. (next-target
      5. (if last-operand?
      6. target
      7. 'arg2))
      8. (code-for-next-+
      9. (preserving
      10. '(arg2)
      11. (compile (first-operand operands)
      12. 'arg1 'next)
      13. (make-instruction-sequence
      14. '(arg1 arg2) (list next-target)
      15. `((assign ,next-target (op +) (reg arg1) (reg arg2)))))))
      16. (if last-operand?
      17. code-for-next-+
      18. (preserving
      19. '(env)
      20. code-for-next-+
      21. (code-to-rest-+ (rest-operands operands))))))
      22. (let ((operands (operands exp)))
      23. (let ((number-of-arguments (length operands)))
      24. (case number-of-arguments
      25. ((0)
      26. (end-with-linkage
      27. linkage
      28. (make-instruction-sequence
      29. '() (list target))
      30. `((assign ,target (const 0)))))
      31. ((1)
      32. (compile (first-operand operands)
      33. target linkage))
      34. (else
      35. (let ((operands (reverse operands)))
      36. (end-with-linkage
      37. linkage
      38. (preserving
      39. '(env)
      40. (compile (first-operand operands)
      41. 'arg2 'next)
      42. (code-to-rest-+ (rest-operands operands))))))))))

      Then let’s install this:

      1. ;; detector for +
      2. (define (+? exp) (tagged-list? exp '+))

      And

      1. *** in compile clause
      2. ;; ((=? exp) (compile-= exp target linkage))
      3. ((+? exp) (compile-+ exp target linkage))
      4. ((open-coded-prims? exp) =>
      5. (lambda (op-binding)
      6. (compile-open-coded-prim exp target linkage (cadr op-binding))))
      7. (application? exp)

      Then test:

      1. ;; test for compile-+
      2. (pp (compile '(+ 1 2 3 4) 'val 'next))
      3. (()
      4. (arg2 arg1 val)
      5. ((assign arg2 (const 4))
      6. (assign arg1 (const 3))
      7. (assign arg2 (op +) (reg arg1) (reg arg2))
      8. (assign arg1 (const 2))
      9. (assign arg2 (op +) (reg arg1) (reg arg2))
      10. (assign arg1 (const 1))
      11. (assign val (op +) (reg arg1) (reg arg2))))

      Note that code-to-rest-+ can be generalized to any of expression that use
      recursive definition. Let’s extract out from the compile-+:

      1. (define (apply-recursively- op target operands)
      2. (let* ((last-operand? (no-operands? (rest-operands operands)))
      3. (next-target
      4. (if last-operand?
      5. target
      6. 'arg2))
      7. (code-for-next-op
      8. (preserving
      9. '(arg2)
      10. (compile (first-operand operands)
      11. 'arg1 'next)
      12. (make-instruction-sequence
      13. '(arg1 arg2) (list next-target)
      14. `((assign ,next-target (op ,op) (reg arg1) (reg arg2)))))))
      15. (if last-operand?
      16. code-for-next-op
      17. (preserving
      18. '(env)
      19. code-for-next-op
      20. (apply-recursively- op target (rest-operands operands))))))

      Then our original compile-+ got

      1. (define (compile-+ exp target linkage)
      2. (let ((operands (operands exp)))
      3. (let ((number-of-arguments (length operands)))
      4. (case number-of-arguments
      5. ((0)
      6. (end-with-linkage
      7. linkage
      8. (make-instruction-sequence
      9. '() (list target))
      10. `((assign ,target (const 0)))))
      11. ((1)
      12. (compile (first-operand operands)
      13. target linkage))
      14. (else
      15. (let ((operands (reverse operands)))
      16. (end-with-linkage
      17. linkage
      18. (preserving
      19. '(env)
      20. (compile (first-operand operands)
      21. 'arg2 'next)
      22. (apply-recursively- '+ target (rest-operands operands))))))))))

      The other is almost same as above:

      1. ;; detector for *
      2. (define (*? exp) (tagged-list? exp '*))
      3. (define (compile-* exp target linkage)
      4. (let ((operands (operands exp)))
      5. (let ((number-of-arguments (length operands)))
      6. (case number-of-arguments
      7. ((0)
      8. (end-with-linkage
      9. linkage
      10. (make-instruction-sequence
      11. '() (list target))
      12. `((assign ,target (const 1))))) ;difference
      13. ((1)
      14. (compile (first-operand operands)
      15. target linkage))
      16. (else
      17. (let ((operands (reverse operands)))
      18. (end-with-linkage
      19. linkage
      20. (preserving
      21. '(env)
      22. (compile (first-operand operands)
      23. 'arg2 'next)
      24. (apply-recursively- '* target (rest-operands operands)))))))))) ;the other one

      Only the ones annotated is the change from compile-+

Lexical Addressing

  • Exercise 5.39

    Here is the code:

    1. (define (lexical-address-lookup address env)
    2. (let ((val
    3. (list-ref
    4. (frame-values
    5. (frame-ref env (frame-number address)))
    6. (displacement-number address))))
    7. (if (eq? val '*unassigned*)
    8. (error "Unassigned variable:"
    9. (list-ref
    10. (frame-variables
    11. (frame-ref env (frame-number address)))
    12. (displacement-number address)))
    13. val)))
    14. ;; ADT for environment
    15. (define (frame-ref env index) (list-ref env index))
    16. ;; ADT for lexical-address
    17. (define (make-lexical-address frame-num displacement-num)
    18. `(,frame-num ,displacement-num))
    19. (define (frame-number address) (car address))
    20. (define (displacement-number address) (cadr address))

    Test:

    1. ;; Test for lexical-address-lookup
    2. (define test-environment
    3. (extend-environment
    4. '(y z) '((* a b x) (+ c d x))
    5. (extend-environment
    6. '(a b c d e)
    7. '(*unassigned* *unassigned* *unassigned* *unassigned* *unassigned*)
    8. (extend-environment
    9. '(x y)
    10. '(3 4)
    11. the-empty-environment))))
    12. ;; variable x
    13. (lexical-address-lookup '(2 0) test-environment)
    14. ;Value: 3
    15. ;; variable a
    16. (lexical-address-lookup '(1 0) test-environment)
    17. ;Unassigned variable: a
    18. ;To continue, call RESTART with an option number:
    19. ; (RESTART 1) => Return to read-eval-print level 1.

    Then lexical-address-set! can be implemented similarly:

    1. (define (lexical-address-set! address val env)
    2. (set-car!
    3. (list-tail
    4. (frame-values
    5. (frame-ref env (frame-number address)))
    6. (displacement-number address))
    7. val))

    Let’s test:

    1. ;; setting the value for a
    2. (lexical-address-set! '(1 0) 1 test-environment)
    3. ;Unspecified return value
    4. (lexical-address-lookup '(1 0) test-environment)
    5. ;Value: 1
  • Exercise 5.40

    Here is the main code:

    1. ;;; environemnt ADT
    2. (define (extend-compile-time-env params env)
    3. (cons params env))
    4. (define the-empty-compile-time-env '())
    5. (define (compile-lambda-body exp proc-entry env)
    6. (let* ((formals (lambda-parameters exp))
    7. (env (extend-compile-time-env formals env)))
    8. (append-instruction-sequences
    9. (make-instruction-sequence '(env proc argl) '(env)
    10. `(,proc-entry
    11. (assign env (op compiled-procedure-env) (reg proc))
    12. (assign env
    13. (op extend-environment)
    14. (const ,formals)
    15. (reg argl)
    16. (reg env))))
    17. (compile-sequence (lambda-body exp) 'val 'return env))))

    Then we should modify current compile clauses to accept env as additional
    argument. Please see the code for the result.

  • Exercise 5.41

    Here is simple implementation using loop:

    1. (define (find-variable var env)
    2. (let traverse-env ((current-env env)
    3. (frame-num 0))
    4. (if (empty-env? current-env)
    5. 'not-found
    6. (let traverse-frame ((current-vars (frame-vars current-env))
    7. (displacement-num 0))
    8. (cond ((empty-vars? current-vars)
    9. (traverse-env (enclosing-env current-env)
    10. (1+ frame-num)))
    11. ((eq? var (first-var current-vars))
    12. (make-lexical-address frame-num displacement-num))
    13. (else
    14. (traverse-frame (rest-vars current-vars)
    15. (1+ displacement-num))))))))
    16. ;; ADT for compile-time-env
    17. (define (frame-vars compile-time-env)
    18. (car compile-time-env))
    19. (define (enclosing-env compile-time-env)
    20. (cdr compile-time-env))
    21. (define (empty-env? compile-time-env) (null? compile-time-env))
    22. ;; ADT for compile-time-frame
    23. (define (first-var compile-time-frame) (car compile-time-frame))
    24. (define (rest-vars compile-time-frame) (cdr compile-time-frame))
    25. (define (empty-vars? compile-time-frame) (null? compile-time-frame))

    Then let’s test:

    1. ;; test for find-variable
    2. (find-variable 'c '((y z) (a b c d e) (x y)))
    3. ;Value: (1 2)
    4. (find-variable 'x '((y z) (a b c d e) (x y)))
    5. ;Value: (2 0)
    6. (find-variable 'w '((y z) (a b c d e) (x y)))
    7. ;Value: not-found
  • Exercise 5.42

    Here is the code that exploit the lexical address; and incorporated the strategy
    suggested in the text:

    1. (define (compile-variable exp target linkage env)
    2. (let ((address (find-variable exp env)))
    3. (end-with-linkage
    4. linkage
    5. (if (eq? address 'not-found)
    6. (make-instruction-sequence
    7. '() (list-union '(env) (list target))
    8. `((assign env (op get-global-environment))
    9. (assign ,target
    10. (op lookup-variable-value)
    11. (const ,exp)
    12. (reg env))))
    13. (make-instruction-sequence
    14. '(env) (list target)
    15. `((assign ,target
    16. (op lexical-address-lookup)
    17. (const ,address)
    18. (reg env))))))))

    Then let’s test:

    1. ;;; Test for new compile-variable
    2. (pp (compile
    3. '((lambda (x y)
    4. (lambda (a b c d e)
    5. ((lambda (y z) (* x y z))
    6. (* a b x)
    7. (+ c d x))))
    8. 3
    9. 4)
    10. 'val 'next the-empty-compile-time-env))
    11. ((env)
    12. (env proc argl continue val)
    13. ((assign proc (op make-compiled-procedure) (label entry2) (reg env))
    14. (goto (label after-lambda1))
    15. entry2
    16. (assign env (op compiled-procedure-env) (reg proc))
    17. (assign env (op extend-environment) (const (x y)) (reg argl) (reg env))
    18. (assign val (op make-compiled-procedure) (label entry4) (reg env))
    19. (goto (reg continue))
    20. entry4
    21. (assign env (op compiled-procedure-env) (reg proc))
    22. (assign env (op extend-environment) (const (a b c d e)) (reg argl) (reg env))
    23. (assign proc (op make-compiled-procedure) (label entry6) (reg env))
    24. (goto (label after-lambda5))
    25. entry6
    26. (assign env (op compiled-procedure-env) (reg proc))
    27. (assign env (op extend-environment) (const (y z)) (reg argl) (reg env))
    28. (assign arg2 (op lexical-address-lookup) (const (0 1)) (reg env))
    29. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
    30. (assign arg2 (op *) (reg arg1) (reg arg2))
    31. (assign arg1 (op lexical-address-lookup) (const (2 0)) (reg env))
    32. (assign val (op *) (reg arg1) (reg arg2))
    33. (goto (reg continue))
    34. after-lambda5
    35. (assign arg2 (op lexical-address-lookup) (const (1 0)) (reg env))
    36. (assign arg1 (op lexical-address-lookup) (const (0 3)) (reg env))
    37. (assign arg2 (op +) (reg arg1) (reg arg2))
    38. (assign arg1 (op lexical-address-lookup) (const (0 2)) (reg env))
    39. (assign val (op +) (reg arg1) (reg arg2))
    40. (assign argl (op list) (reg val))
    41. (assign arg2 (op lexical-address-lookup) (const (1 0)) (reg env))
    42. (assign arg1 (op lexical-address-lookup) (const (0 1)) (reg env))
    43. (assign arg2 (op *) (reg arg1) (reg arg2))
    44. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
    45. (assign val (op *) (reg arg1) (reg arg2))
    46. (assign argl (op cons) (reg val) (reg argl))
    47. (test (op primitive-procedure?) (reg proc))
    48. (branch (label primitive-branch9))
    49. compiled-branch8
    50. (assign val (op compiled-procedure-entry) (reg proc))
    51. (goto (reg val))
    52. primitive-branch9
    53. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    54. (goto (reg continue))
    55. after-call7
    56. after-lambda3
    57. after-lambda1
    58. (assign val (const 4))
    59. (assign argl (op list) (reg val))
    60. (assign val (const 3))
    61. (assign argl (op cons) (reg val) (reg argl))
    62. (test (op primitive-procedure?) (reg proc))
    63. (branch (label primitive-branch12))
    64. compiled-branch11
    65. (assign continue (label after-call10))
    66. (assign val (op compiled-procedure-entry) (reg proc))
    67. (goto (reg val))
    68. primitive-branch12
    69. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    70. after-call10))

    Looks fine. Let’s implement the other one also:

    1. (define (compile-assignment exp target linkage env)
    2. (let* ((var (assignment-variable exp))
    3. (get-value-code
    4. (compile (assignment-value exp) 'val 'next env))
    5. (address (find-variable var env)))
    6. (end-with-linkage
    7. linkage
    8. (if (eq? address 'not-found)
    9. (append-instruction-sequences
    10. get-value-code
    11. (make-instruction-sequence
    12. '(val) (list-union (list target) '(env))
    13. `((assign env (op get-global-environment))
    14. (perform (op set-variable-value!)
    15. (const ,var)
    16. (reg val)
    17. (reg env))
    18. (assign ,target (const ok)))))
    19. (preserving
    20. '(env)
    21. get-value-code
    22. (make-instruction-sequence
    23. '(env val) (list target)
    24. `((perform (op lexical-address-set!)
    25. (const ,address)
    26. (reg val)
    27. (reg env))
    28. (assign ,target (const ok)))))))))

    Then do the test:

    1. ;;; Test for new compile-assignment
    2. (pp (compile
    3. '((lambda (x y)
    4. (lambda (a b c d e)
    5. ((lambda (y z)
    6. (set! a 5)
    7. (* x y z))
    8. (* a b x)
    9. (+ c d x))))
    10. 3
    11. 4)
    12. 'val 'next the-empty-compile-time-env))
    13. ((env)
    14. (env proc argl continue val)
    15. ((assign proc (op make-compiled-procedure) (label entry2) (reg env))
    16. (goto (label after-lambda1))
    17. entry2
    18. (assign env (op compiled-procedure-env) (reg proc))
    19. (assign env (op extend-environment) (const (x y)) (reg argl) (reg env))
    20. (assign val (op make-compiled-procedure) (label entry4) (reg env))
    21. (goto (reg continue))
    22. entry4
    23. (assign env (op compiled-procedure-env) (reg proc))
    24. (assign env (op extend-environment) (const (a b c d e)) (reg argl) (reg env))
    25. (assign proc (op make-compiled-procedure) (label entry6) (reg env))
    26. (goto (label after-lambda5))
    27. entry6
    28. (assign env (op compiled-procedure-env) (reg proc))
    29. (assign env (op extend-environment) (const (y z)) (reg argl) (reg env))
    30. (assign val (const 5))
    31. (perform (op lexical-address-set!) (const (1 0)) (reg val) (reg env))
    32. (assign val (const ok))
    33. (assign arg2 (op lexical-address-lookup) (const (0 1)) (reg env))
    34. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
    35. (assign arg2 (op *) (reg arg1) (reg arg2))
    36. (assign arg1 (op lexical-address-lookup) (const (2 0)) (reg env))
    37. (assign val (op *) (reg arg1) (reg arg2))
    38. (goto (reg continue))
    39. after-lambda5
    40. (assign arg2 (op lexical-address-lookup) (const (1 0)) (reg env))
    41. (assign arg1 (op lexical-address-lookup) (const (0 3)) (reg env))
    42. (assign arg2 (op +) (reg arg1) (reg arg2))
    43. (assign arg1 (op lexical-address-lookup) (const (0 2)) (reg env))
    44. (assign val (op +) (reg arg1) (reg arg2))
    45. (assign argl (op list) (reg val))
    46. (assign arg2 (op lexical-address-lookup) (const (1 0)) (reg env))
    47. (assign arg1 (op lexical-address-lookup) (const (0 1)) (reg env))
    48. (assign arg2 (op *) (reg arg1) (reg arg2))
    49. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
    50. (assign val (op *) (reg arg1) (reg arg2))
    51. (assign argl (op cons) (reg val) (reg argl))
    52. (test (op primitive-procedure?) (reg proc))
    53. (branch (label primitive-branch9))
    54. compiled-branch8
    55. (assign val (op compiled-procedure-entry) (reg proc))
    56. (goto (reg val))
    57. primitive-branch9
    58. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    59. (goto (reg continue))
    60. after-call7
    61. after-lambda3
    62. after-lambda1
    63. (assign val (const 4))
    64. (assign argl (op list) (reg val))
    65. (assign val (const 3))
    66. (assign argl (op cons) (reg val) (reg argl))
    67. (test (op primitive-procedure?) (reg proc))
    68. (branch (label primitive-branch12))
    69. compiled-branch11
    70. (assign continue (label after-call10))
    71. (assign val (op compiled-procedure-entry) (reg proc))
    72. (goto (reg val))
    73. primitive-branch12
    74. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    75. after-call10))
  • Exercise 5.43

    Like cond->if, this one is also just one of the derived form! So we can reuse
    the previous scan-out-defines. Here we modified the previous definition using
    the continuation for the efficiency and to return the body if there is no
    internal definitions rather than transforming to let expression:

    1. (define (scan-out-defines proc-body)
    2. (let loop
    3. ((exps proc-body)
    4. (accept
    5. (lambda (internal-defs rest-body)
    6. (if (null? internal-defs)
    7. rest-body
    8. (let ((vars (map definition-variable internal-defs))
    9. (exps (map definition-value internal-defs)))
    10. (let ((bindings
    11. (map (lambda (var) (list var (list 'quote '*unassigned*)))
    12. vars))
    13. (set-exps
    14. (map (lambda (var val)
    15. (make-assignment var val))
    16. vars
    17. exps)))
    18. (make-let bindings (append set-exps rest-body))))))))
    19. (if (null? exps)
    20. (accept '() '())
    21. (let ((exp (car exps))
    22. (rest (cdr exps)))
    23. (loop
    24. rest
    25. (lambda (defs rest-body)
    26. (if (definition? exp)
    27. (accept (cons exp defs)
    28. rest-body)
    29. (if (null? defs)
    30. (accept defs
    31. (cons exp rest-body))
    32. (error "Internal defintions intertwines with others" proc-body)))))))))

    Then compile-lambda-body got

    1. (define (compile-lambda-body exp proc-entry env)
    2. (let* ((formals (lambda-parameters exp))
    3. (env (extend-compile-time-env formals env)))
    4. (append-instruction-sequences
    5. (make-instruction-sequence '(env proc argl) '(env)
    6. `(,proc-entry
    7. (assign env (op compiled-procedure-env) (reg proc))
    8. (assign env
    9. (op extend-environment)
    10. (const ,formals)
    11. (reg argl)
    12. (reg env))))
    13. (let ((scanned (scan-out-defines (lambda-body exp))))
    14. (if (let? scanned)
    15. (compile scanned 'val 'return env)
    16. (compile-sequence scanned 'val 'return env))))))

    Then let’s test:

    1. ;;; test for new feature!
    2. (pp (compile
    3. '(lambda (x y)
    4. (define (test-internal x y z) z)
    5. (define y 5)
    6. (+ y (test-internal x y 3)))
    7. 'val 'next the-empty-compile-time-env))
    8. ((env)
    9. (val)
    10. ((assign val (op make-compiled-procedure) (label entry2) (reg env))
    11. (goto (label after-lambda1))
    12. entry2
    13. (assign env (op compiled-procedure-env) (reg proc))
    14. (assign env (op extend-environment) (const (x y)) (reg argl) (reg env))
    15. (assign proc (op make-compiled-procedure) (label entry4) (reg env))
    16. (goto (label after-lambda3))
    17. entry4
    18. (assign env (op compiled-procedure-env) (reg proc))
    19. (assign env (op extend-environment) (const (test-internal y)) (reg argl) (reg env))
    20. (assign val (op make-compiled-procedure) (label entry10) (reg env))
    21. (goto (label after-lambda9))
    22. entry10
    23. (assign env (op compiled-procedure-env) (reg proc))
    24. (assign env (op extend-environment) (const (x y z)) (reg argl) (reg env))
    25. (assign val (op lexical-address-lookup) (const (0 2)) (reg env))
    26. (goto (reg continue))
    27. after-lambda9
    28. (perform (op lexical-address-set!) (const (0 0)) (reg val) (reg env))
    29. (assign val (const ok))
    30. (assign val (const 5))
    31. (perform (op lexical-address-set!) (const (0 1)) (reg val) (reg env))
    32. (assign val (const ok))
    33. (save continue)
    34. (save env)
    35. (assign proc (op lexical-address-lookup) (const (0 0)) (reg env))
    36. (assign val (const 3))
    37. (assign argl (op list) (reg val))
    38. (assign val (op lexical-address-lookup) (const (0 1)) (reg env))
    39. (assign argl (op cons) (reg val) (reg argl))
    40. (assign val (op lexical-address-lookup) (const (1 0)) (reg env))
    41. (assign argl (op cons) (reg val) (reg argl))
    42. (test (op primitive-procedure?) (reg proc))
    43. (branch (label primitive-branch7))
    44. compiled-branch6
    45. (assign continue (label proc-return8))
    46. (assign val (op compiled-procedure-entry) (reg proc))
    47. (goto (reg val))
    48. proc-return8
    49. (assign arg2 (reg val))
    50. (goto (label after-call5))
    51. primitive-branch7
    52. (assign arg2 (op apply-primitive-procedure) (reg proc) (reg argl))
    53. after-call5
    54. (restore env)
    55. (assign arg1 (op lexical-address-lookup) (const (0 1)) (reg env))
    56. (assign val (op +) (reg arg1) (reg arg2))
    57. (restore continue)
    58. (goto (reg continue))
    59. after-lambda3
    60. (assign val (const *unassigned*))
    61. (assign argl (op list) (reg val))
    62. (assign val (const *unassigned*))
    63. (assign argl (op cons) (reg val) (reg argl))
    64. (test (op primitive-procedure?) (reg proc))
    65. (branch (label primitive-branch13))
    66. compiled-branch12
    67. (assign val (op compiled-procedure-entry) (reg proc))
    68. (goto (reg val))
    69. primitive-branch13
    70. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    71. (goto (reg continue))
    72. after-call11
    73. after-lambda1))

    Looks fine!

  • Exercise 5.44

    The reasoning behind what we trying to implement is follows: If the given open
    coded primitive procedure found in the given compile time environment (with
    find-variable procedure), then we should hand over given expression to the
    compile-application; otherwise, it means either given procedure variable bound
    in global environment which is dynamic one so we can not access that in compile
    time or not bound at all (to be used compile time).

    This analysis means the only case we can not cope with in this scheme is the one
    defined in global environment by define or through set! what primitive
    procedure bound in the global environment for the interpreter.

    Well, enough for word; let’s implement it:

    1. (define (not-bound? var env)
    2. (eq? (find-variable var env) 'not-found))

    And then

    1. *** in compile clause
    2. ((and (not-bound? (operator exp) env)
    3. (+? exp))
    4. (compile-+ exp target linkage env))
    5. ((and (not-bound? (operator exp) env)
    6. (*? exp))
    7. (compile-* exp target linkage env))
    8. ((and (not-bound? (operator exp) env)
    9. (open-coded-prims? exp)) =>
    10. (lambda (op-binding)
    11. (compile-open-coded-prim exp target linkage env (cadr op-binding))))
    12. ((application? exp)
    13. (compile-application exp target linkage env))

    We just delegated if the given operator is rebound.

    Then test!

    1. ;; Test for this new feature
    2. (pp (compile
    3. '(lambda (+ * a b x y)
    4. (+ (* a x) (* b y)))
    5. 'val 'next the-empty-compile-time-env))
    6. ((env)
    7. (val)
    8. ((assign val (op make-compiled-procedure) (label entry13) (reg env))
    9. (goto (label after-lambda12))
    10. entry13
    11. (assign env (op compiled-procedure-env) (reg proc))
    12. (assign env (op extend-environment) (const (+ * a b x y)) (reg argl) (reg env))
    13. (assign proc (op lexical-address-lookup) (const (0 0)) (reg env))
    14. (save continue)
    15. (save proc)
    16. (save env)
    17. (assign proc (op lexical-address-lookup) (const (0 1)) (reg env))
    18. (assign val (op lexical-address-lookup) (const (0 5)) (reg env))
    19. (assign argl (op list) (reg val))
    20. (assign val (op lexical-address-lookup) (const (0 3)) (reg env))
    21. (assign argl (op cons) (reg val) (reg argl))
    22. (test (op primitive-procedure?) (reg proc))
    23. (branch (label primitive-branch19))
    24. compiled-branch18
    25. (assign continue (label after-call17))
    26. (assign val (op compiled-procedure-entry) (reg proc))
    27. (goto (reg val))
    28. primitive-branch19
    29. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    30. after-call17
    31. (assign argl (op list) (reg val))
    32. (restore env)
    33. (save argl)
    34. (assign proc (op lexical-address-lookup) (const (0 1)) (reg env))
    35. (assign val (op lexical-address-lookup) (const (0 4)) (reg env))
    36. (assign argl (op list) (reg val))
    37. (assign val (op lexical-address-lookup) (const (0 2)) (reg env))
    38. (assign argl (op cons) (reg val) (reg argl))
    39. (test (op primitive-procedure?) (reg proc))
    40. (branch (label primitive-branch16))
    41. compiled-branch15
    42. (assign continue (label after-call14))
    43. (assign val (op compiled-procedure-entry) (reg proc))
    44. (goto (reg val))
    45. primitive-branch16
    46. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    47. after-call14
    48. (restore argl)
    49. (assign argl (op cons) (reg val) (reg argl))
    50. (restore proc)
    51. (restore continue)
    52. (test (op primitive-procedure?) (reg proc))
    53. (branch (label primitive-branch22))
    54. compiled-branch21
    55. (assign val (op compiled-procedure-entry) (reg proc))
    56. (goto (reg val))
    57. primitive-branch22
    58. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
    59. (goto (reg continue))
    60. after-call20
    61. after-lambda12))

    Works well.

Interfacing Compiled Code to the Evaluator

If we allowed to optimize our compiler using the preceding exercises, that is,
to use the opencoded primitive procedures and lexical operations, we can cause
even more dramatic result:

  1. (compile-and-go
  2. '(define (factorial n)
  3. (if (= n 1)
  4. 1
  5. (* (factorial (- n 1)) n))))
  6. (total-pushes = 0 maximum-depth = 0)
  7. ;;; EC-Eval value:
  8. ok
  9. ;;; EC-Eval input:
  10. (factorial 5)
  11. (total-pushes = 17 maximum-depth = 9)
  12. ;;; EC-Eval value:
  13. 120

Versus normal version of compiler:

  1. (compile-and-go
  2. '(define (factorial n)
  3. (if (= n 1)
  4. 1
  5. (* (factorial (- n 1)) n))))
  6. (total-pushes = 0 maximum-depth = 0)
  7. ;;; EC-Eval value:
  8. ok
  9. ;;; EC-Eval input:
  10. (factorial 5)
  11. (total-pushes = 31 maximum-depth = 14)
  12. ;;; EC-Eval value:
  13. 120
  • Exercise 5.45

    • a.

      We have already deduced the formulas for interpreted factorial and
      special-purpose factorial machine; all the left is for the factorial using
      compile. We’ll do the analysis using two different versions of our compiler:
      One for the unoptimized and the other for optimized using lexical operations and
      open-coded primitives.

      • Via unoptimized compiler

        Let’s get the data to deduce the formulas for total-pushes and
        maximum-depth. Actually as we know the resulting formula should be linear in
        both, we can deduce wanting formula using only two data:

        1. ;;; EC-Eval input:
        2. (factorial 5)
        3. (total-pushes = 31 maximum-depth = 14)
        4. ;;; EC-Eval value:
        5. 120
        6. ;;; EC-Eval input:
        7. (factorial 4)
        8. (total-pushes = 25 maximum-depth = 11)
        9. ;;; EC-Eval value:
        10. 24

        From this data, we can formulate as

        • total-pushes = 6n + 1
        • maximum-depth = 3n - 1

        So the ratio for the total-pushes in compiled version to interpreted version
        converges to 3/16; and that for maximum-depth is 3/5.

        And that of compiled version to special-purpose factorial machine are 3 and 3/2,
        respectively.

      • Via optimized compiler

        If we do the same thing as we did preceding one, we got

        1. ;;; EC-Eval input:
        2. (factorial 5)
        3. (total-pushes = 17 maximum-depth = 9)
        4. ;;; EC-Eval value:
        5. 120
        6. ;;; EC-Eval input:
        7. (factorial 4)
        8. (total-pushes = 14 maximum-depth = 7)
        9. ;;; EC-Eval value:
        10. 24

        Then,

        • total-pushes = 3n + 2
        • maximum-depth = 2n - 1

        So the ratio for the total-pushes in this to interpreted version converges to
        3/32; and that for maximum-depth is 2/5.

        And that of this to special-purpose factorial machine are 3/2 and 1, respectively.

    • b.

      For the unoptimized compiler, just use the optimized one; but if we aren’t
      satisfied with the difference the performance produced by optimized compiler
      from special-purpose factorial machine, we should analyze the controller code to
      realize what causes that difference:

      Here is the generated code from optimized compiler:

      1. (pp (compile
      2. '(define (factorial n)
      3. (if (= n 1)
      4. 1
      5. (* (factorial (- n 1)) n)))
      6. 'val 'next
      7. the-empty-compile-time-env))
      8. ((env)
      9. (val)
      10. ((assign val (op make-compiled-procedure) (label entry11) (reg env))
      11. (goto (label after-lambda10))
      12. entry11
      13. (assign env (op compiled-procedure-env) (reg proc))
      14. (assign env (op extend-environment) (const (n)) (reg argl) (reg env))
      15. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
      16. (assign arg2 (const 1))
      17. (assign val (op =) (reg arg1) (reg arg2))
      18. (test (op false?) (reg val))
      19. (branch (label false-branch13))
      20. true-branch14
      21. (assign val (const 1))
      22. (goto (reg continue))
      23. false-branch13
      24. (save continue)
      25. (assign arg2 (op lexical-address-lookup) (const (0 0)) (reg env))
      26. (save arg2)
      27. (save env) ;the overhead
      28. (assign env (op get-global-environment))
      29. (assign proc (op lookup-variable-value) (const factorial) (reg env))
      30. (restore env) ;***
      31. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
      32. (assign arg2 (const 1))
      33. (assign val (op -) (reg arg1) (reg arg2))
      34. (assign argl (op list) (reg val))
      35. (test (op primitive-procedure?) (reg proc))
      36. (branch (label primitive-branch17))
      37. compiled-branch16
      38. (assign continue (label proc-return18))
      39. (assign val (op compiled-procedure-entry) (reg proc))
      40. (goto (reg val))
      41. proc-return18
      42. (assign arg1 (reg val))
      43. (goto (label after-call15))
      44. primitive-branch17
      45. (assign arg1 (op apply-primitive-procedure) (reg proc) (reg argl))
      46. after-call15
      47. (restore arg2)
      48. (assign val (op *) (reg arg1) (reg arg2))
      49. (restore continue)
      50. (goto (reg continue))
      51. after-if12
      52. after-lambda10
      53. (perform (op define-variable!) (const factorial) (reg val) (reg env))
      54. (assign val (const ok))))

      If we compare this code with the fact-machine in Exercise 5.14, we come to
      know that the only overhead of stack operation is (save env) as annotated
      above code; we have introduced this overhead when we dealing with the lexical
      address to lookup variable, if the given variable not found in the lexical
      environment then it means it should be found in global environment; we’ve
      exploited this fact into our compiler.

      If we reverse this optimization we can remove this overhead stack operation, but
      this only disguise the overhead into the operation lookup-variable-value,
      which, in principle, should be implemented as subroutines; so basically we
      couldn’t resolved the optimization problem.

      Nevertheless, let’s verify our observation by reverting noted optimization:

      1. (define (compile-variable exp target linkage env)
      2. (let ((address (find-variable exp env)))
      3. (end-with-linkage
      4. linkage
      5. (if (eq? address 'not-found)
      6. (make-instruction-sequence
      7. ;; '() (list-union '(env) (list target))
      8. '(env) (list target)
      9. `(;; (assign env (op get-global-environment))
      10. (assign ,target
      11. (op lookup-variable-value)
      12. (const ,exp)
      13. (reg env))))
      14. (make-instruction-sequence
      15. '(env) (list target)
      16. `((assign ,target
      17. (op lexical-address-lookup)
      18. (const ,address)
      19. (reg env))))))))

      Then

      1. ;;; EC-Eval input:
      2. (factorial 5)
      3. (total-pushes = 13 maximum-depth = 8)
      4. ;;; EC-Eval value:
      5. 120
      6. ;;; EC-Eval input:
      7. (factorial 4)
      8. (total-pushes = 11 maximum-depth = 6)
      9. ;;; EC-Eval value:
      10. 24

      As expected, now the asymptotic complexity of these stack operations got same as
      the special-purpose factorial machine – both ratio became just 1 together; but
      as noted above this is not the real improvement – just spurious appearance.

      To make this produced code improved, not just spuriously, we need to get rid of
      the lookup-variable-value operation which possibly be the main place of the
      overhead if the global environment has a lot of bindings and so on.

      So one strategy we can come up with is replace all the lookup-variable-value
      with lexical-address-lookup since it exploit the compile time environment,
      compiler can alleviate the work of machine should do in execution time a lot.

      To do this, we need to change the definition of factorial using named-let
      expression, which does not change the algorithm of the original factorial at
      all. This would make close our machine’s behavior that execute compiled code to
      the special-purpose machine.

  • Exercise 5.46

    We will do the same thing to procedure computing the Fibonacci numbers. As noted
    in the text book, the analysis result for interpreted version obtained in
    Exercise 5.29.

    So here, we are going to analyze the special-purpose machine for Fibonacci and
    compiled version (one for the unoptimized and the other for the optimized).

    Before doing analysis, note that we know the complexity of steps and spaces
    about the Fibonacci algorithm in asymptotic manner; this complexity doesn’t
    change by the implementation strategy. So we expect the complexity of space
    should be linear and that of step should be exponential – same as the Fibonacci
    number.

    • Analyze special purpose machine

      • Maximum-depth

        As it is linear, we can estimate the formula with just 2 data:

        1. (start fib-machine)
        2. 4
        3. (total-pushes = 16 maximum-depth = 6)
        4. ;Value: done
        5. (start fib-machine)
        6. 5
        7. (total-pushes = 28 maximum-depth = 8)
        8. ;Value: done

        From this fact, we know the maximum-depth be 2(n - 1).

      • Total-pushes

        We can use the strategy of analysis of exercise 5.29b since the complexity is
        same. So if we apply that formulation to following data:

        1. (start fib-machine)
        2. 0
        3. (total-pushes = 0 maximum-depth = 0)
        4. ;Value: done
        5. (start fib-machine)
        6. 1
        7. (total-pushes = 0 maximum-depth = 0)
        8. ;Value: done
        9. (start fib-machine)
        10. 2
        11. (total-pushes = 4 maximum-depth = 2)
        12. ;Value: done
        13. (start fib-machine)
        14. 3
        15. (total-pushes = 8 maximum-depth = 4)
        16. ;Value: done

        We got total-pushes = 4 (Fib(n + 1) - 1)

    • Analyze machine operated on compiled code

      • Via unoptimized compiler

        We will omit the description for below code since it is same process as above
        and simply can be deduced by preceding context.

        • Maximum-depth

          1. ;;; EC-Eval input:
          2. (fib 4)
          3. (total-pushes = 47 maximum-depth = 11)
          4. ;;; EC-Eval value:
          5. 3
          6. ;;; EC-Eval input:
          7. (fib 5)
          8. (total-pushes = 77 maximum-depth = 14)
          9. ;;; EC-Eval value:
          10. 5

          Maximum-depth = 3n - 1

        • Total-pushes

          1. ;;; EC-Eval input:
          2. (fib 0)
          3. (total-pushes = 7 maximum-depth = 3)
          4. ;;; EC-Eval value:
          5. 0
          6. ;;; EC-Eval input:
          7. (fib 1)
          8. (total-pushes = 7 maximum-depth = 3)
          9. ;;; EC-Eval value:
          10. 1
          11. ;;; EC-Eval input:
          12. (fib 2)
          13. (total-pushes = 17 maximum-depth = 5)
          14. ;;; EC-Eval value:
          15. 1

          Total-pushes = 10 Fib(n + 1) - 3

      • Via optimized compiler

        • Maximum-depth

          1. ;;; EC-Eval input:
          2. (fib 4)
          3. (total-pushes = 52 maximum-depth = 9)
          4. ;;; EC-Eval value:
          5. 3
          6. ;;; EC-Eval input:
          7. (fib 5)
          8. (total-pushes = 85 maximum-depth = 11)
          9. ;;; EC-Eval value:
          10. 5

          Maximum-depth = 2n + 1

        • Total-pushes

          1. ;;; EC-Eval input:
          2. (fib 0)
          3. (total-pushes = 8 maximum-depth = 3)
          4. ;;; EC-Eval value:
          5. 0
          6. ;;; EC-Eval input:
          7. (fib 1)
          8. (total-pushes = 8 maximum-depth = 3)
          9. ;;; EC-Eval value:
          10. 1
          11. ;;; EC-Eval input:
          12. (fib 2)
          13. (total-pushes = 19 maximum-depth = 5)
          14. ;;; EC-Eval value:
          15. 1

          Total-pushes = 11 Fib(n + 1) - 3

    • Summarizing statistics

      So the ratios of unoptimized compiler version to the interpreted version are 3/5
      and 5/28 respectively; and those of unoptimized compiler version to the
      special-purpose machine version are 3/2 and 5/2 respectively.

      With optimized one, the above ratios are 2/5 and 11/56 compared to interpreted
      one, and 1 and 11/4 compared to special-purpose machine.

      The reason the unoptimized version has less total-pushes than optimized one is
      same preceding exercise; and the improvement strategy also be same as that.

      If we remove the assignment of global-environment, we got

      1. ;;; EC-Eval input:
      2. (fib 0)
      3. (total-pushes = 7 maximum-depth = 3)
      4. ;;; EC-Eval value:
      5. 0
      6. ;;; EC-Eval input:
      7. (fib 1)
      8. (total-pushes = 7 maximum-depth = 3)
      9. ;;; EC-Eval value:
      10. 1
      11. ;;; EC-Eval input:
      12. (fib 2)
      13. (total-pushes = 14 maximum-depth = 4)
      14. ;;; EC-Eval value:
      15. 1

      which implies total-pushes = 7 Fib(n + 1).

      With this exercise, we could detect erroneous behavior of our optimized
      compiler:

      1. (pp (compile
      2. '(define (fib n)
      3. (if (< n 2)
      4. n
      5. (+ (fib (- n 1)) (fib (- n 2)))))
      6. 'val 'next the-empty-compile-time-env))
      7. ((env)
      8. (val)
      9. ((assign val (op make-compiled-procedure) (label entry23) (reg env))
      10. (goto (label after-lambda22))
      11. entry23
      12. (assign env (op compiled-procedure-env) (reg proc))
      13. (assign env (op extend-environment) (const (n)) (reg argl) (reg env))
      14. (save continue)
      15. (save env) ;***
      16. (save env) ;consequent same operation
      17. (assign env (op get-global-environment))
      18. (assign proc (op lookup-variable-value) (const <) (reg env))
      19. (restore env)
      20. (assign val (const 2))
      21. (assign argl (op list) (reg val))
      22. (assign val (op lexical-address-lookup) (const (0 0)) (reg env))
      23. (assign argl (op cons) (reg val) (reg argl))
      24. (test (op primitive-procedure?) (reg proc))
      25. (branch (label primitive-branch37))
      26. compiled-branch36
      27. (assign continue (label after-call35))
      28. (assign val (op compiled-procedure-entry) (reg proc))
      29. (goto (reg val))
      30. primitive-branch37
      31. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      32. after-call35
      33. (restore env)
      34. (restore continue)
      35. (test (op false?) (reg val))
      36. (branch (label false-branch25))
      37. true-branch26
      38. (assign val (op lexical-address-lookup) (const (0 0)) (reg env))
      39. (goto (reg continue))
      40. false-branch25
      41. (save continue)
      42. (save env)
      43. (save env)
      44. (assign env (op get-global-environment))
      45. (assign proc (op lookup-variable-value) (const fib) (reg env))
      46. (restore env)
      47. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
      48. (assign arg2 (const 2))
      49. (assign val (op -) (reg arg1) (reg arg2))
      50. (assign argl (op list) (reg val))
      51. (test (op primitive-procedure?) (reg proc))
      52. (branch (label primitive-branch33))
      53. compiled-branch32
      54. (assign continue (label proc-return34))
      55. (assign val (op compiled-procedure-entry) (reg proc))
      56. (goto (reg val))
      57. proc-return34
      58. (assign arg2 (reg val))
      59. (goto (label after-call31))
      60. primitive-branch33
      61. (assign arg2 (op apply-primitive-procedure) (reg proc) (reg argl))
      62. after-call31
      63. (restore env)
      64. (save arg2)
      65. (save env)
      66. (assign env (op get-global-environment))
      67. (assign proc (op lookup-variable-value) (const fib) (reg env))
      68. (restore env)
      69. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
      70. (assign arg2 (const 1))
      71. (assign val (op -) (reg arg1) (reg arg2))
      72. (assign argl (op list) (reg val))
      73. (test (op primitive-procedure?) (reg proc))
      74. (branch (label primitive-branch29))
      75. compiled-branch28
      76. (assign continue (label proc-return30))
      77. (assign val (op compiled-procedure-entry) (reg proc))
      78. (goto (reg val))
      79. proc-return30
      80. (assign arg1 (reg val))
      81. (goto (label after-call27))
      82. primitive-branch29
      83. (assign arg1 (op apply-primitive-procedure) (reg proc) (reg argl))
      84. after-call27
      85. (restore arg2)
      86. (assign val (op +) (reg arg1) (reg arg2))
      87. (restore continue)
      88. (goto (reg continue))
      89. after-if24
      90. after-lambda22
      91. (perform (op define-variable!) (const fib) (reg val) (reg env))
      92. (assign val (const ok))))

      Please note the annotated instruction; it just duplicate preceding instruction.
      This is due to the fact that we destroyed the barrier between what we assumed as
      primitive procedures available as machine operations and not; more specifically
      we mixed the static operation and dynamic operation which leads to leak the guts
      (assign env (op get-global-environment)) – of what we assumed available as
      machine operation.

      So for now, let us fix this problem using ad-hoc procedure:

      1. (define (lookup-variable-value-in-frame var env frame-num)
      2. (lookup-variable-value
      3. var
      4. (extend-env-with-frame (frame-ref env frame-num)
      5. the-empty-environment)))
      6. (define (extend-env-with-frame frame env) (cons frame env))

      Then:

      1. (define (compile-variable exp target linkage env)
      2. (let ((address (find-variable exp env)))
      3. (end-with-linkage
      4. linkage
      5. (if (eq? address 'not-found)
      6. (make-instruction-sequence
      7. '(env) (list target)
      8. `((assign ,target
      9. (op lookup-variable-value-in-frame)
      10. (const ,exp)
      11. (reg env)
      12. (const ,(length env))))) ;relative frame number for global env
      13. (make-instruction-sequence
      14. '(env) (list target)
      15. `((assign ,target
      16. (op lexical-address-lookup)
      17. (const ,address)
      18. (reg env))))))))

      Now we resolved the duplicative code:

      1. (pp (compile
      2. '(define (fib n)
      3. (if (< n 2)
      4. n
      5. (+ (fib (- n 1)) (fib (- n 2)))))
      6. 'val 'next the-empty-compile-time-env))
      7. ((env)
      8. (val)
      9. ((assign val (op make-compiled-procedure) (label entry18) (reg env))
      10. (goto (label after-lambda17))
      11. entry18
      12. (assign env (op compiled-procedure-env) (reg proc))
      13. (assign env (op extend-environment) (const (n)) (reg argl) (reg env))
      14. (save continue)
      15. (save env)
      16. (assign proc (op lookup-variable-value-in-frame) (const <) (reg env) (const 1))
      17. (assign val (const 2))
      18. (assign argl (op list) (reg val))
      19. (assign val (op lexical-address-lookup) (const (0 0)) (reg env))
      20. (assign argl (op cons) (reg val) (reg argl))
      21. (test (op primitive-procedure?) (reg proc))
      22. (branch (label primitive-branch32))
      23. compiled-branch31
      24. (assign continue (label after-call30))
      25. (assign val (op compiled-procedure-entry) (reg proc))
      26. (goto (reg val))
      27. primitive-branch32
      28. (assign val (op apply-primitive-procedure) (reg proc) (reg argl))
      29. after-call30
      30. (restore env)
      31. (restore continue)
      32. (test (op false?) (reg val))
      33. (branch (label false-branch20))
      34. true-branch21
      35. (assign val (op lexical-address-lookup) (const (0 0)) (reg env))
      36. (goto (reg continue))
      37. false-branch20
      38. (save continue)
      39. (save env)
      40. (assign proc (op lookup-variable-value-in-frame) (const fib) (reg env) (const 1))
      41. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
      42. (assign arg2 (const 2))
      43. (assign val (op -) (reg arg1) (reg arg2))
      44. (assign argl (op list) (reg val))
      45. (test (op primitive-procedure?) (reg proc))
      46. (branch (label primitive-branch28))
      47. compiled-branch27
      48. (assign continue (label proc-return29))
      49. (assign val (op compiled-procedure-entry) (reg proc))
      50. (goto (reg val))
      51. proc-return29
      52. (assign arg2 (reg val))
      53. (goto (label after-call26))
      54. primitive-branch28
      55. (assign arg2 (op apply-primitive-procedure) (reg proc) (reg argl))
      56. after-call26
      57. (restore env)
      58. (save arg2)
      59. (assign proc (op lookup-variable-value-in-frame) (const fib) (reg env) (const 1))
      60. (assign arg1 (op lexical-address-lookup) (const (0 0)) (reg env))
      61. (assign arg2 (const 1))
      62. (assign val (op -) (reg arg1) (reg arg2))
      63. (assign argl (op list) (reg val))
      64. (test (op primitive-procedure?) (reg proc))
      65. (branch (label primitive-branch24))
      66. compiled-branch23
      67. (assign continue (label proc-return25))
      68. (assign val (op compiled-procedure-entry) (reg proc))
      69. (goto (reg val))
      70. proc-return25
      71. (assign arg1 (reg val))
      72. (goto (label after-call22))
      73. primitive-branch24
      74. (assign arg1 (op apply-primitive-procedure) (reg proc) (reg argl))
      75. after-call22
      76. (restore arg2)
      77. (assign val (op +) (reg arg1) (reg arg2))
      78. (restore continue)
      79. (goto (reg continue))
      80. after-if19
      81. after-lambda17
      82. (perform (op define-variable!) (const fib) (reg val) (reg env))
      83. (assign val (const ok))))
  • Exercise 5.47

    This exercise is a lot like the analogous process of extension of evaluator to
    use the compiled procedure.

    From the contraction of compound-apply subroutine, all we need to do is to
    save the return point into the stack. The rest is obvious. Here is the resulting
    code:

    1. (define (compile-procedure-call target linkage)
    2. (let ((primitive-branch (make-label 'primitive-branch))
    3. (compiled-branch (make-label 'compiled-branch))
    4. (compound-branch (make-label 'compound-branch))
    5. (after-call (make-label 'after-call)))
    6. (let ((not-primitive-linkage
    7. (if (eq? linkage 'next) after-call linkage)))
    8. (append-instruction-sequences
    9. (make-instruction-sequence
    10. '(proc) '()
    11. `((test (op primitive-procedure?) (reg proc))
    12. (branch (label ,primitive-branch))))
    13. (make-instruction-sequence
    14. '(proc) '()
    15. `((test (op compound-procedure?) (reg proc))
    16. (branch (label ,compound-branch))))
    17. (parallel-instruction-sequences
    18. (append-instruction-sequences
    19. compiled-branch
    20. (compile-proc-appl target not-primitive-linkage))
    21. (append-instruction-sequences
    22. compound-branch
    23. (compound-proc-appl target not-primitive-linkage))
    24. (append-instruction-sequences
    25. primitive-branch
    26. (end-with-linkage
    27. linkage
    28. (make-instruction-sequence
    29. '(proc argl)
    30. (list target)
    31. `((assign ,target
    32. (op apply-primitive-procedure)
    33. (reg proc)
    34. (reg argl)))))))
    35. after-call))))

    Then compound-proc-appl becomes

    1. (define (compound-proc-appl target linkage)
    2. (cond ((and (eq? target 'val) (not (eq? linkage 'return)))
    3. (make-instruction-sequence
    4. '(compapp) all-regs
    5. `((assign continue (label ,linkage))
    6. (save continue)
    7. (goto (reg compapp)))))
    8. ((and (not (eq? target 'val))
    9. (not (eq? linkage 'return)))
    10. (let ((proc-return (make-label 'proc-return)))
    11. (make-instruction-sequence
    12. '(compapp) all-regs
    13. `((assign continue (label ,proc-return))
    14. (save continue)
    15. (goto (reg compapp))
    16. ,proc-return
    17. (assign ,target (reg val))
    18. (goto (label ,linkage))))))
    19. ((and (eq? target 'val) (eq? linkage 'return))
    20. (make-instruction-sequence
    21. '(compapp continue) all-regs
    22. '((save continue)
    23. (goto (reg compapp)))))
    24. ((and (not (eq? target 'val)) (eq? linkage 'return))
    25. (error "return linkage, target not val -- COMPILE"
    26. target))))

    To accommodate arbitrary arguments for parallel-instruction-sequence like
    above, we need to change that procedure analogous to
    append-instruction-sequence:

    1. (define (parallel-instruction-sequences . seqs)
    2. (define (parallel-2-sequences seq1 seq2)
    3. (make-instruction-sequence
    4. (list-union (registers-needed seq1)
    5. (registers-needed seq2))
    6. (list-union (registers-modified seq1)
    7. (registers-modified seq2))
    8. (append (statements seq1) (statements seq2))))
    9. (define (parallel-seq-list seqs)
    10. (if (null? seqs)
    11. (empty-instruction-sequence)
    12. (parallel-2-sequences (car seqs)
    13. (parallel-seq-list (cdr seqs)))))
    14. (parallel-seq-list seqs))

    Now let’s test:

    1. (compile-and-go
    2. '(define (f x)
    3. (g x)))
    4. (total-pushes = 0 maximum-depth = 0)
    5. ;;; EC-Eval value:
    6. ok
    7. ;;; EC-Eval input:
    8. (define (g x) (* x x))
    9. (total-pushes = 3 maximum-depth = 3)
    10. ;;; EC-Eval value:
    11. ok
    12. ;;; EC-Eval input:
    13. (f 5)
    14. (total-pushes = 14 maximum-depth = 5)
    15. ;;; EC-Eval value:
    16. 25

    Works as expected!

  • Exercise 5.48

    To implement given task, we should realize that the assembled instructions, the
    instruction procedures, are just mutating the machine’s state – the contents of
    registers, the contents of stack, (implicitly) the contents of heap. So by
    defining the compile-and-run procedure as primitive in global environment, we
    can actually access the machine state with eceval since at that point,
    eceval defined underlying Scheme’s global environment.

    As it is similar procedure with compile-and-go in that behavior, we can learn
    from that procedure to get idea what we have to do to implement
    compile-and-run.

    As noted above, we can use the current state of machine, and our
    compile-and-run procedure executed with pritivie-apply subroutine, we should
    organize that combination to the analogous expression of external-entry except
    for the initializing process as we are going to use current state.

    That is, we need to arrange compile-and-run to get continue register have
    the return point before goto the newly compiled instructions.

    As apply-primitive-procedure does not change the contents of continue, we
    can change the order as follows safely:

    1. primitive-apply
    2. (restore continue) ;changed
    3. (assign val (op apply-primitive-procedure)
    4. (reg proc)
    5. (reg argl))
    6. (goto (reg continue))

    Then now compile-and-run called with continue register having return point,
    all the left is goto the newly compiled code, which produced from the given
    expression. As we can treat eceval machine as if we were in the outside of
    that machine, we can mimic the behavior of goto rather direct fashion:

    1. (define (compile-and-run user-exp)
    2. ;; provided that continue register contains return point
    3. (let* ((exp (pair*->pair user-exp))
    4. (instructions
    5. (assemble
    6. (statements
    7. (compile exp 'val 'return the-empty-compile-time-env))
    8. eceval)))
    9. (set-register-contents! eceval 'pc instructions)))

    (Here we used pair*->pair since the pair object constructed by user is
    different from the underlying pair object to protect the tagged data.)

    Then let’s test this:

    1. ;;; EC-Eval input:
    2. (compile-and-run
    3. '(define (factorial n)
    4. (if (= n 1)
    5. 1
    6. (* (factorial (- n 1)) n))))
    7. (total-pushes = 5 maximum-depth = 3)
    8. ;;; EC-Eval value:
    9. ok
    10. ;;; EC-Eval input:
    11. factorial
    12. (total-pushes = 0 maximum-depth = 0)
    13. ;;; EC-Eval value:
    14. done

    Unfortunately this won’t work; if we trace the execution, we realize that our
    evaluator start to execute from the second instruction of what compiled, not
    first.

    To verify this observation, let’s modify above defintion as follows

    1. (define (compile-and-run user-exp)
    2. ;; provided that continue register contains return point
    3. (let* ((exp (pair*->pair user-exp))
    4. (instructions
    5. (assemble
    6. (statements
    7. (compile exp 'val 'return the-empty-compile-time-env))
    8. eceval)))
    9. (set-register-contents! eceval 'pc (cons 'dummy instructions))))

    Then we get

    1. ;;; EC-Eval input:
    2. (compile-and-run
    3. '(define (factorial n)
    4. (if (= n 1)
    5. 1
    6. (* (factorial (- n 1)) n))))
    7. (total-pushes = 5 maximum-depth = 3)
    8. ;;; EC-Eval value:
    9. ok
    10. ;;; EC-Eval input:
    11. factorial
    12. (total-pushes = 0 maximum-depth = 0)
    13. ;;; EC-Eval value:
    14. <compiled-procedure>
    15. ;;; EC-Eval input:
    16. (factorial 5)
    17. (total-pushes = 13 maximum-depth = 8)
    18. ;;; EC-Eval value:
    19. 120

    But this strategy destroy the ADT of machine; we would be better to treat
    compile-and-run explicitly like compile-and-go; but it would cause overhead
    to apply primitivie procedures since now it should ensure the flag false
    before applying the primitive procedure.

    It was not a big deal when it comes to the compile-and-go since we start
    machine just once to start up the REPL.

    Whatever, let’s just try this idea:

    1. primitive-apply
    2. (restore continue)
    3. (assign flag (op get-false)) ;initialize the flag
    4. (assign val (op apply-primitive-procedure)
    5. (reg proc)
    6. (reg argl))
    7. (branch (label temporary-external-entry))
    8. (goto (reg continue))
    9. temporary-external-entry
    10. (goto (reg val))

    With

    1. (define (compile-and-run user-exp)
    2. ;; provided that continue register contains return point
    3. (let* ((exp (pair*->pair user-exp))
    4. (instructions
    5. (assemble
    6. (statements
    7. (compile exp 'val 'return the-empty-compile-time-env))
    8. eceval)))
    9. (set-register-contents! eceval 'flag true)
    10. instructions))

    And in the eceval-operations

    1. ;; alternative implementation for exercise 5.48
    2. `(get-false ,(lambda () false))

    Then

    1. ;;; EC-Eval input:
    2. (compile-and-run
    3. '(define (factorial n)
    4. (if (= n 1)
    5. 1
    6. (* (factorial (- n 1)) n))))
    7. (total-pushes = 5 maximum-depth = 3)
    8. ;;; EC-Eval value:
    9. ok
    10. ;;; EC-Eval input:
    11. (factorial 5)
    12. (total-pushes = 13 maximum-depth = 8)
    13. ;;; EC-Eval value:
    14. 120

    Works as expected.

    Or without using get-false, we could exploit following fact

    1. (false? (car '(#f)))
    2. ;Value: #t

    Then

    1. primitive-apply
    2. (restore continue)
    3. (assign flag (const #f)) ;initialize the flag
    4. (assign val (op apply-primitive-procedure)
    5. (reg proc)
    6. (reg argl))
    7. (branch (label temporary-external-entry))
    8. (goto (reg continue))

    Well, this alternative implementation is better in that it does not exploit the
    implementation detail; thus do not cut through the abstraction barrier.

    I think the most reasonable solution would be changing the specifications of the
    machine as to use next-pc explicitly rather than cdr the current pc. If we
    chose that specification, then our original implementation of compile-and-run
    can do their job without breaking the specification by setting the next-pc to
    the newly compiled instruction.

  • Exercise 5.49

    It is straightforward task; we don’t need to design priori:

    1. read-compile-execute-print-loop
    2. (perform (op initialize-stack))
    3. (perform
    4. (op prompt-for-input) (const ";;; EC-Eval input:"))
    5. (assign exp (op read))
    6. (assign env (op get-global-environment))
    7. (assign unev (op compile) (reg exp) (const val) (const return))
    8. (assign unev (op statements) (reg unev))
    9. (assign val (op assemble) (reg unev))
    10. (assign continue (label print-result))
    11. (goto (reg val))
    12. print-result
    13. ;;**following instruction optional -- if use it, need monitored stack
    14. (perform (op print-stack-statistics))
    15. (perform
    16. (op announce-output) (const ";;; EC-Eval value:"))
    17. (perform (op user-print) (reg val))
    18. ;; (goto (label read-eval-print-loop))
    19. (goto (label read-compile-execute-print-loop))

    With

    1. *** eceval-operations
    2. `(compile ,(lambda (exp target linkage)
    3. (compile exp target linkage the-empty-compile-time-env)))
    4. `(statements ,statements)
    5. `(assemble ,(lambda (inst) (assemble inst eceval)))

    Let’s test:

    ```scheme
    (start-eceval)

  1. ;;; EC-Eval input:
  2. (define (factorial n)
  3. (if (= n 1)
  4. 1
  5. (* (factorial (- n 1)) n)))
  6. (total-pushes = 0 maximum-depth = 0)
  7. ;;; EC-Eval value:
  8. ok
  9. ;;; EC-Eval input:
  10. (factorial 5)
  11. (total-pushes = 8 maximum-depth = 8)
  12. ;;; EC-Eval value:
  13. 120
  14. ;;; EC-Eval input:
  15. (factorial 20)
  16. (total-pushes = 38 maximum-depth = 38)
  17. ;;; EC-Eval value:
  18. 2432902008176640000
  19. ;;; EC-Eval input:
  20. (define (fib n)
  21. (if (< n 2)
  22. n
  23. (+ (fib (- n 1)) (fib (- n 2)))))
  24. (total-pushes = 0 maximum-depth = 0)
  25. ;;; EC-Eval value:
  26. ok
  27. ;;; EC-Eval input:
  28. (fib 1)
  29. (total-pushes = 2 maximum-depth = 2)
  30. ;;; EC-Eval value:
  31. 1
  32. ;;; EC-Eval input:
  33. (fib 2)
  34. (total-pushes = 9 maximum-depth = 4)
  35. ;;; EC-Eval value:
  36. 1
  37. ;;; EC-Eval input:
  38. (fib 3)
  39. (total-pushes = 16 maximum-depth = 6)
  40. ;;; EC-Eval value:
  41. 2
  42. ```
  • Exercise 5.50

    The whole idea is straightforward one, as noted in the statement, making all the
    details to work needs extra works. That is, we need to unwind all the higher
    order procedures – e.g. map – or the primitive procedures that should be
    provided underlying Scheme language – e.g. apply-in-underlying-scheme.

    Here is the complement code:

    1. ;;; IN MCEVAL
    2. ;; Unwind higher order primitives
    3. (define (cadr exp) (car (cdr exp)))
    4. (define (cddr exp) (cdr (cdr exp)))
    5. (define (caddr exp) (car (cddr exp)))
    6. (define (cdddr exp) (cdr (cddr exp)))
    7. (define (cadddr exp) (car (cdddr exp)))
    8. (define (caadr exp) (car (cadr exp)))
    9. (define (cadadr exp) (cadr (cadr exp)))
    10. ;;; Rewind all the higher order function
    11. (define (map proc lst)
    12. (if (null? lst)
    13. '()
    14. (cons (proc (car lst))
    15. (map (proc (cdr lst))))))
    16. ;;; IN ECEVAL-SUPPORT
    17. (define set-car!*
    18. (check-error-with
    19. (named-lambda (set-car! p v)
    20. (if (pair?* p)
    21. (set-car! (cdr p) v)
    22. (make-error-exp `("The object " ,p " is not a pair -- set-car!"))))
    23. 2))
    24. (define set-cdr!*
    25. (check-error-with
    26. (named-lambda (set-cdr! p v)
    27. (if (pair?* p)
    28. (set-car! (cddr p) v)
    29. (make-error-exp `("The object " ,p " is not a pair -- set-cdr!"))))
    30. 2))
    31. (define length*
    32. (check-error-with
    33. (named-lambda (length lst*)
    34. (let ((lst (pair*->pair lst*)))
    35. (if (list? lst)
    36. (length lst)
    37. (make-error-exp `("The object " ,lst " is not a list -- length")))))
    38. 1))
    39. (define apply*
    40. (check-error-with
    41. (named-lambda (apply proc* args*)
    42. (apply (primitive-implementation proc*) (pair*->pair args*)))
    43. 2))
    44. (define (list* . args)
    45. (fold-right cons* '(pair) args))
    46. ;; in primitive-procedures
    47. `(apply ,apply*)
    48. `(display ,user-print)
    49. `(error ,error)
    50. `(number? ,number?)
    51. `(string? ,string?)
    52. `(eq? ,eq?)
    53. `(symbol? ,symbol?)
    54. `(not ,not)
    55. `(list ,list*)
    56. `(set-car! ,set-car!*)
    57. `(set-cdr! ,set-cdr!*)
    58. `(length ,length*)
    59. `(read ,read)
    60. `(newline ,newline)
    61. ;;; IN SYNTAX
    62. (define (pair->pair* lst)
    63. (tree-map identity-procedure
    64. (lambda (x y) `(pair ,x ,y))
    65. '(pair)
    66. lst))
    67. ;;; IN ECEVAL-SUPPORT
    68. ;; in user-print
    69. ;; for displaying user defined pair object
    70. ((pair?* object)
    71. (print-pair* object))

    Let’s test!:

    1. (compile-and-go
    2. '(begin
    3. ;;;;METACIRCULAR EVALUATOR FROM CHAPTER 4 (SECTIONS 4.1.1-4.1.4) of
    4. ...
    5. 'METACIRCULAR-EVALUATOR-LOADED
    6. ))
    7. (total-pushes = 9 maximum-depth = 5)
    8. ;;; EC-Eval value:
    9. metacircular-evaluator-loaded
    10. ;;; EC-Eval input:
    11. (define the-global-environment (setup-environment))
    12. ;The object "Too few arguments supplied", passed as the first argument to car, is not the correct type.
    13. ;To continue, call RESTART with an option number:
    14. ; (RESTART 2) => Specify an argument to use in its place.
    15. ; (RESTART 1) => Return to read-eval-print level 1.

    ..Oops! Debugging time! Turns out we mistyped the map procedure. If we fix
    that as follows

    1. (define (map proc lst)
    2. (if (null? lst)
    3. '()
    4. (cons (proc (car lst))
    5. (map proc (cdr lst)))))

    It works as expected:

    ```scheme
    ;;; EC-Eval input:
    (define the-global-environment (setup-environment))

    (total-pushes = 243 maximum-depth = 24)
    ;;; EC-Eval value:
    ok

    ;;; EC-Eval input:
    (driver-loop)

  1. ;;; M-Eval input:
  2. ```
  3. Let's test the behavior:
  4. ```scheme
  5. ;;; M-Eval input:
  6. (car '(1 2))
  7. ;Unknown expression type -- EVAL (car (quote (1 2)))
  8. ;To continue, call RESTART with an option number:
  9. ; (RESTART 1) => Return to read-eval-print level 1.
  10. ```
  11. Oops! This is due to the our `read` er spitting out unwrapped `pair` object –
  12. not the user `pair` object:
  13. ```scheme
  14. ;; IN ECEVAL-SUPPORT
  15. (define read*
  16. (check-error-with
  17. (named-lambda (read)
  18. (pair->pair* (read)))
  19. 0))
  20. ;; in primitive-procedures
  21. `(read ,read*)
  22. ```
  23. Let's retry
  24. ```scheme
  25. ;;; M-Eval input:
  26. (define x (cons 2 3))
  27. ;;; M-Eval value:
  28. ok
  29. ;;; M-Eval input:
  30. x
  31. ;;; M-Eval value:
  32. (2 . 3)
  33. ```
  34. It seems like working as expected except
  35. ```scheme
  36. ;;; M-Eval input:
  37. (car (cons 2 3))
  38. ;;; M-Eval value:
  39. (error (The object (2 . 3) is not a pair -- car))
  40. ```
  41. Need to debug! By reasoning, we should realize this is due to our
  42. inconsiderate `apply*` definition; we shouldn't unwrap the ADT of user pair –
  43. our primitive operations expect the argument to be wrapped.
  44. So, actually we should have defined `apply*` as
  45. ```scheme
  46. (define apply*
  47. (check-error-with
  48. (named-lambda (apply proc* args*)
  49. (apply (primitive-implementation proc*) (map pair->pair* (pair*->pair args*))))
  50. 2))
  51. ```
  52. Now it works as expected:
  53. ```scheme
  54. ;;; M-Eval input:
  55. (cons 2 3)
  56. ;;; M-Eval value:
  57. (2 . 3)
  58. ;;; M-Eval input:
  59. (car (cons 2 3))
  60. ;;; M-Eval value:
  61. 2
  62. ;;; M-Eval input:
  63. (null? '())
  64. ;;; M-Eval value:
  65. #t
  66. ```
  67. Or as what we needed here was convert the top `list*` into `list`, we could do
  68. this as follows:
  69. ```scheme
  70. (define apply*
  71. (check-error-with
  72. (named-lambda (apply proc* args*)
  73. (apply (primitive-implementation proc*)
  74. (fold-right* cons '() args*)))
  75. 2))
  76. (define (fold-right* proc* init* lst*)
  77. (if (null?* lst*)
  78. init*
  79. (proc* (car* lst*)
  80. (fold-right* proc* init* (cdr* lst*)))))
  81. ```
  82. As the counter part of `list*`.
  83. - Conclusion
  84. We have dealt quite complex expressions since we protected the data structures
  85. we made to represent components composing our underlying environment (by making
  86. `pair` object). As this process erected several "Scheme" languages – universal
  87. register machine, which also used to execute compiled code, that evaluate Scheme
  88. expression erected on underlying Scheme, and in that universal machine, we
  89. execute the code resulted from compiling the meta circular evaluator Scheme program.
  90. Whew! It was quite complicated process to make it work than I ever thought!
  • DONE Exercise 5.51

    This exercise and the last exercise (the one after this) would be longer journey
    than any one we ever have encountered. We should translate the explicit-control
    evaluator of preceding section into low-level language – the typical one would
    be C programming language.

    They warned us that we should provide appropriate storage-allocation routines
    and other run-time support – error handling, dynamic type handling, and so on.

    As I’ve not done any rigorous C programming before, I’d better to go off and
    learn C to implement this and the last exercise; for this learning, I’ve chosen
    the classic book, The C programming language, 2nd edition.

    Meanwhile, I’ve run through the wild C development environment. I’ve visited 3
    books as my main mentor of learning C:

    1. The C programming language, 2nd edition
      As I’ve written above, this was my first C self study reference. This is
      classic book, and has lots of tricks for programming, not just about the C.
      The exercises are quite hard; but it depends on the one’s perspective on
      interpretation of being requested. If you want to produce reasonable
      solution, you’re going to experience the weirdness of C in comparison with
      other higher level languages, which are full of run time support.

      So, from here, I’ve felt that I should have good tool to debug; I’ve searched
      unit test harness in C, I found TDD for embedded C.

    2. TDD for embedded C
      This is another great book has lots of C code in it, which makes you feel
      comportable in that it looks like object-oriented programming in C. It
      teaches a lot of techniques to make the code testable, how to develop the
      program from the requirement interpretation.

      But this method is more intricate than the wishful thinking we’ve learned
      from the SICP; it enforces the disciplines for us to follow without any
      comprehensive explanations at the first stage. This gives another experience
      from which we can reconsider the abstract method of how to program: We do
      usually not understand well the structure we should implement at the first
      state where we interpreting the requirements. In wishful thinking, we
      experiments with the requirement in abstract model – in manual way; whereas
      TDD let us express what the behavior we wishes in a concrete way – via test
      suites. Within this safety net, we are good to be creative as long as it
      passes tests. And then we switches our own phase to be top down defensive
      rather than bottom up creative mode. We divides the big monolithic code chunk
      into smaller pieces with close relations (like object!).

      However this book nearly do touch the mechanisms of linking and preprocessing
      and so on, with which C operates. To solve this mysterious concepts, I’ve
      touched yet another book, which is written in a modern way, Learn C The Hard Way.

    3. Learn C The Hard Way
      With this book, we can get the gist of C programming language, and also get
      the mindset for anyone who going to program in C; the defensive programming
      and so called undefined behaviors in C.

      Actually, without using any test harness, we can code them by ourselves; it
      carries us quite far with a couple of macros. In turns which represents the
      power of C macros implicitly.

    With these tour, I finally started to code the project. Here is the test code.
    I’ve finally done with this way long exercise with properties:

    • No memory leakage – all Scheme object is managed by garbage collector
      (obarray is managed separately.)
    • Garbage collection do his job at the right moment – it do start his job otherwise
      heap can not afford to allocate requested datum.
  • DONE Exercise 5.52

    Done this long journey with compiler. I’ve extracted relevant parts from
    previous exercise into library.

Footnotes

1 We sense more and more replicative patterns here

2 In fact, it is same procedure as fringe.

3 Well, there are several candidates for doing this like plantUML, dot
, etc. If I can afford to learn those, I’d like to upload these study note as
well someday.

4 Note that we naturally inherit the lisp’s power, that is, of manipulating
list. That means we don’t have to parse all the expression to get the token,
which we have to do usually when we implement the complier or interpreter.

5 Here we say “powerful” in that meaning of chapter 1, having closure property in it.

6 The definition of sparse and dense polynomials are defined in the text book.

7 There is yet another approach, namely type class being adopted and
introduced in Haskell.

8 We could have implement this multiplying integerizing factor to dividend by
introducing new procedure that multiply scalar factor to term list using analogy
with neg-terms; however we chose not to: We just wanted to experiment with
this feature and after that, if we find this feature was the neck of efficiency
then, at the very that point, we can improve this with described algorithm.

9 If we could have that ability, we
can implement BFS. But it would cause chaos into the evaluator.

10 We chose to
append bang(!) at the end of the name to capture the meaning of forcing as used
in Vim unlike in our convention of Scheme, which use bang to indicate the
mutation.