# 2.2.4示例：图片语言

> 译者： [https://sicp.comp.nus.edu.sg/chapters/33](https://sicp.comp.nus.edu.sg/chapters/33)

本节介绍一种用于绘制图片的简单语言，该语言说明了数据抽象和闭合的功能，并且以一种必不可少的方式利用了高阶函数。 设计该语言的目的是使其易于尝试使用诸如 [2.9](33#fig_2.9) 之类的模式，这些模式由移位和缩放的重复元素组成。 [[1]](33#footnote-1) 用这种语言，要组合的数据对象表示为函数而不是列表结构。 就像满足闭合属性的`pair`允许我们轻松构建任意复杂的列表结构一样，使用这种语言也满足闭合​​属性的操作允许我们轻松构建任意复杂的模式。

<figure>![](img_javascript/cross-carpet.png) ![](img_javascript/heart-carpet.png)**[图2.9](33#fig_2.9)** 使用图片语言生成的设计。</figure>

## 图片语言

当我们在 [1.1](2) 部分中开始编程研究时，我们强调了通过着重于语言的原语，其组合方式及其抽象方式来描述语言的重要性。 我们将在此遵循该框架。

这种图片语言的部分优雅之处在于，只有一种元素称为_绘画工具_。 画家绘制的图像经过移位和缩放以适合指定的平行四边形形状的框架。 例如，有一个称为“ HTG0”的原始绘画手，它会产生一个心形，如图 [2.10](33#fig_2.10) 所示。

<figure>![](img_javascript/heart_shapes.png)**[图2.10](33#fig_2.10)** `heart`画家针对四个不同的帧生成的图像。 用细线显示的帧不是图像的一部分。</figure>

图纸的实际形状取决于框架-图中的所有四个图像 <ref name="fig:wave">[2.10](33#fig_2.10)</ref> 是由同一位`heart`画家制作的，但是 关于四个不同的框架。 <split>在下文中，我们将使用函数`show`在默认帧中显示画家。

```js
show(heart);
```</split> 

为了合并图像，我们使用各种操作从给定的画家构造新的画家。 例如，`beside`操作需要两名画家，并产生一个新的复合画家，该画家在框架的左半部分绘制第一幅画家的图像，在框架的右半部分绘制第二幅画家的图像。 同样，`stack`吸收了两名画家，并产生了一个复合画家，该画家将第一位画家的图像绘制在第二位画家的图像下方。 一些操作会转换单个画家以产生新画家。 例如，`flip_vert`拿一个画家并制作一个画家，将其图像上下颠倒，`flip_horiz`制作一个画家，将原始画家的图像左右颠倒。

 <split><figure>![](img_javascript/heart2.png) ![](img_javascript/heart4.png)**[Figure 2.11](33#fig_2.11)** Creating a complex figure, starting from the `heart` painter of Figure <ref name="fig:wave">[2.10](33#fig_2.10)</ref>.</figure></split> 

图 [2.11](33#fig_2.11) 显示了一个名为`heart4`的画家的绘画，该画家从`heart`开始分两个阶段进行构建：

```js
const heart2 = beside(heart, flip_vert(heart)); // (a)
```

```js
const heart4 = stack(heart2, heart2);           // (b)
```

在以这种方式构建复杂的图像时，我们正在利用这样一种事实，即画家在语言的组合方式下是封闭的。 两个画家的`beside`或`stack`本身就是画家； 因此，我们可以将其用作制作更复杂的画家的元素。 与使用`pair`建立列表结构一样，在组合方式下关闭数据对于仅使用少量操作即可创建复杂结构的能力至关重要。

一旦可以合并画家，我们希望能够抽象出合并画家的典型模式。 我们将画家操作实现为JavaScript函数。 这意味着我们在图片语言中不需要特殊的抽象机制：由于组合的方式是普通的JavaScript函数，因此我们可以自动执行可以使用函数进行的绘画操作。 例如，我们可以将`wave4`中的模式抽象为

```js
function flipped_pairs(painter) {
    const painter2 = beside(painter, flip_vert(painter));
    return stack(painter2, painter2);
}
```

and define `heart4` as an instance of this pattern:

```js
const heart4 = flipped_pairs(heart);
```

<figure>![](img_original/ch2-Z-G-37.svg)**[图2.12](33#fig_2.12)** （a）`right_split(n)`和（b）`corner_split(n)`的递归计划。</figure>

我们还可以定义递归操作。 如图所示，使画家分裂并向右分支的图例 <ref name="fig:split-plans">[2.12](33#fig_2.12)</ref> ， <ref name="fig:split-plans-2">[2.13](33#fig_2.13)</ref> 和 <ref name="fig:split-plans-3">[2.14](33#fig_2.14)</ref> ：

```js
function right_split(painter, n) {
    if (n === 0) {
        return painter;
    } else {
        const smaller = right_split(painter, n - 1);
        return beside(painter, stack(smaller, smaller));
    }
}
```

我们可以通过向上和向右分支来产生平衡的模式（请参阅练习 [2.44](33#ex_2.44)[2.12](33#fig_2.12) ）。

```js
function corner_split(painter, n) {
    if (n === 0) {
       return painter;
    } else {
       const up = up_split(painter, n - 1);
       const right = right_split(painter, n - 1);
       const top_left = beside(up, up);
       const bottom_right = stack(right, right);
       const corner = corner_split(painter, n - 1);
       return stack(beside(top_left, corner),
                    beside(painter, bottom_right));
    }
}
```

```js
show(right_split(heart, 4));
```

<figure>![](img_javascript/right_split_heart.png)**[Figure 2.13](33#fig_2.13)** The recursive operation `right_split` applied to the painter `heart`.</figure>

```js
show(corner_split(heart, 4));
```

<figure>![](img_javascript/corner_split_heart.png)**[Figure 2.14](33#fig_2.14)** The recursive operation `corner_split` applied to the painter `heart`. Combining four `corner_split` figures produces symmetric `square_limit` as shown in figure <ref name="fig:sqlimit-designs">[2.9](33#fig_2.9)</ref>.</figure>

通过适当放置四张`corner_split`的副本，我们得到一种称为`square_limit`的图案，其对两个画家的应用如图 [2.9](33#fig_2.9) 所示：

```js
function square_limit(painter, n) {
    const quarter = corner_split(painter, n);
    const upper_half = beside(flip_horiz(quarter), quarter);
    const lower_half = beside(turn_upside_down(quarter),
                              flip_vert(quarter));
    return stack(upper_half, lower_half);
}
```

<exercise>Define the function `up_split` used by `corner_split`. It is similar to `right_split`, except that it switches the roles of `stack` and `beside`.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_1_div">Solution</button> <solution>```js
function up_split(painter, n) {
    if (n === 0) {
        return painter;
    } else {
        const smaller = up_split(painter, n - 1);
        return stack(beside(smaller, smaller), painter);
    }
}
```</solution></exercise> 

## 高阶运算

除了抽象合并画家的模式之外，我们还可以在更高层次上进行抽象合并画家操作的模式。 也就是说，我们可以将绘画操作视为要操作的元素，并且可以为这些元素编写组合方式，这些功能将绘画操作作为参数并创建新的绘画操作。

例如，`flipped_pairs`和`square_limit`分别以正方形图案排列画家图像的四份副本； 它们的区别仅在于副本的方向。 提取此画家组合模式的一种方法是使用以下函数，该函数采用四个单参数画家操作，并生成画家操作，该操作将这四个画家转换为给定画家并将结果排列成正方形。 函数`tl`，`tr`，`bl`和`br`是分别应用于左上副本，右上副本，左下副本和右下副本的转换。

```js
function square_of_four(tl, tr, bl, br) {
    return painter => stack(beside(tl(painter), tr(painter)),
                            beside(bl(painter), br(painter)));
}
```

Then `flipped_pairs` can be defined in terms of `square_of_four` as follows:[[2]](33#footnote-2)

```js
function flipped_pairs(painter) {
    const combine4 = square_of_four(turn_upside_down, flip_vert, 
                                    flip_horiz, identity);
    return combine4(painter);
}
```

and `square_limit` can be expressed as[[3]](33#footnote-3)

```js
function square_limit(painter, n) {
    const combine4 = square_of_four(flip_horiz, identity, 
                                    turn_upside_down, flip_vert);
    return combine4(corner_split(painter, n));
}
```

<exercise>The functions `right_split` and `up_split` can be expressed as instances of a general splitting operation. Define a function `split` with the property that evaluating

```js
const right_split = split(beside, below);
const up_split = split(below, beside);
```

produces functions `right_split` and `up_split` with the same behaviors as the ones already defined.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_2_div">Solution</button> <solution>```js
function split(identity_op, smaller_op) {
    function rec_split(painter, n) {
        if (n===0) {
            return painter;
        } else {
            const smaller = rec_split(painter, n - 1);
            return identity_op(painter, 
                        smaller_op(smaller, smaller));
        }
    }
    return rec_split;
}
```

```js
const right_split = split(beside, stack);

show(right_split(heart, 4));
```</solution></exercise> 

## 镜框

在展示如何实现画家及其组合方式之前，我们必须首先考虑框架。 一个帧可以用三个向量描述：一个原始向量和两个边缘向量。 原点向量指定框架原点相对于平面中某个绝对原点的偏移量，边缘向量指定框架角点相对于原点的偏移量。 如果边缘垂直，则框架将为矩形。 否则，框架将是更一般的平行四边形。

图 [2.15](33#fig_2.15)[2.47](33#ex_2.47) ）。

<figure>![](img_original/ch2-Z-G-42.svg)**[图2.15](33#fig_2.15)** 帧由三个向量（原点和两个边缘）描述。</figure>

我们将使用单位正方形（![0%5Cleq%20x%2C%20y%5Cleq%201](img/fe65775e8f7b57cbe5f7dca84ef7e73e.jpg)）中的坐标来指定图像。 对于每一帧，我们都关联一个_帧坐标图_，它将用于平移和缩放图像以适合该帧。 该映射通过将向量![%5Cmathbf%7Bv%7D%3D%28x%2C%20y%29](img/164f1f27acb1a01df95977c8376db442.jpg)映射到向量和![%0A%20%20%20%20%20%20%20%20%5Ctext%7BOrigin%28Frame%29%7D%20%2B%20x%5Ccdot%20%5Ctext%7B%20Edge%7D_1%5Ctext%7B%20%28Frame%29%7D%0A%20%20%20%20%20%20%20%20%2B%20y%5Ccdot%20%5Ctext%7B%20Edge%7D_2%5Ctext%7B%20%28Frame%29%7D%0A%20%20%20%20%20%20%20%20](img/d92c0b17162b123d9c50f478c0846994.jpg)来将单位平方变换为帧。例如，![%280%2C%200%29](img/00bb3a09a511fd3d09969c58b048843f.jpg)映射到帧的原点，![%281%2C%201%29](img/b7edf244be14e1f9a15a530a08fa796b.jpg)映射到与原点对角的顶点，并且 ![%280.5%2C%200.5%29](img/5cef52ae68b2cb72507e805124e641e6.jpg)移至画面中央。 我们可以使用以下功能创建框架的坐标图： [[4]](33#footnote-4)

```js
function frame_coord_map(frame) {
    return 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`应用于帧会返回一个函数，给定一个向量，该函数会返回一个向量。 如果参数向量在单位平方中，则结果向量将在帧中。 例如，

```js
frame_coord_map(a_frame)(make_vect(0, 0));
```

returns the same vector as

```js
origin_frame(a_frame);
```

<exercise>A two-dimensional vector ![v](img/a7b0439b75ff3fa542a86700c6afc69a.jpg) running from the origin to a point can be represented as a pair consisting of an ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)-coordinate and a ![y](img/c592009395c2de830215c39f7bb6f97b.jpg)-coordinate. Implement a data abstraction for vectors by giving a constructor `make_vect` and corresponding selectors `xcor_vect` and `ycor_vect`. In terms of your selectors and constructor, implement functions `add_vect`, `sub_vect`, and `scale_vect` that perform the operations vector addition, vector subtraction, and multiplying a vector by a scalar: \begin{eqnarray*} (x_1, y_1)+(x_2, y_2) &=&(x_1+x_2, y_1+y_2)\\ (x_1, y_1)-(x_2, y_2)&=& (x_1-x_2, y_1-y_2)\\ s\cdot(x, y)&=&(sx, sy) \end{eqnarray*}<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_3_div">Solution</button> <solution>```js
function make_vect(x, y) {
    return pair(x, y);
}
function xcor_vect(vector) {
    return head(vector);
}
function ycor_vect(vector) {
    return tail(vector);
}
function scale_vect(factor, vector) {
    return make_vect(factor * xcor_vect(vector), 
                     factor * ycor_vect(vector));
}
function add_vect(vector1, vector2) {
    return make_vect(xcor_vect(vector1)  
                     + xcor_vect(vector2), 
                     ycor_vect(vector1)  
                     + ycor_vect(vector2));
}
function sub_vect(vector1, vector2) {
    return make_vect(xcor_vect(vector1)  
                     - xcor_vect(vector2), 
                     ycor_vect(vector1)  
                     - ycor_vect(vector2));
}
```</solution></exercise> <exercise>Here are two possible constructors for frames:

```js
function make_frame(origin, edge1, edge2) {
    return list(origin, edge1, edge2);
}

function make_frame(origin, edge1, edge2) {
    return pair(origin, pair(edge1, edge2));
}
```

For each constructor supply the appropriate selectors to produce an implementation for frames.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_4_div">Solution</button> <solution>1.  ```js
    function make_frame(origin, edge1, edge2) {
        return list(origin, edge1, edge2);
    }
    function origin_frame(frame) {
        return list_ref(frame, 0);
    }
    function edge1_frame(frame) {
        return list_ref(frame, 1);
    }
    function edge2_frame(frame) {
        return list_ref(frame, 2);
    }
    ```

2.  ```js
    function make_frame(origin, edge1, edge2) {
        return pair(origin, pair(edge1, edge2));
    }
    function origin_frame(frame) {
        return head(frame);
    }
    function edge1_frame(frame) {
        return head(tail(frame));
    }
    function edge2_frame(frame) {
        return tail(tail(frame));
    }
    ```</solution></exercise> 

## 画家

画家被表示为一个函数，该函数在给定框架作为参数的情况下绘制已移动并缩放以适合该框架的特定图像。 就是说，如果`p`是一个画家，`f`是一幅画框，那么我们就可以通过以`f`作为参数调用`p`在`f`中产生`p`的图像。

如何实现原始画家的细节取决于图形系统的特定特征和要绘制的图像类型。 例如，假设我们有一个函数`draw_line`，它在屏幕上的两个指定点之间画一条线。 然后我们可以从线段列表中创建线图的画家，例如图中的`wave`画家 [2.10](33#fig_2.10) ，如下所示： [[5]](33#footnote-5)

```js
function segments_to_painter(segment_list) {
    return frame => 
               for_each(segment => 
                            draw_line(frame_coord_map(frame)
                                      (start_segment(segment)), 
                                      frame_coord_map(frame)
                                      (end_segment(segment))), 
                        segment_list);
}
```

使用相对于单位平方的坐标来给出分段。 对于列表中的每个线段，绘画者使用帧坐标图对线段端点进行转换，并在转换后的点之间绘制一条线。

将画家表现为功能会在图片语言中建立强大的抽象障碍。 我们可以基于各种图形功能来创建和混合各种原始画家。 它们的实现细节并不重要。 任何函数都可以充当绘画者，只要它以框架为参数并绘制缩放比例以适合该框架即可。 [[6]](33#footnote-6)

<exercise>A directed line segment in the plane can be represented as a pair of vectors—the vector running from the origin to the start-point of the segment, and the vector running from the origin to the end-point of the segment. Use your vector representation from exercise <ref name="ex:vectors">[2.46](33#ex_2.46)</ref> to define a representation for segments with a constructor `make_segment` and selectors `start_segment` and `end_segment`.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_5_div">Solution</button> <solution>```js
function make_segment(v_start, v_end) {
    return pair(v_start, v_end);
}
function start_segment(v) {
    return head(v);
}
function end_segment(v) {
    return tail(v);
}
```</solution></exercise> <exercise>Use `segments_to_painter` to define the following primitive painters:

1.  绘制指定框架轮廓的画家。
2.  画家通过连接框架的相对角来绘制 &lt;quote&gt;X&lt;/quote&gt; 。
3.  通过连接框架侧面的中点绘制菱形的画家。

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_6_div">Solution</button> <solution>1.  绘制指定框架轮廓的画家。

    ```js
    const outline_start_1 = make_vect(0.0, 0.0);
    const outline_end_1 = make_vect(1.0, 0.0);
    const outline_segment_1 = make_segment(outline_start_1, 
                                           outline_end_1);
    const outline_start_2 = make_vect(1.0, 0.0);
    const outline_end_2 = make_vect(1.0, 1.0);
    const outline_segment_2 = make_segment(outline_start_2, 
                                           outline_end_2);
    const outline_start_3 = make_vect(1.0, 1.0);
    const outline_end_3 = make_vect(0.0, 1.0);
    const outline_segment_3 = make_segment(outline_start_3, 
                                           outline_end_3);
    const outline_start_4 = make_vect(0.0, 1.0);
    const outline_end_4 = make_vect(0.0, 0.0);
    const outline_segment_4 = make_segment(outline_start_4, 
                                           outline_end_4);
    const outline_painter = segments_to_painter(
                                  list(outline_segment_1, 
                                       outline_segment_2,
    				   outline_segment_3,
    				   outline_segment_4));
    ```

2.  画家通过连接框架的相对角来绘制 &lt;quote&gt;X&lt;/quote&gt; 。

    ```js
    const x_start_1 = make_vect(0.0, 0.0);
    const x_end_1 = make_vect(1.0, 1.0);
    const x_segment_1 = make_segment(x_start_1, 
                                     x_end_1);
    const x_start_2 = make_vect(1.0, 0.0);
    const x_end_2 = make_vect(0.0, 1.0);
    const x_segment_2 = make_segment(x_start_2, 
                                     x_end_2);
    const x_painter = segments_to_painter(
                                  list(x_segment_1, 
                                       x_segment_2));
    ```

3.  通过连接框架侧面的中点绘制菱形的画家。

    ```js
    const diamond_start_1 = make_vect(0.5, 0.0);
    const diamond_end_1 = make_vect(1.0, 0.5);
    const diamond_segment_1 = make_segment(diamond_start_1, 
                                           diamond_end_1);
    const diamond_start_2 = make_vect(1.0, 0.5);
    const diamond_end_2 = make_vect(0.5, 1.0);
    const diamond_segment_2 = make_segment(diamond_start_2, 
                                           diamond_end_2);
    const diamond_start_3 = make_vect(0.5, 1.0);
    const diamond_end_3 = make_vect(0.0, 0.5);
    const diamond_segment_3 = make_segment(diamond_start_3, 
                                           diamond_end_3);
    const diamond_start_4 = make_vect(0.0, 0.5);
    const diamond_end_4 = make_vect(0.5, 0.0);
    const diamond_segment_4 = make_segment(diamond_start_4, 
                                           diamond_end_4);
    const diamond_painter = segments_to_painter(
                                  list(diamond_segment_1, 
                                       diamond_segment_2,
    				   diamond_segment_3,
    				   diamond_segment_4));
    ```</solution></exercise> 

## 改造和合并画家

对绘画者的操作（例如`flip_vert`或`beside`）通过创建一个绘画者来工作，该绘画者针对从自变量框架派生的框架调用原始绘画者。 因此，例如，`flip_vert`不必知道画家如何翻转它—它只需要知道如何将框架上下颠倒即可：翻转的画家仅使用原始画家，但是在翻转时 帧。

Painter操作基于`transform_painter`函数，该函数将画家和有关如何变换框架并产生新画家的信息作为参数。 转换的画家在框架上调用时，将转换框架并在转换的框架上调用原始画家。 `transform_painter`的参数是指定新框架角的点（表示为矢量）： [[7]](33#footnote-7) 映射到框架中时，第一个点指定新框架的原点，另外两个指定 边缘向量的末端。 因此，单位平方内的自变量指定原始帧内包含的帧。

```js
function transform_painter(painter, origin,
                           corner1, corner2) {
    return frame => {
             const m = frame_coord_map(frame);
             const new_origin = m(origin);
             return painter(make_frame(
                              new_origin, 
                              sub_vect(m(corner1), 
                                       new_origin), 
                              sub_vect(m(corner2), 
                                       new_origin)));
           };
}
```

Here's how to flip painter images vertically:

```js
function flip_vert(painter) {
    return transform_painter(painter, 
                make_vect(0.0, 1.0),  // new origin
                make_vect(1.0, 1.0),  // new end of edge1
                make_vect(0.0, 0.0)); // new end of edge2
}
```

使用`transform_painter`，我们可以轻松定义新的转换。 例如，我们可以定义一个画家，将其图像缩小到给定帧的右上四分之一：

```js
function shrink_to_upper_right(painter) {
    return transform_painter(painter, 
                             make_vect(0.5, 0.5), 
                             make_vect(1.0, 0.5), 
                             make_vect(0.5, 1.0));
}
```

其他变换将图像逆时针旋转90度 [[8]](33#footnote-8)

```js
function rotate90(painter) {
    return transform_painter(painter, 
                             make_vect(1.0, 0.0), 
                             make_vect(1.0, 1.0), 
                             make_vect(0.0, 0.0));
}
```

or squash images towards the center of the frame:[[9]](33#footnote-9)

```js
function squash_inwards(painter) {
    return transform_painter(painter, 
                             make_vect(0.0, 0.0), 
                             make_vect(0.65, 0.35), 
                             make_vect(0.35, 0.65));
}
```

框架变换也是定义组合两个或更多画家的方法的关键。 例如，`beside`函数需要两个画家，将它们转换为分别在自变量框架的左半部分和右半部分绘画，然后生成一个新的复合画家。 当给复合画家一个框架时，它调用第一个变换的画家在框架的左半部分绘画，并调用第二个变换的画家在框架的右半部分绘画：

```js
function beside(painter1, painter2) {
    const split_point = make_vect(0.5, 0.0);
    const paint_left = transform_painter(painter1, 
                            make_vect(0.0, 0.0), 
                            split_point, 
                            make_vect(0.0, 1.0));
    const paint_right = transform_painter(painter2, 
                            split_point, 
                            make_vect(1.0, 0.0), 
                            make_vect(0.5, 1.0));
    return frame => {
               paint_left(frame);
               paint_right(frame);
           };
}
```

观察绘画者数据的抽象，尤其是绘画者作为函数的表示，如何使`beside`易于实现。 `beside`功能不需要了解组件绘画者的详细信息，除了每个绘画者会在其指定的框架中画些东西。

<exercise>Define the transformation `flip_horiz`, which flips painters horizontally, and transformations that rotate painters counterclockwise by 180 degrees and 270 degrees.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_7_div">Solution</button> <solution>1.  转换`flip_horiz`：

    ```js
    function flip_horiz(painter) {
        return transform_painter(painter, 
                   make_vect(1.0, 0.0),  // new origin
                   make_vect(0.0, 0.0),  // new end of edge1
                   make_vect(1.0, 1.0)); // new end of edge2
    }
    ```

2.  转换`rotate180`：

    ```js
    function rotate180(painter) {
        return transform_painter(
                   painter, 
                   make_vect(1.0, 1.0),  // new origin
                   make_vect(0.0, 1.0),  // new end of edge1
                   make_vect(1.0, 0.0)); // new end of edge2
    }
    ```

3.  转换`rotate270`：

    ```js
    function rotate270(painter) {
        return transform_painter(
                   painter, 
                   make_vect(0.0, 1.0),  // new origin
                   make_vect(0.0, 0.0),  // new end of edge1
                   make_vect(1.0, 0.0)); // new end of edge2
    }
    ```</solution></exercise> <exercise>Define the `stack` operation for painters. The function `stack` takes two painters as arguments. The resulting painter, given a frame, draws with the first painter in the bottom of the frame and with the second painter in the top. Define `stack` in two different ways—first by writing a function that is analogous to the `beside` function given above, and again in terms of `beside` and suitable rotation operations (from exercise <ref name="ex:rotate">[2.50](33#ex_2.50)</ref>).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_8_div">Solution</button> <solution>1.  首先是直接方法：

    ```js
    function stack(painter1, painter2) {
        const split_point = make_vect(0.0, 0.5);
        const paint_upper =
            transform_painter(painter1, 
                              split_point, 
                              make_vect(1.0, 0.5), 
                              make_vect(0.0, 1.0));
        const paint_lower =
            transform_painter(painter2, 
                              make_vect(0.0, 0.0), 
                              make_vect(1.0, 0.0), 
                              split_point);
        return frame =&gt; {
                   paint_upper(frame);
                   paint_lower(frame);
               };
    }
    ```

2.  现在带有旋转和`beside`的版本：

    ```js
    function stack(painter1, painter2) {
        return rotate270(beside(rotate90(painter1),
                                rotate90(painter2)));
    }
    ```</solution></exercise> 

## 语言水平，实现可靠的设计

图片语言体现了我们介绍的有关使用函数和数据进行抽象的一些关键思想。 基本数据抽象器Painter是使用功能表示形式实现的，这使该语言能够以统一的方式处理不同的基本绘图功能。 组合方式满足封闭特性，这使我们可以轻松构建复杂的设计。 最后，我们可以使用所有用于抽象功能的工具来抽象画家的组合方式。

我们还了解了有关语言和程序设计的另一个关键思想。 这是_分层设计_的方法，即将复杂系统构造为使用一系列语言描述的一系列级别的概念。 每个级别是通过组合在该级别被视为基本体的部分来构造的，而在每个级别上构建的部分都将在下一级别用作基本体。 在分层设计的每个级别使用的语言具有适合该级别细节的原语，组合方式和抽象方式。

分层设计遍布复杂系统的工程。 例如，在计算机工程中，电阻器和晶体管被组合在一起（并使用模拟电路的语言来描述），以产生诸如与门和或门之类的零件，这些零件形成了数字电路设计语言的原语。 [[10]](33#footnote-10) 这些部分组合在一起，以构建处理器，总线结构和内存系统，然后使用适合于计算机体系结构的语言将它们组合在一起以形成计算机。 使用适合于描述网络互连的语言，将计算机组合起来形成分布式系统。

作为分层的一个小例子，我们的图片语言使用原始元素（原始绘画者），这些元素指定点和线以提供类似`heart`的绘画者的形状。 我们对图片语言的描述主要集中在使用`beside`和`stack`等几何组合器组合这些图元上。 我们还在更高层次上进行了工作，将`beside`和`stack`作为要用一种语言操作的原语，该语言的操作（例如`square_of_four`）捕获了组合几何组合器的常见模式。

分层设计有助于使程序_健壮_，也就是说，规范中的细微更改可能需要对程序进行相应的细微更改。 例如，假设我们要基于图 [2.9](33#fig_2.9) 中显示的`heart`更改图像。 我们可以在最低级别进行更改，以更改`heart`元素的详细外观； 我们可以在中层工作，以更改`corner_split`复制`wave`的方式； 我们可以在最高级别上进行更改，以更改`square_limit`如何排列四个角点。 通常，分层设计的每个级别都提供了不同的词汇来表达系统的特征，并提供了不同的更改能力。

<exercise>Make changes to the square limit of `heart` shown in Figure <ref name="fig:sqlimit-designs">[2.9](33#fig_2.9)</ref> by working at each of the levels described above. In particular:

1.  更改`corner_split`构建的图案（例如，仅使用`up_split`和`right_split`图像的一个副本，而不是两个）。
2.  修改使用`square_of_four`的`square_limit`的版本，以便以不同的样式组装边角。

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_33_9_div">Solution</button> <solution>```js
// Click here to play with any abstraction
// used for square_limit
```</solution></exercise> 

* * *

[[1]](33#footnote-link-1) The picture language is based on the language Peter Henderson created to construct images like M.C. Escher's <quote>Square Limit</quote> woodcut (see <citation>Henderson 1982</citation>). The woodcut incorporates a repeated scaled pattern, similar to the arrangements drawn using the `square_limit` function in this section.

[[2]](33#footnote-link-2) Equivalently, we could write

```js
const flipped_pairs =
    square_of_four(turn_upside_down, flip_vert, 
                   flip_horiz, identity);
```

 [[3]](33#footnote-link-3) The function `turn_upside_down` rotates a painter by 180 degrees. Instead of `turn_upside_down` we could say `compose(flip_vert, flip_horiz)`, using the `compose` function from exercise <ref name="ex:compose">[1.42](22#ex_1.42)</ref>.

[[4]](33#footnote-link-4) The function `frame_coord_map` uses the vector operations described in exercise <ref name="ex:vectors">[2.46](33#ex_2.46)</ref> below, which we assume have been implemented using some representation for vectors. Because of data abstraction, it doesn't matter what this vector representation is, so long as the vector operations behave correctly.

[[5]](33#footnote-link-5) The function `segments_to_painter` uses the representation for line segments described in exercise <ref name="ex:segments2">[2.48](33#ex_2.48)</ref> below. It also uses the `for_each` function described in exercise <ref name="ex:for-each">[2.23](30#ex_2.23)</ref>.

[[6]](33#footnote-link-6) <split>For example, the `heart` painter of figure <ref name="fig:wave">[2.10](33#fig_2.10)</ref> was constructed from a gray-level image. For each point in a given frame, the `rogers` painter determines the point in the image that is mapped to it under the frame coordinate map, and shades it accordingly.</split> By allowing different types of painters, we are capitalizing on the abstract data idea discussed in section <ref name="sec:data?">[2.1.3](27)</ref>, where we argued that a rational-number representation could be anything at all that satisfies an appropriate condition. Here we're using the fact that a painter can be implemented in any way at all, so long as it draws something in the designated frame. Section <ref name="sec:data?">[2.1.3](27)</ref> also showed how pairs could be implemented as functions. Painters are our second example of a functional representation for data.

[[7]](33#footnote-link-7) In `transform_painter`, we make use of a slight extension of the syntax of function definition expressions, compared to section <ref name="sec:lambda">[1.3.2](20)</ref>: The body of a function definition can be a block, not just a single return expression. Such function definition expressions have the following shape: ![%20%5Ctexttt%7B%28%7D%20%5C%20%5Ctextit%7Bparameters%7D%5C%20%5Ctexttt%7B%29%20%3D%3E%20%5C%7B%7D%5C%20%5Ctextit%7Bstatements%7D%5C%20%5Ctexttt%7B%5C%7D%7D%20](img/08b849e9464e89ddd704cd81db87fcbb.jpg)

 [[8]](33#footnote-link-8) The function `rotate90` is a pure rotation only for square frames, because it also stretches and shrinks the image to fit into the rotated frame.

[[9]](33#footnote-link-9) <split>The diamond-shaped images in figures <ref name="fig:wave">[2.10](33#fig_2.10)</ref> were created with `squash_inwards` applied to `heart`.</split>

[[10]](33#footnote-link-10) Section <ref name="sec:circuit-simulator">[3.3.4](61)</ref> describes one such language.

