在DrRacket中编译SICP图片练习?

13
我正在进行自学,阅读SICP书籍,并且现在正在第二章的图形语言部分。在之前的练习中我一直使用DrRacket,但是当我尝试基于本书中“draw-line”图片函数进行练习时,我遇到了编译错误。
具体来说,这段代码...
(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)))

... 会产生这个错误 ...

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

我在论坛上做了一些研究,并安装了Neil Van Dyke提供的SICP软件包(http://www.neilvandyke.org/racket-sicp/#(part._usage))。我按照指示执行了所有步骤,将语言更改为SICP,但仍然出现相同的错误。

我认为此软件包的目的是定义这个“内置”函数(以及书中的其他函数)。提前预测一些问题,我的文件中没有'require'语句,并使用'#lang planet neil/sicp'来指定语言,而不是使用菜单(我还尝试使用菜单将语言更改为SICP,但得到了一个更奇怪的错误;请参见下面的附言)。我的环境是Windows 7,DrRacket的版本是5.3.1。

也许我只是犯了一个新手错误;任何见解都将不胜感激。

谢谢。

附言:对于那些感兴趣的人,当我使用菜单将语言设置为'SICP(PLaneT 1.17)'时,我会获得以下任何定义尝试编译的错误(即使是最微不足道的定义)...

<unsaved editor>:1:0: #%top-interaction: unbound identifier;
also, no #%app syntax transformer is bound in: #%top-interaction

1
看起来你正在使用的库缺少draw-line的实现。 话虽如此,这个遗漏似乎是有意为之的,因为它除了在segments->painter中没有被任何人使用,而且segments->painter已经作为库的一部分提供给你了。其他人已经向你展示了如何使用DrRacket的原始绘图库从头编写draw-line,但是对于SICP的图片语言练习,你也不需要直接使用它。 - dyoo
1
跟进一下,你正在阅读的那段文字是针对图片语言的实现者而非图片语言的用户。实现者将会为segments->painter撰写定义。你可以在DrRacket中看到它是如何完成的:http://planet.racket-lang.org/package-source/soegaard/sicp.plt/2/1/prmpnt.scm。因此,在那里有一个`draw-line`的定义(实际上,在那里它被称为`draw-line-on-screen`),但作为图片语言的用户,你不应该使用它。 - dyoo
非常有趣。显然我做了比我需要的更多的工作 - 请看我的第二篇帖子。感谢dyoo的建议。 - Kevin Finch
5个回答

9
在Racket中,这些定义解决了我在SICP第2章中绘图方面的问题,之后我成功地完成了相关练习。
(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))))))

谢谢,奥斯卡。请您看一下我下面的帖子。 - Kevin Finch

5

我希望感谢alinsoar、flamingo和Oscar提供的非常有帮助的建议。

我决定采用Oscar的方法,他使用了racket图形库,而不是Neil Van Dyke准备的特殊包(我尝试过但没有成功)。这是我的代码的重要部分(省略了与图形库无关的定义):

#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)))

因此,有几个需要注意的事项:

1)如上所述,这使用的是标准的racket语言,而不是Neil Van Dyke实现的SICP语言。

2)该库中的'draw-line'函数接受一个视口(基本上是一个窗口)作为参数,并创建一个接受两个坐标参数(以及我未使用的可选颜色参数)的函数。然而,在这种情况下,'坐标'并不是练习中使用的简单向量。坐标是“posn”结构的实例,它基本上只是x和y值的包装器。

出现这个posn数据类型意味着在将我的向量用于'segments-painter'函数之前,我必须使用posn构造函数来包装它们。 ('vector-to-posn'函数就是这个包装器)。还要注意,在书中关于'segments-painter'的定义中使用的单词'draw-line'被替换为'line',它定义为(draw-line vp)。

3)有趣的是,racket图形库对视口坐标进行了与我预期略有不同的定义。坐标(0 0)是框架中的左上点(我本来猜测底部左侧),而(1 1)是右下角。(请注意,这里我是根据单位正方形来说话的。在我上面的代码中使用的框架中,底部右侧的真实坐标将是(500 500)。)

由于我正在处理具有关于水平轴对称性的练习(2.49),因此这个小错误并不重要,但否则可能会让你感到惊讶。我想到了解决这个'inversion'的一种方法是使用书中的'flip-vert'函数,但我没有花时间去做。

再次感谢您的所有帮助!


谢谢,这正是我需要在DrRacket中使事情正常运转的方法。解决“垂直翻转”问题的简单方法是按以下方式定义您的框架:(let ((fr (make-frame (make-vect 0 500) (make-vect 500 0) (make-vect 0 -500)))) (wave-painter fr)) - qu1j0t3

2

我也使用DrRacket做SICP练习。因此,为了使用绘图函数,您应该在源文件的顶部添加这些行:

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

然后,您应该设置图形上下文,如下所示:

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

接下来,您可以画一条线:

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

并将结果保存到文件中:

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

这是我在第二章练习中的解决方案


谢谢,flamingo。请看我的第二篇帖子。 - Kevin Finch

1
大多数练习只需从planet加载sicp模块,并在每个文件的顶部添加以下语言声明即可:#lang planet neil/sicp
对于图片语言问题,请改用:#lang planet neil/sicp (#%require sicp-pict)
以下内容缺失: 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)))))

罗杰斯:
用替代的基本元素einstein替换

如果您还想为练习2.49实现自己的sicp-pict模块,可能最简单的方法是使用htdp教学语言中的图像库。将以下内容放在程序顶部:

#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

我的方法有点简单:即使你已经全部实现了,也不要使用自己的定义来完成Ex.2.49,而是使用内置的函数。

#lang sicp
(#%require sicp-pict)

诚然,这样做可能不太令人满意,但它可以快速完成任务,让您专注于练习的重要部分,而不是纠结于解决问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接