SICP section 1.1

June 21st, 2007 at 5:50 am

This is the first real post in the “SICP reading” series. It refers to section 1.1 of the book, discussing interesting points and solving selected exercises (I’m trying to fully solve all the non-trivial ones).

Sections 1.1.1 – 1.1.6

This section presents the very basics of Lisp (the Scheme dialect, to be exact). I think that the choice of Lisp for a teaching language can be clearly justified by seeing how short and simple the section is. The basic rules and forms presented here are enough to write very complex Lisp programs.

Substitution model for procedure application

In order to understand what’s going under the hood when a Lisp procedure is called, the authors present a simplified model:

To apply a compound procedure to arguments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument.

This model will work for the first few chapters of the book but later will break done when closures and environments are presented. For now, however, it is a useful tool for mechanical understanding of how Lisp evaluates expressions.

Exercise 1.4

This is an interesting example of how Common Lisp is different from Scheme. I want to dwell on it here for a moment. The function presented is (Scheme)

  (define (a-plus-abs-b a b)
    ((if (> b 0) + -) a b))

The if returns a function, either + or -, according to its argument. The function is then applied to a and b.

To rewrite it in Common Lisp, we must remember that unlike Scheme, Common Lisp has a separate namespace for functions. Therefore the if must return something CL will recognize as a function, and which will then be explicitly applied to the arguments

  (defun a-plus-abs-b (a b)
    (funcall (if (> b 0) #'+ #'-) a b))

The different name and syntax of defun as opposed to define aside, see the usage of #' as function specifiers (meaning “the following symbol is a function”) and funcall as an explicit application of the functions.

By the way, #' is a shorthand for the function special form, so the above can be rewritten in CL as

  (defun a-plus-abs-b (a b)
    (funcall (if (> b 0) (function +) (function -)) a b))

Exercise 1.5

The authors presented normal-order evaluation vs. applicative-order evaluation in section 1.1.5. Applicative-order is what Scheme / CL actually use, while normal-order evaluation is a useful tool which is sometimes coded explicitly (and the authors promise to show how in chapters 3 and 4).

Anyway, the code presented for this exercise is (Scheme):

  (define (p) (p))

  (define (test x y)
    (if (= x 0)
        0
        y))

  (test 0 (p))

Obviously, the definition of p is invalid – calling it calls p again, and so on recursively ad infinitum (a good interpreter will probably catch it as a stack overflow, or infinite recursion).

So, when the call to test is evaluated in applicative order, its arguments are evaluated first. Hence, (p) is evaluated, and the program fails with an error. However, with normal order evaluation, (p) is not evaluated until it’s actually needed, which never happens, because the comparison of x with 0 suceeds and the call just
returns 0.

Section 1.1.7

A very nice quote about the difference between mathematical functions and programming procedures:

The contrast between function and procedure is a reflection of the general distinction between describing properties of things and describing how to do things, or, as it is sometimes referred to, the distinction between declarative knowledge and imperative knowledge. In mathematics we are usually concerned with declarative
(what is) descriptions, whereas in computer science we are usually concerned with imperative (how to) descriptions.

Exercise 1.6

The normal if form is special – it evaluates its then-clause part only if the predicate is true, and its else-clause part only if the predicate is false. The new-if function proposed here evaluates both parts, always (applicative-order, remember?). This is easily demonstrated:

  (defun new-if (predicate then-clause else-clause)
    (cond (predicate then-clause)
          (t else-clause)))

  (print "new-if:")
  (new-if (= 2 3) (print "yes") (print "no"))

  (print "if:")
  (if (= 2 3) (print "yes") (print "no"))

So when used in sqrt-iter, new-if will cause an infinite loop by repeatedly calling itself, even when the guess is good enough.

Exercise 1.7

There indeed is a problem with very large and very small numbers in the current implementation. For example, (mysqrt 92400000000000000) fails with a “program stack overflow” message, while (mysqrt 0.0005) gives a bad result – 0.036, instead of the expected 0.022 (Note that I changed the function name to mysqrt in order to avoid a clash with the sqrt built into CL.

The problem with very small numbers is easy to understand. Since we have an explicit epsilon value in guess – 0.001, the function will give incorrect results for inputs that approach the epsilon. Consider the 0.005 I used in my example – the square of 0.022 is 0.00484, which is within 0.001 of 0.005, while 0.022 is 50% smaller than the correct answer 0.036;

The problem with large numbers is a little more tricky. The square root computations must be performed in floating point arithmetic, which has limited precision in CL systems (on the other hand, integer arithmetic is arbitrarily large). Floating point numbers are usually represented with an exponent and a mantissa, and when the exponent
grows large, the “quantas” the number can advance in become large. In our example, after a few iterations the number 3.039737E8 was reached and from there the process entered infinite recursion because (improve 3.039737E8) returns 3.039737E8 itself.

To solve the problem, we’ll do as suggested by the authors, and implement good-enough? as a test of how guess changes from one iteration to the next and stop when the change is a very small fraction of the guess. Here is the improved code:

  (defun  sqrt-iter (guess x)
    (let ((improved-guess (improve guess x)))
      (if (close-enough? guess improved-guess)
        improved-guess
        (sqrt-iter improved-guess x))))

  (defun improve (guess x)
    (average guess (/ x guess)))

  (defun average (x y)
    (/ (+ x y) 2))

  (defun close-enough? (a b)
    (let ((ratio (/ a b)))
      (and (< ratio 1.001) (> ratio 0.999))))

  (defun square (x)
    (* x x))

  (defun mysqrt (x)
    (sqrt-iter 1.0 x))

Note: I implemented square explicitly because it’s not built into CL. Also, I’ve used the let form in this code although it wasn’t taught in the book yet.

The good-enough? function was replaced by close-enough? that is used to judge whether two consecutive guesses are close enough to each other. The real improvement here is that this judgement does not rely on some fixed number like 0.001 but rather on a quantity relative to the guess itself. As expected, this new method provides good results for the whole range of numbers, including very small and very large.

Exercise 1.8

Naturally, the code is similar to exercise 1.7, except for the improve function which follows the formula given in the book:

  (defun improve (guess x)
    (/ (+ (/ x (square guess))
          (* 2 guess))
        3)))

Related posts:

  1. SICP section 1.3.4
  2. SICP section 1.3.1
  3. SICP section 3.3.2
  4. SICP section 2.2.1
  5. SICP section 4.2.2

9 Responses to “SICP section 1.1”

  1. Thomas KapplerNo Gravatar Says:

    Hey, how cool to find this by accident (via technorati.com). Over the last few years, I’ve been dabbling on and off in Scheme, and just recently I decided to work through SICP. While I can only start in two months or so, I’ll be following your endeavour and I’ll get back to the posts when I’m at the corresponding position in the book (hopefully after doing the assignments!).

    It’s especially cool that you’re doing it in CL, since I’ve been reading a lot about the differences between CL and Scheme and wondered which one to tackle. That will give me some opportunity to compare.

    Best,
    Thomas

  2. elibenNo Gravatar Says:

    How cool that you’ve found it only a few hours after the post !

    I hope you enjoy the posts, and I hope I will write them consistently.

  3. LievenNo Gravatar Says:

    As a style note,

  4. AlpinweisNo Gravatar Says:

    Hi and greetings from Moldova! Glad I bumped into your lisp pages. I am a C&C# programmer on my way through SICP myself (using CL) and just out of interest wanted to have a look at your solution to ex 1.3, but haven’t seen one.
    It’s really useful what you do so keep up the good job!

  5. elibenNo Gravatar Says:

    I skipped the first 3 exercises because they seemed too trivial. Ex. 1.3 would’t be different from the solution in C or C#, simply a “case” that finds out the two largest numbers and returns the required computation.

  6. aleixNo Gravatar Says:

    Thank you very for all this stuff. A great work, really. I’ve also taken (or trying to) the challenge to read SICP and do the exercises (don’t know if I’ll get that far).

    Just a note on Ex. 1.7. I have implemented it just by passing a new parameter “old-guess” to the sqrt-iter function:

    (define (square-root x)
    (sqrt-iter 1.0 0.0 x))

    (define (sqrt-iter guess old-guess x)
    (if (good-enough? guess old-guess)
    guess
    (sqrt-iter (improve guess x) guess x)))

    (define (improve guess x)
    (average guess (/ x guess)))

    (define (average x y)
    (/ (+ x y) 2))

    (define (good-enough? guess old-guess)
    (

  7. aleixNo Gravatar Says:

    Uppps… sorry.

    (define (square-root x)
    (sqrt-iter 1.0 0.0 x))

    (define (sqrt-iter guess old-guess x)
    (if (good-enough? guess old-guess)
    guess
    (sqrt-iter (improve guess x) guess x)))

    (define (improve guess x)
    (average guess (/ x guess)))

    (define (average x y)
    (/ (+ x y) 2))

    (define (good-enough? guess old-guess)
    (< (abs (- guess old-guess)) 0.001))

    This solves the problem for small numbers (except I still have a tolerance for the precision we want). Your “closed-enough?” suffers from divsion by zero for very small numbers (at least in DrScheme).

    For large numbers, as you said, the problem is with floating point arithmetic, and this is because of the “improve” procedure. So, in my opinion, the problem is not solved even if we use this new method.

    Is this correct?

  8. aleixNo Gravatar Says:

    Hi again,

    about the division by zero I commented above, forget it, it was an error of mine.

    And about my “good-enough?” implementation, I think is much better as it doesn’t suffer from my precision problem (at least not so much).

  9. Mike SluyterNo Gravatar Says:

    Re: exercise 1.6. For a while I was mislead by the cond expression in new-if, thinking that somehow it was different from an ‘if’ expression, and that that was the essential issue. But it turns out that the actual body of new-if is irrelevant. In fact, the infinite recursion will happen even if the definition of new-if is something like:

    (define (new-if predicate then-clause else-clause) 17)

    That is, even if new-if doesn’t really do anything interesting (in this case, returns a constant), the problem will still occur, because its arguments will be evaluated in applicative order before the body of new-if is even reached.

Leave a Reply

You can use these HTML tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> .
To post code with preserved formatting, enclose it in `backticks` (even multiple lines)