12
votes

I am going through SICP as a self-study and am on the picture language section in Chapter 2. I have been using DrRacket for the earlier exercises, but I get compilation errors when trying to do an exercise based on the 'draw-line' picture function in this section of the book.

Specifically, this code ...

(define (segments->painter segment-list)   
 (lambda (frame)     
  (for-each     
   (lambda (segment)        
    (draw-line         
     ((frame-coord-map frame) (start-segment segment))         
     ((frame-coord-map frame) (end-segment segment))))      
 segment-list)))

...produces this error ...

draw-line: unbound identifier in module in: draw-line

So I did a bit of research on this forum and installed the SICP package that Neil Van Dyke offers (http://www.neilvandyke.org/racket-sicp/#(part._usage)). I followed all of the steps, changed the language to SICP as directed, but still get the same error.

I assumed that the purpose of this package was to have defined this 'built-in' function (as well as others in the book). Just to anticipate some questions, I do not have 'require' statements in the file and used '#lang planet neil/sicp' to specify the language instead of using the menu (I also tried changing the language to SICP using the menu and get an even stranger error; see the postscript below). My environment is Windows 7 and the version of DrRacket is 5.3.1.

Perhaps I am just making a rookie mistake; any insight would be appreciated.

Thanks.

PS: For those interested, when I set the language to 'SICP (PLaneT 1.17)' using the menu, I get the following error for any definition that I try to compile (even the most trivial)...

<unsaved editor>:1:0: #%top-interaction: unbound identifier;
also, no #%app syntax transformer is bound in: #%top-interaction
5
It appears the library you're using is missing an implementation for draw-line. That being said, this omission appears to be by design, because it's not used anyone except for in segments->painter, and segments->painter is already given to you as part of the library. Other people have shown you how to write a draw-line from scratch using DrRacket's primitive drawing libraries, but you don't need to use it directly anyway for SICP's picture language exercises.dyoo
To follow up, that paragraph you're reading is meant for an implementor of the picture language, not a user of the picture language. An implementer would be writing up a definition for segments->painter. You can see how it's being done in DrRacket: planet.racket-lang.org/package-source/soegaard/sicp.plt/2/1/…. So there is a definition for draw-line in there (actually, in there it's called draw-line-on-screen), but you are not supposed to use it as a user of the picture language.dyoo
Very interesting. Apparently I did a lot more work than I needed to - see my second posting. Thanks dyoo for the input.Kevin Finch

5 Answers

7
votes

In Racket, these definitions solved my problems with the drawings in chapter 2 of SICP, I solved successfully the exercises after that:

(require graphics/graphics)
(open-graphics)
(define vp (open-viewport "A Picture Language" 500 500))

(define draw (draw-viewport vp))
(define (clear) ((clear-viewport vp)))
(define line (draw-line vp))

(define (make-vect x y)
  (cons x y))

(define (xcor-vect v)
  (car v))

(define (ycor-vect v)
  (cdr v))

(define (add-vect v1 v2)
  (make-vect (+ (xcor-vect v1)
                (xcor-vect v2))
             (+ (ycor-vect v1)
                (ycor-vect v2))))

(define (sub-vect v1 v2)
  (make-vect (- (xcor-vect v1)
                (xcor-vect v2))
             (- (ycor-vect v1)
                (ycor-vect v2))))

(define (scale-vect s v)
  (make-vect (* s (xcor-vect v))
             (* s (ycor-vect v))))


(define (make-frame origin edge1 edge2)
  (list origin edge1 edge2))

(define (origin-frame f)
  (car f))

(define (edge1-frame f)
  (cadr f))

(define (edge2-frame f)
  (caddr f))

(define (frame-coord-map frame)
  (lambda (v)
    (add-vect
     (origin-frame frame)
     (add-vect (scale-vect (xcor-vect v)
                           (edge1-frame frame))
               (scale-vect (ycor-vect v)
                           (edge2-frame frame))))))
5
votes

I want to thank alinsoar, flamingo, and Oscar for their very helpful advice.

I decided to go with Oscar's approach, which (apparently) uses the racket graphic libraries as opposed to the special package put together by Neil Van Dyke (I simply had no luck with that one). Here is the important part of my code (with the definitions that are not graphics library dependent left out):

#lang racket
(require graphics/graphics)
(open-graphics)
(define vp (open-viewport "A Picture Language" 500 500))

(define draw (draw-viewport vp))
(define (clear) ((clear-viewport vp)))
(define line (draw-line vp))

;need a wrapper function so that the graphics library works with my code...
(define (vector-to-posn v)
  (make-posn (car v) (car(cdr v))))

(define (segments->painter segment-list)   
  (lambda (frame)     
   (for-each     
     (lambda (segment)        
      (line         
        (vector-to-posn ((frame-coord-map frame) (start-segment segment)))         
        (vector-to-posn ((frame-coord-map frame) (end-segment segment)))))      
      segment-list)))

So, a few things to note:

1) as stated above, this uses the standard racket language, not the SICP language that is the purpose of Neil Van Dyke's implementation.

2) the 'draw-line' function in this library takes a viewport (which is basically a window) as a parameter and creates a function that takes two coordinates for parameters (and an optional color parameter that I did not use). However the 'coordinates' in this case are not the simple vectors that the exercises use. The coordinates are instances of the struct 'posn', which is basically just a wrapper for the x and y values.

