brian_hassinger

Software Developer

Introduction to Data Abstraction

Finishing Chapter 1 of The Structure and Interpretation of Computer Programs feels excellent! Let's keep the mojo flowing and power on through to Chapter 2.

One thing I love about working through this book is developing a solution, and then comparing it to the work of other people. I'm often amazed at how different or similar our answers can be. There is only one exercise for the first section of chapter 2, so I will highlight the differences in answers from various sources.

Exercise 2.01

Define a better version of make-rat that handles positive and negative arguments, normalizing the signs.

(define (make-rat n d)
  (if (> 0 d)
    (make-rat (- n) (- d))
    (let ((g (gcd (abs n) (abs d))))
        (cons (/ n g) (/ d g)))))

My solution when the denominator is less than 0 is to flip both numerator and denominator and pass them back into the make-rat procedure. Also I found I had to use the abs values of n and d for the gcd otherwise the signs would not be preserved.

On to [Eli Bendersky][eli]. Keep in mind Eli is using Common Lisp. [eli]: http://eli.thegreenplace.net/2007/07/25/sicp-sections-211-212/

(defun make-rat (n d)
  (labels (
    (make-rat-reduce (n d)
      (let ((g (gcd n d)))
        (cons (/ n g) (/ d g)))))

    (cond ((and (< n 0) (< d 0))
            (make-rat-reduce (- n) (- d)))
          ((and (< d 0) (> n 0))
            (make-rat-reduce (- n) (- d)))
          (t
            (make-rat-reduce n d)))))

Here Eli has a condition to check when the numerator and denominator are both negative, or only the denominator is negative. In both cases the solution is to negate both numerator and denominator. If neither case is true then just proceed as normal. I haven't run Eli's code, but I suspect his gcd will not preserve the signs as he intends. This is similar to a problem I ran into with my solution which is why I use the abs for g.

On to [Bill the Lizard][bill]'s solution. [bill]: http://www.billthelizard.com/2010/09/sicp-21-rational-numbers.html

(define (make-rat n d)
  (let ((g (gcd n d)))
    (if (< d 0)
        (cons (/ (* n -1) g) (/ (* d -1) g))
        (cons (/ n g) (/ d g)))))

Bill's solution is much simpler. Here the numerator and denominator are negated only if the denominator is less than zero. This negation is done directly in the cons procedure by the multiplication primitive.

On to [flaming0][flame]'s solution. [flame]: https://github.com/flaming0/SICP/blob/master/ch2/ex2-1.scm

(define (make-rat n d)
  (let ((g (gcd n d)))
    (if (< d 0)
        (cons (/ (- n) g) (/ (- d) g))
        (cons (/ n g) (/ d g)))))

Flaming0's solution is identical to Bill's except flaming0 uses the negative procedure to produce a negative value. Subtle difference, but I love it!

And finally, the [Scheme Community Wiki][scw] solution. [scw]: http://community.schemewiki.org/?sicp-ex-2.1

(define (make-rat n d) 
   (let ((g ((if (< d 0) - +) (gcd n d)))) 
     (cons (/ n g) (/ d g)))) 

This solution is a bit more clever than the rest. It will negate the gcd if the denominator is less than zero. This change of signs is applied to both numerator and denominator during the division by g in the cons process.

After running through all these solutions my favorite is flaming0's for it's simplicity and legibility. All the differences here are rather subtle, but each have their own merits and show how a self-taught course in SICP is interesting.