<< Previous exercise (2.1) | Index | Next exercise (2.3) >>

;; ex 2.2. Straightforward ;; Point (define (make-point x y) (cons x y)) (define (x-point p) (car p)) (define (y-point p) (cdr p)) (define (print-point p) (newline) (display "(") (display (x-point p)) (display ",") (display (y-point p)) (display ")")) ;; Segment (define (make-segment start-point end-point) (cons start-point end-point)) (define (start-segment segment) (car segment)) (define (end-segment segment) (cdr segment)) (define (midpoint-segment segment) (define (average a b) (/ (+ a b) 2.0)) (let ((a (start-segment segment)) (b (end-segment segment))) (make-point (average (x-point a) (x-point b)) (average (y-point a) (y-point b))))) ;; Testing (define seg (make-segment (make-point 2 3) (make-point 10 15))) (print-point (midpoint-segment seg))

I think the above solution misses part of the point about abstraction barriers; midpoint-segment reaches through both layers to achieve its goal.

(define (average-points a b) (make-point (average (x-point a) (x-point b)) (average (y-point a) (y-point b)))) (define (midpoint-segment seg) (average-points (start-segment seg) (end-segment seg)))

Actually, I don’t think the first solution violates abstraction barriers at all. It’s almost completely analogous to the rational arithmetic procedures shown in the original text. Segment is directly above points in the abstraction ladder, so it’s natural that segment uses point interface procedures like make-point, x-point, etc. The “average-points” procedure in your solution is simply another procedure added alongside make-point, etc. It improves code because the package directly above points can just use average-points without writing out the whole thing again and again.

In other words, it solves the problem of code repetition, and is quite different from the problem of abstraction barriers.

Wrote a solution to demo real-world abstractions.

; An overview of how a real-world library would abstract details ; We wrap cons in make-point as a way to document that ; first argument is x, second is y (define (make-point x y) (cons x y)) ; x-point and y-point can be aliased to car and cdr because we don't lose any info (define x-point car) (define y-point cdr) (define (print-point p) (newline) (display "(") (display (x-point p)) (display ", ") (display y-point p) (display ")")) ; Abstracting out scalar operations on points with numbers (define (apply-scalar-op op p val) (make-point (op (x-point p) val) (op (y-point p) val))) ; Building scalar ops using our abstraction (define (divide-point p divisor) (apply-scalar-op / p divisor)) ; Abstracting out application of scalar operation b/w points (define (apply-scalar-op-on-points op p1 p2) (make-point (op (x-point p1) (x-point p2)) (op (y-point p1) (y-point p2)))) ; Building scalar ops using our abstraction (define (add-points p1 p2) (apply-scalar-op-on-points + p1 p2)) ; Again, maintaining name of the arguments here (define (make-segment start-point end-point) (cons start-point end-point)) (define start-segment car) (define end-segment cdr) ; Defining midpoint using our existing abstraction (define (midpoint-segment segment) (divide-point (add-points (start-segment segment) (end-segment segment)) 2.0))

Straightforward. Coming from Java/Ruby/C++, I can't say I care for the data being shoved arbitrarily into a list, but I guess that's why the layered approach and strict enforcement of using accessors like x-point is necessary.