The presence of this posn datatype meant that I had to wrap my vectors with a posn constructor before they could be used in the 'segments-painter' function. (The 'vector-to-posn' function is this wrapper). Note also that the word 'draw-line' used in the book's definition of 'segments-painter' is replaced with 'line', which is defined to be (draw-line vp).

3) interestingly, the racket graphics library defines the viewport coordinates a bit differently from what I had anticipated. The coordinate (0 0) is the top left point in the frame (I would have guessed bottom left) and (1 1) is bottom right. (Note that here I am talking in terms of the unit square. In the frame that I use in the code above the true coordinates for bottom right would be (500 500).)

Since the exercise that I was working on (2.49) involved drawing figures that have symmetry about the horizontal axis this little wrinkle does not matter, but otherwise it might surprise you. It occurs to me that one way around this 'inversion' is to use the 'flip-vert' function in the book, but I did not take the time to do that.

Thanks again for all of the help!

2
votes

I do SICP exercises using DrRacket too. So, in order to use drawing functions you should add this lines in the top of your source file:

(require (lib "racket/draw"))
(require racket/class)

Then you should setup graphics context, like so:

(define target (make-bitmap 100 100))
(define dc (new bitmap-dc% [bitmap target]))

After this you can draw a line:

(send dc draw-line
  (x1 y1) (x2 y2)
  (x3 y3) (x4 y4))

And save results into a file:

(send target save-file "foo.png" 'png)

Here's my solution of an exercise from the chapter 2

1
votes

For the majority of exercises, it is enough to load the sicp module from planet and add the following language declaration at the top of each file:

#lang planet neil/sicp

For the picture language questions, use instead:

#lang planet neil/sicp (#%require sicp-pict)

The following are missing:

wave:

(define wave
  (segments->painter
   (list
    (make-segment (make-vect 0.20 0.00) (make-vect 0.35 0.50))
    (make-segment (make-vect 0.35 0.50) (make-vect 0.30 0.60))
    (make-segment (make-vect 0.30 0.60) (make-vect 0.15 0.45))
    (make-segment (make-vect 0.15 0.45) (make-vect 0.00 0.60))
    (make-segment (make-vect 0.00 0.80) (make-vect 0.15 0.65))
    (make-segment (make-vect 0.15 0.65) (make-vect 0.30 0.70))
    (make-segment (make-vect 0.30 0.70) (make-vect 0.40 0.70))
    (make-segment (make-vect 0.40 0.70) (make-vect 0.35 0.85))
    (make-segment (make-vect 0.35 0.85) (make-vect 0.40 1.00))
    (make-segment (make-vect 0.60 1.00) (make-vect 0.65 0.85))
    (make-segment (make-vect 0.65 0.85) (make-vect 0.60 0.70))
    (make-segment (make-vect 0.60 0.70) (make-vect 0.75 0.70))
    (make-segment (make-vect 0.75 0.70) (make-vect 1.00 0.40))
    (make-segment (make-vect 1.00 0.20) (make-vect 0.60 0.48))
    (make-segment (make-vect 0.60 0.48) (make-vect 0.80 0.00))
    (make-segment (make-vect 0.40 0.00) (make-vect 0.50 0.30))
    (make-segment (make-vect 0.50 0.30) (make-vect 0.60 0.00)))))

rogers:
replace with the alternative primitive einstein

If you also want to implement your own version of the sicp-pict module for exercise 2.49, probably the simplest way is to use the image library in the htdp teaching language. Put the following at the top of your program:

#lang sicp
(#%require 2htdp/image)
(#%require lang/posn)
(#%require racket/base)

(define *current-image* empty-image)  

(define (*new-image* new-frame)
  (define (xy->posn x y)
    (let ((v ((frame-coord-map new-frame) (make-vect x y))))
      (make-posn (xcor-vect v) (ycor-vect v))))
  (set! *current-image*
        (polygon
         (list
          (xy->posn 0 0)
          (xy->posn 0 1)
          (xy->posn 1 1)
          (xy->posn 1 0))
         "solid"
         "white")))  

(define (draw-line start end)
    (set! *current-image*
        (add-line
         *current-image*
         (xcor-vect start)
         (ycor-vect start)
         (xcor-vect end)
         (ycor-vect end)
         "black")))  

(define (paint-in-frame painter frame)
    (*new-image* frame)
    (painter frame)
    *current-image*))  

(define (paint painter)
    (paint-in-frame
        painter
        (make-frame
            (make-vect 0 150)
            (make-vect 150 0)
            (make-vect 0 -150))))
0
votes

My approach is somewhat simpler: Despite just having them all implemented yourself, just don't use any of your own definitions for Ex.2.49 but the built-ins you get with

#lang sicp
(#%require sicp-pict)

It's admittedly not as satisfying, but it get's the job done quickly and let's you focus on the important part of doing the exercise instead of fiddling with workarounds.