灯下 登录
计算机科学 / SICP / 2.2.4 Example: A Picture Language

Exercise 2.45 · 习题

Exercise 2.45: Right-split and
up-split can be expressed as instances of a general splitting operation.
Define a procedure split with the property that evaluating

(define right-split (split beside below))
(define up-split (split below beside))

produces procedures right-split and up-split with the same
behaviors as the ones already defined.

Frames

Before we can show how to implement painters and their means of combination, we
must first consider frames. A frame can be described by three vectors—an
origin vector and two edge vectors. The origin vector specifies the offset of
the frame’s origin from some absolute origin in the plane, and the edge vectors
specify the offsets of the frame’s corners from its origin. If the edges are
perpendicular, the frame will be rectangular. Otherwise the frame will be a
more general parallelogram.

Figure 2.15 shows a frame and its associated vectors.
In accordance with data abstraction, we need not be specific yet about how
frames are represented, other than to say that there is a constructor
make-frame, which takes three vectors and produces a frame, and three
corresponding selectors origin-frame, edge1-frame, and
edge2-frame (see Exercise 2.47).

SVG

Figure 2.15: A frame is described by three vectors — an origin and two edges.

We will use coordinates in the unit square (
0

x
,
y

1
) to specify
images. With each frame, we associate a
frame coordinate map, which
will be used to shift and scale images to fit the frame. The map transforms
the unit square into the frame by mapping the vector v

=
(
x
,
y
) to
the vector sum
Origin(Frame)

+

x

Edge
1

(Frame)

+

y

Edge
2

(Frame)
.
For example, (0, 0) is mapped to the origin of the frame, (1, 1) to the vertex
diagonally opposite the origin, and (0.5, 0.5) to the center of the frame. We
can create a frame’s coordinate map with the following
procedure:

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

Observe that applying frame-coord-map to a frame returns a procedure
that, given a vector, returns a vector. If the argument vector is in the unit
square, the result vector will be in the frame. For example,

((frame-coord-map a-frame) (make-vect 0 0))

returns the same vector as

(origin-frame a-frame)

练习 2.45:right-split 和 up-split 可以视为某种通用分割操作的实例。定义一个过程 split,使得对

(define right-split (split beside below))
(define up-split (split below beside))

求值后,产生的过程 right-split 和 up-split 具有与已定义的相同行为。

框架

在展示如何实现画家及其组合方法之前,我们必须先考虑框架 (frames)。一个框架可以用三个向量来描述——一个原点向量和两个边向量。原点向量指定框架原点相对于平面中某绝对原点的偏移,两个边向量指定框架各角顶点相对于其原点的偏移。如果两条边相互垂直,则该框架为矩形;否则为更一般的平行四边形。

图 2.15 展示了一个框架及其相关向量。依据数据抽象的原则,我们暂时不必明确框架的具体表示方式,只需知道存在一个构造函数 make-frame,它接受三个向量并产生一个框架,以及三个对应的选择函数 origin-frame、edge1-frame 和 edge2-frame(见练习 2.47)。

SVG

图 2.15:框架由三个向量描述——一个原点和两条边。

我们将用单位正方形(0 ≤ x, y ≤ 1)中的坐标来指定图像。对于每个框架,我们关联一个框架坐标映射 (frame coordinate map),用于将图像平移和缩放以适合框架。该映射通过将向量 v = (x, y) 映射到向量和

Origin(Frame) + x · Edge_1(Frame) + y · Edge_2(Frame)

来将单位正方形变换为框架。例如,(0, 0) 映射到框架原点,(1, 1) 映射到与原点对角相对的顶点,(0.5, 0.5) 映射到框架中心。我们可以用下列过程创建框架的坐标映射:

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

注意,将 frame-coord-map 应用于一个框架会返回一个过程,该过程接受一个向量并返回一个向量。若参数向量位于单位正方形内,则结果向量将位于该框架内。例如,

((frame-coord-map a-frame) (make-vect 0 0))

返回与

(origin-frame a-frame)

相同的向量。

Racket #lang sicp
(define right-split (split beside below))
(define up-split (split below beside))
Racket #lang sicp
(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))))))
Racket #lang sicp
((frame-coord-map a-frame) (make-vect 0 0))
Racket #lang sicp
(origin-frame a-frame)