Prologue: How to Program 前奏: 怎样编程

好的编程不仅仅是学习一门语言的机制。**最重要的是,要记住,程序员创建程序是为了让其他人将来阅读。**一个好的程序反映问题陈述及其重要概念。它有一个简洁的自我描述。简而言之,好的编程就是系统地解决问题,并在代码中传达系统。最重要的是,这种编程方法实际上使每个人都能访问编程—因此它可以共享的(使每个人都看得懂)

这门程序课经审核与K2的高中生又适合于大学一年级的新生。

对于大一新生,前奏:怎样编程(Prologue)我们建议通过create-rocket-scene进入Big-bang程序向学生展示动画是如何工作的。我们将“Big-bang”解释为底层操作系统的一小部分,即负责时钟滴答、击键和其他事情的部分。

一旦涵盖了“Big-bang”,我们将继续以同样的非正式风格讨论基本主题:

数字、布尔值、字符串和图像(运算 Arithmetic);

间隔和枚举(间隔、枚举和分项 (Intervals, Enumerations, and
Itemizations);

和结构类型(添加结构 Structure)

对于K-12高中生,在此就不再一一叙述了

在定义区 definitions area(上部)(+ 1
1),注意符号与数字之间必须有空格,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tv2jPNpB-1618589248396)(media/fd61b4f9c80da7a6fa6029d489cad95a.png)]

程序由表达式组成。你们见过数学中的表达式。现在,表达式要么是一个普通的数字,要么以左括号“(”开始,以匹配的右括号“)”结束

程序运行的结果在 交互区显示 interactions area.(下部)

注:这本书不会教你Racket,即使编辑器叫DrRacket。请参阅序言,特别是关于DrRacket和教学语言的章节,了解选择发展我们自己的语言的细节。

运算和计算

字符串
string,首先需要知道的是,在BSL中,文本是用双引号(")括起来的任意键盘字符序列。我们称它为字符串。因此,“hello
world”是一个完美的字符串。

(string-length “hello world”) 字符串长度

> (string->number “hello world”)
#false

这个既不是数字也不是字符串,他是一个Boolean(布尔值)

Boolean(布尔值)在逻辑中,真值或逻辑值是指示一个陈述在什么程度上是真的。在计算机编程上多称作布尔值。布尔值是“
True 或**“”** False 中的一个。动作脚本也会在适当时将值 True 和 False
转换为 1 和
0。布尔值经常与动作脚本语句中通过比较控制脚本流的逻辑运算符一起使用。

使用
string->number把字符串->数字,但括号内的不是数字,结果:#false

很简单: 如果x和y为真,**(and x
y)为真(#true);如果x或y,或两者都为真,则(or x
y)为真(#true);当x为#false时,(not x)**会精确地为 #true

也可以把数字转化为布尔值

插入图像

DrRacket会对图片进行响应。与许多其他编程语言相比,BSL能够理解图像,并且它支持图像的运算,就像它支持数字或字符串的运算一样。简而言之,您的程序可以使用图像进行计算,您可以在交互区域进行计算。此外,像其他编程语言的程序员一样,BSL程序员创建其他人可能会觉得有用的库。使用这些库就像使用新单词扩展您的词汇表,或者使用新的原语扩展你的编程词汇表。我们称这种图书馆为教学包(
teachpacks),因为它们对教学很有帮助。

在此首先要在定义区输入
(require
2htdp/image)

overlay-覆盖

image-width-图像宽度

以上只是测量图像尺寸

还有两种操作很重要:
empty-scene
(空场景)和place-image放置图像。第一个创建了一个场景,一个特殊的矩形。第二种是将图像放入这样的场景中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NxJevYbk-1618589248407)(media/b2fb688aac75e6dea69f30b088719de0.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AJDfzoQE-1618589248409)(media/f82de6aae690b6326e775686ad91f046.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wP7TNoT1-1618589248412)(media/7846585ac5380dfebd52d8abd934f228.png)]

输入和输出

我们考虑程序时,乍一看,函数就像表达式一样,总是在左边有一个y,后面跟着一个=符号和一个表达式。然而,它们不是表达。你在学校里经常看到的函数符号是完全误导人的。在DrRacket中,你写函数的方式有点不同:

(define
(y x)
(*
x x)) 定义y与x的关系为x*x即——y=x*x

定义说“把y看作一个函数”,它像表达式一样计算一个值。然而,一个函数的值取决于一个叫做输入的东西的值,我们用**(y
x)**来表示它,因为我们不知道这个输入是什么,所以我们用一个名字来表示输入。按照数学传统,我们用x代替未知输入;但很快,我们会用到各种各样的名字。

这第二部分意味着你必须提供一个数字——为x,以确定一个特定的y值。当你这样做时,DrRacket将x的值插入到与函数相关的表达式中。这里的表达式是(*
x
x),当x换成一个值,比如1,DrRacket就可以计算出表达式的结果,也叫做函数的输出。

单击RUN并观察没有任何事情发生。在交互区域没有显示任何东西。在DrRacket的其他地方似乎没有什么变化。就好像你什么都没完成似的。但是你做到了。你实际上定义了一个函数并告知DrRacket它的存在。事实上,后者现在已经可以使用该函数了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z4pYXayY-1618589248414)(media/477a978f6e49b84a648196df8527214d.png)]

首先:(define
(FunctionName InputName) BodyExpression)

(定义(函数名称 输入名称)表达式))

第二步:(FunctionName ArgumentExpression)

这是一个函数应用程序。第一部分告诉DrRacket你希望使用的函数。第二部分是要应用函数的输入。如果您正在阅读Windows或Mac手册,它可能会告诉您这个表达式“启动”名为FunctionName的“应用程序”,并且它将处理ArgumentExpression作为输入。与所有表达式一样,后者可能是一段普通的数据或一个深度嵌套的表达式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWwyM7ak-1618589248416)(media/0ec03784b78c9d4d71b4e88049130de2.png)]

N种计算方法

你可以用一个条件表达式cond在DrRacket中定义这个函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Smemg9Aa-1618589248417)(media/184f8bf46a903a28cca6557c1e82e2f4.png)]

将cond表达式用于v2火箭:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWG0duVY-1618589248418)(media/f7232e019fd455aa771b603e56589325.png)]

这可能还是不美观。可以通过找点的方法,设置参数找到对应的表达式:(这就很好看了)

(define (picture-of-rocket.v3 height)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5t6kH6Y-1618589248419)(media/ac47317de7d940543dbef6e0e7dbebb3.png)] (cond

[(<= height (- 60 (/ (image-height
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MQ2azckB-1618589248420)(media/c4e6bcc73e293831cc25731ea4708c9c.png)])
2)))

(place-image
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGQVWWIF-1618589248421)(media/c4e6bcc73e293831cc25731ea4708c9c.png)]
50 height

(empty-scene 100 60))]

[(> height (- 60 (/ (image-height
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rn2Ih0FA-1618589248422)(media/c4e6bcc73e293831cc25731ea4708c9c.png)])
2)))

(place-image
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGrfd6tv-1618589248424)(media/c4e6bcc73e293831cc25731ea4708c9c.png)]
50 (- 60 (/ (image-height
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6BEJqt22-1618589248425)(media/c4e6bcc73e293831cc25731ea4708c9c.png)])
2))

(empty-scene 100 60))]))

程序中很多的火箭,显得很乱,是否应该改正呢?看看后面的……

一种程序不同表达

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiGLvjy6-1618589248426)(media/d86b06594c06ff0ed8a959b2aaf4f008.png)]

这就好了许多。

在理想的程序中,一个小请求(比如更改画布的大小),应该需要同样小的更改方式。使用BSL定义,实现这种简单性的工具。除了定义函数之外,还可以引入常量定义,它们为常量指定一些名称。常数定义的一般形状很简单:(define
Name Expression)

因此,如果键入:(define
HEIGHT 60)

在你的程序中,你说高度(HIGHT)总是代表数字60。这样一个定义的意义是你所期望的。每当DrRacket在计算过程中遇到高度时,它就用60代替。

另外,注意上图的程序由四个定义组成:一个函数定义和三个常量定义。数字100和60只出现两次——一次作为宽度的值,一次作为高度的值。您可能还注意到,对于photo
-of- rocketl
.v4的函数参数,它使用h而不是height。严格来说,这个变化是没有必要的,因为DrRacket没有把高度和高度搞混,但是我们这样做是为了避免把你搞混。(还要注意大小写)

当DrRacket计算(animate
picture-of-rocket.v4)时,它将高度替换为60,宽度替换为100,每次遇到这些名称时,将火箭替换为图像。要体验真正程序员的乐趣,将高度旁边的60换成400,然后单击RUN。你可以在一个100
* 400的场景中看到火箭下降和降落。一个小小的改变就能改变一切。

用现代的说法,您刚刚经历了第一次程序重构。每当您重新组织您的程序以为将来可能的变更请求做准备时,您就重构您的程序。把它写进你的简历。这听起来不错,你未来的雇主可能会喜欢读这样的流行语,即使它不能让你成为一个好的程序员。然而,一个好的程序员永远不会接受的是,让一个程序包含三次相同的表达式:

(-
HEIGHT
(/
(image-height
ROCKET) 2))

每次你的朋友和同事读这个程序的时候,他们需要理解这个表达式计算的内容,也就是,画布顶部和停留在地面上的火箭中心点之间的距离。DrRacket每次计算表达式的值都要执行三个步骤:(1)确定图像的高度;(2)除以2;然后(3)用高度减去结果。每一次,它都得到相同的数字。

这一观察结果需要再引入一个定义:

(define
ROCKET-CENTER-TO-TOP

(-
HEIGHT
(/
(image-height
ROCKET) 2)))

现在用ROCKET- center - to - top替换程序其余部分中的表达式(- HEIGHT (/
(image-height ROCKET)
2)。您可能想知道这个定义应该放在高度定义的上面还是下面。更一般地,您应该想知道定义的顺序是否重要。**对于常数定义,顺序很重要;对于函数定义,它不需要。**一旦DrRacket遇到一个常量定义,它就会确定表达式的值,然后把这个名字和这个值联系起来。例如:

(define HEIGHT (* 2 CENTER))

(define CENTER 100)

当遇到高度的定义时,引起DrRacket抱怨说“中心在它的定义之前就用过了”。相比之下:

像预期的那样。首先是DrRacket会把CENTER与100联系起来。其次,它计算(* 2
CENTER),结果是200。最后,DrRacket把200和HEIGHT联系起来。

虽然常量定义的顺序很重要,但常量定义相对于函数定义的位置并不重要。实际上,如果您的程序包含许多函数定义,它们的顺序也不重要,尽管最好先介绍所有常量定义,然后按照重要性的递减顺序介绍函数的定义。当您开始编写自己的多定义程序时,您就会明白为什么这种顺序很重要。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hejRbcrK-1618589248427)(media/486da076164c5aa6cef507cd5b0f4d7d.png)]

神奇数字:再看看下图,因为我们消去了所有重复的表达式,除了一个数,其它的都从函数定义中消失了。在编程的世界里,这些数字被称为魔术数字,没有人喜欢它们。在你意识到这一点之前,你就忘记了数字所起的作用,以及什么变化是合法的。最好在定义中命名这些数字

这里我们实际上知道50是火箭的x坐标的选择。尽管50看起来不像一个表达式,但它实际上也是一个重复的表达式。因此,我们有两个理由从函数定义中删除50,我们把它留给您去做。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LloHE8CZ-1618589248428)(media/7b53452f0ac632c35109fc1335dc07c8.png)]

  • How would you change the program to create a 200-by-400 scene?

  • How would you change the program so that it depicts the landing of a green
    UFO (unidentified flying object)? Drawing the UFO is easy:

(overlay (circle 10 “solid” “green”)
(rectangle 40 4 “solid” “green”))
  • How would you change the program so that the background is always blue?

  • How would you change the program so that the rocket lands on a flat rock bed
    that is 10 pixels higher than the bottom of the scene? Don’t forget to
    change the scenery, too.

    又一个定义

    回想一下,animate实际上是将其函数应用于自第一次调用它以来经过的时钟滴滴涕的次数。也就是说,关于火箭图片的自变量不是高度,而是时间。我们以前对火箭图的定义使用了错误的名称作为函数的参数;h是height的缩写,而应该用t表示时间:

(define (picture-of-rocket t)
(cond
[(<= t ROCKET-CENTER-TO-TOP)
(place-image ROCKET 50 t MTSCN)]
[(> t ROCKET-CENTER-TO-TOP)
(place-image ROCKET
50 ROCKET-CENTER-TO-TOP MTSCN)]))

这个对定义的小改变立刻澄清了这个程序使用时间就好像它是一个距离。真是个坏主意。

即使你从未上过物理课,你也知道时间不是距离。所以我们的程序是偶然运行的。别担心,虽然;这一切都很容易解决。你所需要知道的只是一些深奥的科学,我们称之为物理学。

物理? !
?好吧,也许你已经忘记了在那门课上学到的东西。又或者你从来没有上过物理课,因为你太年轻或太温柔了。不用担心。这种情况经常发生在最好的程序员身上,因为他们需要帮助人们解决音乐、经济、摄影、护理和其他各种学科的问题。显然,即使是程序员也不是什么都知道。所以他们查找他们需要知道的东西。或者他们和合适的人交谈。如果你和物理学家交谈,你会发现,行走的距离与时间成正比

d=v×t

也就是说,如果一个物体的速度是v,那么这个物体在t秒内移动了d英里(米或像素)

当然,老师应该给你一个正确的函数定义:(这部分内容扩展很多,可以细读)

d(t)= v×t

因为这马上告诉大家d的计算依赖于t
v是一个常数。程序员则更进一步,使用有意义的名字来表示这些单字母缩写:

(define V 3)
(define (distance t)
(* V t))

这个程序片段由两个定义组成:一个函数距离,计算物体以恒定速度移动的距离,和一个常数V,描述速度。

你可能想知道为什么这里V是3。没有什么特别的原因。我们认为每个时钟3个像素是一个很好的速度。你也许并不会注意到这一点。使用这个数字,看看动画会发生什么。

现在我们可以再次修复picture-of-rocket了。函数可以使用(distance
t)来计算火箭下落的距离,而不是将t与高度进行比较。最后的程序如下图所示。它由两个函数定义组成:picture-of-rocket.v6和distance(距离)。剩余常数定义函数定义可读和可修改性。一如既往,你可以运行这个程序与animate(动画):>
(animate
picture-of-rocket.v6)

与以前版本的picture-of-rocket相比,这个版本显示了一个程序可以由几个相互引用的函数定义组成。而且,即使是第一个版本使用+和/
——只是你认为它们是内置在BSL。

当您成为一名真正的程序员时,您会发现程序由许多函数定义和许多常量定义组成。您还将看到函数之间一直相互引用。你真正需要练习的是组织它们,这样你就可以轻松地阅读它们,即使是在完成几个月之后。毕竟,老版本的你或其他人会想要对这些程序进行更改;如果您不能理解程序的组织,那么即使是最小的任务,您也会困难重重。否则,你基本上知道该知道的事情。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HarOxY8Y-1618589248430)(media/2dca7b717927ac12131e6e54fb9b39f7.png)]

您可能认为自己还没有掌握足够的知识来编写对击键、鼠标点击等作出反应的程序。事实证明,你有。除了animate函数之外,2htdp/universe库还提供了其他函数,这些函数将程序连接到计算机中的键盘、鼠标、时钟和其他移动部件上。事实上,它甚至支持编写程序,连接您的计算机与世界上任何其他人的计算机。所以这不是问题。

简而言之,您已经了解了将程序组合在一起的几乎所有机制。如果你了解了所有可用的函数,你就可以编写程序来玩有趣的电脑游戏,运行模拟,或者跟踪商业账户。问题是这是否真的意味着你是一个程序员。对吗?

I Fixed-Size Data

我们把“组织思想”等同于设计,这本书的第一部分向你介绍了设计程序的系统方法。

(快速浏览第一章,跳到第二章,当你遇到你不认识的“算术”时,再回到这里。)

本书的第一部分(I)介绍了BSL的运算,就是在序言中使用的编程语言。

Arithmetic 运算

提醒一下,这里有一个原始表达:我们用 == 表示“根据计算定律等于”

(+ 1 2)
==
3

本章的其余部分将介绍BSL的四种形式的原子数据:数字、字符串、图像和布尔值。

布尔值(Boolean):在逻辑中,真值或逻辑值是指示一个陈述在什么程度上是真的,即逻辑运算。在计算机编程上多称作布尔值。布尔值是“真”
True 或“假” False 中的一个。动作脚本也会在适当时将值 True 和 False 转换为 1
和 0。

我们在这里用“原子(atomic)”这个词来比喻物理学。您无法窥视原子数据的内部,但是您可以使用一些函数将多个原子数据合并到另一个原子数据中,检索这些原子数据的“属性”,等等。本章的部分将介绍其中的一些函数,也称为原语操作(primitive
operations
)或预定义操作(pre-defined
operations
)。你可以在DrRacket附带的BSL文档中找到其他的文件。下一卷《如何设计组件》将解释如何设计原子数据。

1.1 数字运算

大多数人听到“算术”时想到的是“数字”和“对数字的运算”。“数的运算”是指两个数相加,得到第三个数,减去另一个数,确定两个数的最大公约数,以及更多类似的事情。如果我们不按字面意思来计算,我们甚至可以包括角的正弦值,将实数四舍五入到最接近的整数,等等。

BSL语言支持数字及其运算。如在序言中所讨论的,运算操作如+是这样使用的:(+
3 4)也就是前缀表示法。下面是我们的语言提供的一些对数字的操作:
+,
-,
*,
/,
abs,
add1,
ceiling,
denominator,
exact->inexact,
expt,
floor,
gcd,
log,
max,
numerator,
quotient,
random,
remainder,
sqr,

tan.
我们通过字母表选择我们的方式只是为了展示操作的多样性。探索他们计算的东西,然后找出还有多少。

“数(Number)”一词是指各种各样的数字,包括计算数字、整数、有理数、实数,甚至复数。在大多数情况下,您可以放心地将Number与小学时的数轴等同起来,尽管这种翻译有时过于不精确。如果我们想要精确,我们可以使用适当的词:整数、有理数等等。我们甚至可以使用诸如正整数、非负整数、负整数等标准术语来改进这些概念。

Exercise 1. Add the following definitions for x and y to DrRacket’s
definitions area:

(define x 3)
(define y 4)

Now imagine that x and y are the coordinates of a Cartesian (笛卡儿坐标)point.
Write down an expression that computes the distance of this point to the origin,
that is, a point with the coordinates (0,0).

The expected result for these values is 5, but your expression should produce
the correct result even after you change these definitions.

Just in case you have not taken geometry courses or in case you forgot the
formula that you encountered there, the point (x,y) has the distance

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IiRfl2cW-1618589248431)(media/5ebe7bd2d18da4e5457aaf3df295520d.png)]

from the origin. After all, we are teaching you how to design programs, not how
to be a geometer.

To develop the desired expression, it is best to click RUN and to experiment
in the interactions area. The RUN action tells DrRacket what the current
values of x and y are so that you can experiment with expressions that involve x
and y:

> x
3
> y
4
> (+ x 10)
13
> (* x y)
12

Once you have the expression that produces the correct result, copy it from the
interactions area to the definitions area.

To confirm that the expression

answer

(define x 12)

(define y 5)

(sqrt (+ (* x x) (* y y)))

1.2 字符串运算 The Arithmetic of Strings

大多数编程语言至少提供一种处理这种符号信息的数据。现在,我们使用BSL的字符串。一般来说,字符串是可以在键盘上输入的字符序列,加上一些我们不关心的字符,这些字符用双引号括起来。在Prologue:
How to
Program,我们已经看到了一些BSL字符串:
“hello”, “world”,“blue”,
"red"和其他。前两个词可能会出现在对话或信件中;其他的是我们可能希望使用的颜色名称。

BSL包含一个专门使用和生成字符串的操作:string-append,正如我们在Prologue:
How to
Program中看到的,它将两个给定的字符串连接成一个字符串。可以将string-append看作类似于+的操作。后者消耗两个(或更多)数字并生成一个新数字,前者消耗两个或更多字符串并生成一个新字符串:

> (string-append "what a " “lovely " “day” " 4 BSL”)
“what a lovely day 4 BSL”

当将给定的数字加起来时,没有任何变化;当string-append将给定的字符串连接成一个大字符串时,没有任何变化。如果你想对这样的表达式求值,你只需要想想string-append的明显规律,类似于+:

Exercise 2. Add the following two lines to the definitions area:

(define prefix “hello”)
(define suffix “world”)

Then use string primitives to create an expression that concatenates prefix and
suffix and adds “_” between them. When you run this program, you will see
“hello_world” in the interactions area.

See
exercise 1
for how to create expressions using DrRacket.

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vBjLl66i-1618589248432)(media/d4dd7779f6ab441855414cce75135bd1.png)]

1.3 混合Mixing It Up

与字符串相关的所有其他操作(在BSL中)使用或生成字符串以外的数据。下面是一些例子:

string-length测量字符串的长度,空格算一个字符

string-ith 摘取字符串s和第i个字母,>(string-ith s
i),i是第n+1位,空格也算一个字符

number->string
使用一个数字并生成一个字符串。

substring
摘录字符串 >(substring s i
j)摘录字符串第i到j字符,位数从0开始,如果没有j,就从i到结束

Exercise 3. Add the following two lines to the definitions area:

(define str “helloworld”)
(define i 5)

Then create an expression using string primitives that adds “_” at position i.
In general this means the resulting string is longer than the original one; here
the expected result is “hello_world”.

Position means i characters from the left of the string, but programmers
start counting at 0. Thus, the 5th letter in this example is “w”, because the
0th letter is “h”. Hint When you encounter such “counting problems” you may
wish to add a string of digits below str to help with counting:

(define str “helloworld”)
(define ind “0123456789”)
(define i 5)

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d18fLJAG-1618589248433)(media/327d9ed5771277bed6ffb966b0c9db5c.png)]

Exercise 4. Use the same setup as in
exercise 3
to create an expression that deletes the ith position from str. Clearly this
expression creates a shorter string than the given one. Which values for i are
legitimate?

Answer:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IKTUjWL6-1618589248434)(media/39eced10ab053915d8792acc3c9e7660.png)]

1.4 图像运算 The Arithmetic of Images

!要在前面输入:(require 2htdp/image)

图像是可视的、矩形的数据块,例如,一张照片或一个几何图形及其框架。你可以在任何你可以写下表达式的地方插入图像,因为图像就是值,就像数字和字符串一样。

您的程序还可以使用原始操作操作图像。这些基本操作有三种形式。第一种是关于基本形象的创作:

  • circle
    用半径、模式串和颜色串产生圆图像;

  • ellipse
    用两种直径、一种模态字符串和一种颜色字符串产生一个椭圆;

  • line
    用两个点和一个颜色字符串生成一条线;

  • rectangle 用宽度、高度、模式字符串和颜色字符串生成矩形;

  • text
    用字符串、字体大小和颜色字符串生成文本图像;

  • triangle
    用一个大小、一个模态字符串和一个颜色字符串生成一个向上的等边三角形。

    这些操作的名称主要解释了它们创建的图像的类型。你所必须知道的是,模式字符串的意思是“实体”"solid"或“轮廓”
    “outline”,,而颜色字符串是诸如“橙色”“orange”、 “黑色”,
    “black”、之类的字符串。

> (circle 10 “solid” “green”)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EjNVeZnn-1618589251959)(media/8b4a3445bb01b28c71bcf8e6db5a0baf.png)]
> (rectangle 10 20 “solid” “blue”)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRcEQV16-1618589251962)(media/57ac028b6ec1f5e38c49d36dedfc026f.png)]
> (star 12 “solid” “gray”)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Xm2eNff-1618589251964)(media/126d3d3b4a118f25c04559f5d3a2bf19.png)]

第二类函数涉及图像的图像属性:

  • image-width
    以像素为单位确定图像的宽度;

  • image-height
    确定图像的高度;

> (image-width (circle 10 “solid” “red”))
20
> (image-height (rectangle 10 20 “solid” “blue”))
20

要正确理解第三种构图原语,需要引入一个新概念:定位点(anchor
point
)。一幅图像不仅仅是一个像素,它由许多像素组成。具体来说,每个图像都像一张照片,即像素的矩形。其中一个像素是一个隐式锚点。当你使用一个图像原语来组成两个图像时,这个组合是相对于定位点发生的,除非你明确指定其他点:

  • overlay
    使用中心作为锚点,将它应用到的所有图像放置在彼此之上;

  • overlay/xy
    就像
    overlay
    但是在两个图像参数之间接受两个数字x和y。它将第二幅图像向右移动x个像素,向下移动y个像素——都相对于第一幅图像的左上角移不出所料,负的x使图像向左平移,负的y向上平移;

  • overlay/align
    就像
    overlay但是接受两个将锚点移动到矩形的其他部分的字符串。总共有九个不同的方位,尝试所有的可能性!

    2htdp/image图像库还提供了许多用于组合图像的其他基本函数。当您熟悉图像处理时,您将需要阅读这些内容。现在,我们将介绍另外三种,因为它们对于创建游戏的动画场景和图像非常重要:

  • empty-scene
    创建具有一定宽度和高度的矩形;

  • place-image
    将图像置于场景中的指定位置。如果图像不适合给定的场景,它会被适当地裁剪;

  • scene+line
    使用一个场景、四个数字和一种颜色在给定图像中绘制一条线。用它做实验,看看它是如何工作的。

数字运算 图像运算
(+ 1 1) == 2 (overlay (square 4 “solid” “orange”)
(circle 6 “solid” “yellow”))
==
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9LztIya-1618589251967)(media/5e0cb172f4614bdef4d887ac007243d8.png)]

(+
1 2) == 3

(underlay (circle 6 “solid” “yellow”)
(square 4 “solid” “orange”))
==
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tP1FttI6-1618589251969)(media/5e0cb172f4614bdef4d887ac007243d8.png)]

(+
2 2) == 4

(place-image (circle 6 “solid” “yellow”)
10 10
(empty-scene 20 20))
==
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PH7SDdlV-1618589251971)(media/4761e4f35f24a38d20b155c5c49d436d.png)]

图 14: 图像创建规律

图像的运算法则与数字的运算法则相似;参见图14以获得一些示例以及与数值算法的比较。同样,没有图像被破坏或改变。像+
一样,这些原语只是组成新的图像,以某种方式结合给定的图像。

Exercise 5. Use the 2htdp/image library to create the image of a
simple boat or tree. Make sure you can easily change the scale of the entire
image.

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qmOwC0Pn-1618589248435)(media/be3211053713aac5202bf5b1ebd18d7e.png)]

Exercise 6. Add the following line to the definitions area:

Copy and paste the image into your DrRacket.

(define
cat
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVTfjneQ-1618589248437)(media/2f42043a506b96a8fb53d4eb6d70d37c.png)])

Create an expression that counts the number of pixels in the image.

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Veq7YcA-1618589248438)(media/ccfcfc8f134e06cf59478a6d53116bbb.png)]

1.5 布尔运算 The Arithmetic of Booleans (逻辑运算)

在我们设计程序之前,我们还需要最后一种原始数据:布尔值。布尔值只有两种:#true和#false。程序使用布尔值来表示决策或开关的状态。

布尔值的计算也很简单。特别地,BSL语程序有三种操作:
or,
and,

not.这些运算有点像数字的加法、乘法和否定。当然,因为只有两个布尔值,它实际上有可能演示这些函数如何在所有可能的情况下工作:

  • or
    检查任何给定的布尔值是否为 #true:
> (or #true #true)
#true
> (or #true #false)
#true
> (or #false #true)
#true
> (or #false #false)
#false
  • and
    检查是否所有给定的布尔值都为 #true:
> (and #true #true)
#true
> (and #true #false)
#false
> (and #false #true)
#false
> (and #false #false)
#false
  • not
    总是选择没有给出的布尔值:
> (not #true)
#false

毫不奇怪,or与and可以和两个以上的表达一起使用。最后,除了这些解释之外,还有更多关于or
和 and 和的内容,但是要解释额外的部分,需要再次查看嵌套表达式。

Exercise 7. Boolean expressions can express some everyday problems. Suppose
you want to decide whether today is an appropriate day to go to the mall. You go
to the mall either if it is not sunny or if today is Friday (because that is
when stores post new sales items). Nadeem Hamid suggested this formulation of
the exercise.

Here is how you could go about it using your new knowledge about Booleans. First
add these two lines to the definitions area of DrRacket:

(define sunny #true)
(define friday #false)

Now create an expression that computes whether sunny is false or friday is true.
So in this particular case, the answer is #false. (Why?)

Answer

(or (not sunny) friday))

这与后面的练习十五相互印证!

1.6混合逻辑运算 Mixing It Up with Booleans

布尔值的一个重要用法涉及不同类型数据的计算。从序言中我们知道BSL程序可以通过定义来命名值。例如,我们可以定义

(define
x 2) 然后计算其倒数

(define
inverse-of-x
(/
1 x))

只要我们不编辑程序并将x更改为0,这就可以正常工作。

这就是布尔值出现的地方,特别是在条件计算中。首先,基本函数
=
确定两个(或多个)数字是否相等。如果是,则生成#true,否则生成#false。其次,有一种BLS的表达方式我们还没有提到:
if
表达式。它使用" if
"这个词,好像它是一个原始的函数;它不是。单词“if”后面跟着三个用空格隔开的表达式(包括制表符、换行符等)。整个表达式自然被括在括号中。下面是一个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsRptWDT-1618589248439)(media/36fe4a2c4bef31a48642409dd9834139.png)]

(if question-expression then-answer-expression else-answer-expression)

If the question-expression is neither #true nor #false,
if
reports an error.

这个if表达式包含(= x 0)、0和(/ 1
x)三个所谓的子表达式。这个表达式的运算分两个步骤进行:

  • 总是计算第一个表达式。它的结果必须是布尔值。

如果第一个表达式的结果是#true,则对第二个表达式求值;否则第三个就是。无论它们的结果是什么,它们也是整个if表达式的结果。

接下来的几章将介绍比if更好的表达式来表示条件运算,最重要的是,设计它们的系统方法。

Exercise 8. Add the following line to the definitions area:

(define
cat
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rTfjk4Wv-1618589248440)(media/2f42043a506b96a8fb53d4eb6d70d37c.png)])

Create a conditional expression that computes whether the image is tall or wide.
An image should be labeled “tall” if its height is larger than or equal to its
width; otherwise it is “wide”. See
exercise 1
for how to create such expressions in DrRacket; as you experiment, replace the
cat with a rectangle of your choice to ensure that you know the expected answer.

Now try the following modification. Create an expression that computes whether a
picture is “tall”, “wide”, or “square”.

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5OswatqM-1618589248442)(media/a517d07e3c25456c8a29329a2dcc16aa.png)]

1.7 判断:了解数据 Predicates: Know Thy Data

为防止表达式出现错误的一种方法是使用判断(predicate),predicate是使用值并确定它是否属于某个数据类的函数。例如,predicate
number?
确定给定值是否为数字::

> (number? 4)
#true
> (number? pi)
#true
> (number? #true)
#false
> (number? “fortytwo”)
#false

如您所见,判断(predicate)产生布尔值。因此,当判断与条件表达式结合时,程序可以防止表达式被误用:

(define
in
…)

(if
(string?
in)
(string-length
in)
…)

我们在本章中介绍的每一类数据都带有一个判断。尝试用
number?,
string?,
image?,

boolean?
以确保您了解它们是如何工作的。

除了区分不同形式的数据的判断外,编程语言中也有区分不同类型数字的判断。在BLS中,数字有两种分类方式:结构和精确程度。结构指的是熟悉的数字集:integer?,rational?,
real?,和
complex?,
但是许多编程语言,包括BSL,也选择对众所周知的常量使用有限近似,这导致rational?
判断:

> (rational? pi)
#true

关于准确性,我们之前已经提到过这个想法。现在,用exact?

inexact?
以确保他们执行他们名字所暗示的检查。稍后我们将详细讨论数字的本质。

Exercise 9. Add the following line to the definitions area of DrRacket:

(define
in
…)

Then create an expression that converts the value of in to a positive number.
For a String,
it determines how long the
String is; for
an Image, it
uses the area; for a
Number, it
decrements the number by 1, unless it is already 0 or negative; for #true it
uses 10 and for #false 20. Hint Check out
cond
from the Prologue: How to
Program (again).

See
exercise 1
for how to create expressions in DrRacket.

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00KEWj0j-1618589248443)(media/8b7a81e49851d74b30aee86f58c3d50c.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UIlqDKJk-1618589248444)(media/f60a78c5bd48df13f90c4b91012ee84e.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AzIKGVoC-1618589248445)(media/bbe28bf3998c93f44b2769f7dced75f4.png)]

2 函数和程序

作为编程而言,“运算”是游戏的一半;另一半是“代数”。当然,“代数”与学校代数概念的联系,就像前一章的“运算”与小学算术的联系一样少。具体来说,需要的代数概念是变量、函数定义、函数应用和函数组合。这一章以一种有趣和容易理解的方式让你重新认识这些概念。

2.1 函数 Functions

程序的函数。与函数一样,程序消耗输入并产生输出。与您可能知道的函数不同,程序处理各种数据:数字、字符串、图像、所有这些的混合,等等。此外,程序是由现实世界中的事件触发的,程序的输出会影响现实世界。例如,一个电子表格程序可以通过在一些单元格中填充数字来响应会计的按键,或者计算机上的日历程序可以在每个月的最后一天启动一个月工资程序。最后,一个程序可能不会一次使用它所有的输入数据,相反,它可能决定以增量方式处理数据。

**定义:**当许多编程语言模糊了程序和函数之间的关系时,BSL将它带到了前台。每个BSL程序由几个定义组成,通常后跟一个涉及这些定义的表达式。有两种定义:

常量定义
(define
Variable Expression)

(define (FunctionName VariableVariable) Expression)

函数定义

也就是说,为了定义一个函数,我们写下

  • “(define
    (”,

    • 函数的名称,

    • 后面是几个变量,用空格隔开,以")"结尾,

    • 和一个后面跟着“)”的表达式。

      这就是它的全部。下面是一些小例子:

  • (define
    (f x) 1)

  • (define
    (g x y)
    (+
    1 1))

  • (define
    (h x y z)
    (+
    (*
    2 2) 3))

Exercise 11. Define a function that consumes two numbers, x and y, and
that computes the distance of point (x,y) to the origin.

In
exercise 1
you developed the right-hand side of this function for concrete values of x
and y. Now add a header.

Answer

(define (distance x y)(sqrt (+ (sqr x) (sqr y))))

Exercise 12. Define the function cvolume, which accepts the length of a side
of an equilateral cube and computes its volume. If you have time, consider
defining csurface, too.

Hint An equilateral cube is a three-dimensional container bounded by six
squares. You can determine the surface of a cube if you know that the square’s
area is its length multiplied by itself. Its volume is the length multiplied
with the area of one of its squares. (Why?)

Answer

(define (cvolume x)

(* x x x))

(define (csurface x)

(* 6 (* x x)))

Exercise 13. Define the function string-first, which extracts the first
1String
from anon-empty string.

Answer

(define (string-first s)

(if (> (string-length s) 0)

(substring s 0 1)

“”))

(string-first “string”)

Exercise 14. Define the function string-last, which extracts the last
1String
from a non-empty string.

Answer

(define (string-last s)

(if (> (string-length s) 0)

(substring s (- (string-length s) 1))

“”))

(string-last “string”)

Exercise 15. Define ==>. The function consumes two Boolean values, call
them sunnyand friday. Its answer is #true if sunny is false or friday is true.
Note Logicians call this Boolean operation implication, and they use the
notation sunny => friday for this purpose.

Answer

(define sunny #false)

(define friday #true)

(define (=> sunny friday)

(or (not sunny) friday))

(=> sunny friday)

Exercise 16. Define the function image-area, which counts the number of
pixels in a given image. See
exercise 6
for ideas.

Answer

(require 2htdp/image)

(define (image-area IMAGE)

(* (image-width IMAGE) (image-height IMAGE)))

Exercise 17. Define the function image-classify, which consumes an image and
conditionally produces “tall” if the image is taller than wide, “wide” if it is
wider than tall, or “square” if its width and height are the same. See
exercise 8
for ideas.

Answer

(define (image-classify img)

(cond

[(> (image-height img) (image-width img)) “tall”]

[(= (image-height img) (image-width img)) “square”]

[(< (image-height img) (image-width img)) “wide”]))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0aB2d9eB-1618589248446)(media/c8462e591a66c9e1c705e213dda74f1b.png)]

Exercise 18. Define the function string-join, which consumes two strings and
appends them with “_” in between. See
exercise 2
for ideas.

Answer

(define (string-join s1 s2)

(string-append s1 “_” s2))

(string-join “hello” “world”)

Exercise 19. Define the function string-insert, which consumes a string str
plus a number i and inserts “_” at the ith position of str. Assume i is a
number between 0 and the length of the given string (inclusive). See
exercise 3
for ideas. Ponder how string-insert copes with “”. 定义函数
string-insert,该函数使用字符串 str 和数字 i,并在str的第 i 个位置插入“
_”。假设 i 是介于 0 和给定字符串长度(包括该长度)之间的数字。

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w2kODlBr-1618589248448)(media/ef95d4f25ffe688165a403ff79399b33.png)]

Exercise 20. Define the function string-delete, which consumes a string plus
a numberi and deletes the ith position from str. Assume i is a number between 0
(inclusive) and the length of the given string (exclusive). See
exercise 4
for ideas. Can string-deletedeal with empty strings?

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcuiNHBS-1618589248449)(media/ed7d2cfe6b746c6154eea2dfe3446aea.png)]

可以处理空字符

-----------------------------------------------------------------------

2.2 计算 Computing

函数定义和应用程序协同工作。如果你想设计程序,你必须理解这种协作,因为你需要想象
DrRacket
是如何运行你的程序的,因为你需要弄清楚什么地方出错了,什么地方会出错。

虽然你可能在数论课上看到过这个概念,但我们更喜欢用我们自己的方式来解释它。评估一个函数应用程序需要三个步骤:
①DrRacket 确定参数表达式的值; ②检查参数个数与函数参数个数是否相同;
③如果相同,DrRacket
计算函数的值,所有参数都替换为相应的参数值。最后一个值是函数应用程序的值。这有点拗口,所以我们需要例子。

这是 f 的示例计算:

最后一个等式很奇怪,因为 x 没有出现在 f 的主体中。因此,用 2 替换 x
在函数主体中的出现,得到 1,这就是函数主体本身。

对于 ff,DrRacket 执行另一种计算:

最好的一点是,当你把这些计算法则和算术法则结合起来,你几乎可以预测 BSL
中任何程序的结果:

当然,我们可以在其他地方重用这个计算的结果:

总之,DrRacket
是一个非常快的代数学生。它了解所有算术定律,并且擅长替代。更好的是,DrRacket
不能仅确定表达式的值;它也可以告诉你它是如何做到的。也就是说,它可以逐步向你显示如何解决这些代数问题,这些问题要求你确定表达式的值。总之,DrRacket
是一个非常快的代数学生。它了解所有算术定律,并且擅长替代。更好的是,DrRacket
不能仅确定表达式的值;它也可以告诉你它是如何做到的。也就是说,它可以逐步向你显示如何解决这些代数问题,这些问题要求你确定表达式的值。

再看看 DrRacket 附带的按钮。其中一个看起来像是音频播放器上的“advance to next
track”按钮。如果单击此按钮,则会弹出步进器窗口,你可以在定义区域中逐步计算程序。

在定义区域中输入 ff 的定义。在底部添加 (ff (+ 1 1))
。现在单击步骤。将显示步进器窗口。 下图显示了该软件 6.2
版的外观。此时,你可以使用前进和后退箭头查看 DrRacket
用于确定表达式值的所有计算步骤。观察步进器如何执行与我们相同的计算。

Stop!是的,你可以使用 DrRacket 解决一些代数作业。试用步进器提供的各种选项。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fG7ECPx0-1618589248450)(media/1e15bf1d5a8ec690f88af1905c6dd268.png)]

Exercise 21. Use DrRacket’s stepper to evaluate (ff (ff 1)) step-by-step.
Also try
(+
(ff 1) (ff 1)). Does DrRacket’s stepper reuse the results of computations?

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-51E1BOnR-1618589248452)(media/bd6a88e56540835adc3ac9dbe739f514.png)]

在这一点上,你可能认为你回到了代数课程,所有这些计算涉及到无趣的函数和数字。幸运的是,这种方法适用于所有程序,包括本书中介绍的那些有趣的程序。

让我们从处理字符串的函数开始。回顾一些字符串算术定律:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kKVoDfQp-1618589248452)(media/44aea1ed00c9f3d0549874b9f2c10c16.png)]

现在假设我们定义一个函数来创建字母的开头:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nm6mU6ed-1618589248453)(media/942de8dda7a7244b956bb4cc36efd967.png)]

当你将这个函数应用于两个字符串时,你会得到一个字母开头

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tz9w5JeR-1618589248454)(media/e29130a581da477a0c296f1e00c44b27.png)]

然而,更重要的是,计算定律解释了 DrRacket 如何决定结果,以及如何预测
DrRacket 的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lM9Vv9z0-1618589248455)(media/e396854a088e77e8841f21ea5f1b6753.png)]

由于 last-name 没有出现在 opening 的定义中,用“Fisler”代替它是没有效果的。

本书的其余部分介绍了更多形式的数据。为了解释对数据的操作,我们始终使用本书中的算术定律。

Exercise 22. Use DrRacket’s stepper on this program fragment:

(define (distance-to-origin x y)
(sqrt (+ (sqr x) (sqr y))))
(distance-to-origin 3 4)

Does the explanation match your intuition?

Answer

(distance-to-origin 3 4)

== ;Drracket substitutes 3 for x;4 for y

(sqrt (+ (aqr 3) (sqr 4)))

== 5

Exercise 23. The first 1String in “hello world” is “h”. How does the
following function compute this result?

(define (string-first s)
(substring s 0 1))

Use the stepper to confirm your ideas.

Answer

(substring
s 0 1))

== ; Drracket substitutes “hello world” for s

( substring “hello world” 0 1)

== “h”

Exercise 24. Here is the definition of ==>: y

(define (==> x y)
(or (not x) y))

Use the stepper to determine the value of (==> #true #false).

Answer

(define x #true)

(define y #true)

(define (==> x y)

(or (not x) y))

(==> x y)

== ; Drracket substitutes #true for x; #true for y

(#true #true) or ( #false #true)

==#true

Exercise 25. Take a look at this attempt to solve
exercise 17:

(define (image-classify img)
(cond
[(> (image-height img) (image-width img)) “tall”]
[(= (image-height img) (image-width img)) “square”]
[(< (image-height img) (image-width img)) “wide”]))

Does stepping through an application suggest a fix?

Exercise 26. What do you expect as the value of this program:

(define (string-insert s i)
(string-append (substring s 0 i)
“_”
(substring s i)))
(string-insert “helloworld” 6)

Confirm your expectation with DrRacket and its stepper.

答案 略

2.3 组合函数 Composing Functions

一个程序很少由一个函数定义组成。通常,程序由一个主定函数和几个其他函数组成,并将一个函数应用程序的结果转换为另一个函数应用程序的输入。与代数类似,我们称这种方式为定义函数组合,并且我们称这些附加函数为
auxiliary functions 或 *helper functions(*辅助函数)。

(define (letter fst lst signature-name)
(string-append
(opening fst) ;;letter的开头
“\n\n” ;;\n-另起一行
(body fst lst) ;;letter的内容
“\n\n”
(closing signature-name))) ;;letter的结尾署名
(define (opening fst)
(string-append "Dear " fst “,”))
(define (body fst lst)
(string-append
“We have discovered that all people with the” “\n”
"last name " lst " have won our lottery. So, " “\n”
fst ", " “hurry and pick up your prize.”))
(define (closing signature-name)
(string-append
“Sincerely,”
“\n\n”
signature-name
“\n”))

考虑到 上面填写字母模板的程序。它由四个函数组成。第一个是 main
函数,它根据收件人的名字和姓氏以及署名生成完整的字母。main
函数指的是三个辅助函数,用于生成字母的三个部分: opening、body 和
signature,并以正确的顺序用
string-append
组合结果。

Stop! 将这些定义输入 DrRacket 的定义区域,单击
RUN,然后在交互区域中计算这些表达式:

在旁边结果是一个长字符串,其中包含 “\n”,当打印该字符串时代表一个新的一行。
现在添加**(require 2htdp / batch-io)**到你的程序中,这会将函数
write-file 添加到其指令表中; 它允许你将此字符串打印到控制台:

Programs 在某种程度上解释了此类批处理程序.

一般来说,当一个问题涉及到不同的计算任务时,一个程序应该由每个任务一个函数和一个把它们放在一起的主函数组成。我们把这个想法说成一个简单的口号:

Define one function per task. (为每个任务定义一个函数)

遵循这个口号的好处是,你可以得到相当小的函数,每一个函数都很容易理解,其组成也很容易理解。一旦你学会了设计函数,你就会认识到,让小函数正确地工作要比使用大函数容易得多。更好的是,如果你曾经因为问题语句的某些更改而需要更改程序的一部分,那么当它被组织为一组小函数而不是一个大型的单函数时,就更容易找到相关的部分。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6DyTP9Us-1618589248457)(media/8484d313cbe414c8e9f624c739323deb.png)]

现在暂时把 'stdout 看作一个字符串看待

这里有一个例子来说明这一点:

一个小镇上的电影院(垄断的)的老板在制定票价方面有完全的自由。他要价越高,买得起票的人就越少。他要价越低,演出的成本就越高,因为出席人数增加。在最近的一次实验中,船主确定了票价和平均出勤率之间的关系。

以每张票 $ 5.00 的价格,有 120 人参加表演。 票价每变动 10
美分,平均出勤率就会变化 15 个人。 也就是说,如果所有者收取 5.10
美元,则平均有105人参加;如果价格下降到 $ 4.90,则平均出勤率增加到
135。让我们将此想法转换为数学公式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HaUJ8AK-1618589248458)(media/1ba6f57410de180f8f6da9bea0b3f120.png)]

Stop! 在继续操作之前,请解释负号。

不幸的是,出席人数的增加也带来了成本的增加。每一场演出的固定成本为180
美元,加上每位观众 0.04 美元的可变成本。cost=180+0.04*attendance

为了使利润最大化,老板想知道利润和票价之间的确切关系。

虽然任务很明确,但是如何执行却不清楚。在这一点上,我们只能说几个变量是相互依赖的。

当我们遇到这样的情况时,最好是一个一个地梳理出各种依赖关系:

①问题陈述指定了参会者的数量如何取决于票价。计算这个数字显然是一个单独的任务,因此应该有自己的函数定义:

(define (attendees ticket-price)
(- 120 (* (- ticket-price 5.0) (/ 15 0.1))))

②revenue 完全来自门票销售,这意味着它是门票价格和出席人数的乘积:

(define (revenue ticket-price)
(* ticket-price (attendees ticket-price)))

③cost (费用)
由两部分组成:一部分是固定的(180美元),另一部分是根据参加人数而变化的。考虑到参会者的数量是票价的函数,用于计算演出成本的函数也必须使用票价,以便复用
attendees 函数:

(define (cost ticket-price)
(+ 180 (* 0.04 (attendees ticket-price))))

④最后,profit (利润) 是给定票价的收入和成本之差:

(define (profit ticket-price)
(- (revenue ticket-price)
(cost ticket-price)))

BSL 对 profit 的定义直接遵循了非正式问题描述的建议。

这四个函数都是用来计算利润的,现在我们可以用 profit 函数来确定一个好的票价。

Exercise 27. Our solution to the sample problem contains several constants
in the middle of functions. As One Program, Many
Definitions
already points out, it is best to give names to such constants so that future
readers understand where these numbers come from. Collect all definitions in
DrRacket’s definitions area and change them so that all magic numbers are
refactored into constant definitions.

Answer (参见 程序重构 一种程序不同表达) 牵扯下一节的内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HMZr0qa7-1618589248459)(media/72d378c8d0c031879a1401ea987350e8.png)]

Exercise 28. Determine the potential profit for these ticket prices: $1,
$2, $3, $4, and $5. Which price maximizes the profit of the movie theater?
Determine the best ticket price to a dime.

Here is an alternative version of the same program, given as a single function
definition:

(define (profit price)
(- (* (+ 120
(* (/ 15 0.1)
(- 5.0 price)))
price)
(+ 180
(* 0.04
(+ 120
(* (/ 15 0.1)
(- 5.0 price)))))))

Enter this definition into DrRacket and ensure that it produces the same results
as the original version for $1, $2, $3, $4, and $5. A single look should
suffice to show how much more difficult it is to comprehend this one function
compared to the above four.

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQrKaUoP-1618589248460)(media/2a4e3a0d3001c727a52e70ecae4bf5aa.png)]

虽然两者的运算结果相同,但由于上面给出的程序没有进行分布编程,使整个程序看起来十分复杂,不容易解释和修改。

Exercise 29. After studying the costs of a show, the owner discovered
several ways of lowering the cost. As a result of these improvements, there is
no longer a fixed cost; a variable cost of $1.50 per attendee remains.

Modify both programs to reflect this change. When the programs are modified,
test them again with ticket prices of $3, $4, and $5 and compare the results.

Answer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PwOKmywY-1618589248461)(media/05838ad0f0bd4afcd8389c8520317ecf.png)]

2.4 全局常量 Global Constants

正如序言已经说过的,像 profit
这样的函数受益于全局常量的使用。每种编程语言都允许程序员定义常量。在 BSL
中,这样的定义有如下形式:

  • 写下
    “(define
    ”,

  • 写下名字

  • 然后是空格和表达式,以及

  • 写下 “)”.

    常量的名称是一个全局变量,而定义则称为常量定义。我们倾向于称常数定义中的表达式为定义的右边。

    常量定义为所有形式的数据引入了名称:数字、图像、字符串等等。下面是一些简单的例子:

; the current price of a movie ticket:
(define CURRENT-PRICE 5)
; useful to compute the area of a disk:
(define ALMOST-PI 3.14)
; a blank line:
(define NL “\n”)
; an empty scene:
(define MT (empty-scene 100 100))

前两个是数字常量,后两个是字符串和图像。按照惯例,我们对全局常量使用大写字母,因为这样可以确保无论程序有多大,程序的读者都可以很容易地将这些变量与其他变量区分开来。

程序中的所有函数都可以引用这些全局变量。对变量的引用就像使用相应的常量一样。使用变量名而不是常量的好处是,对常量定义的一次编辑将影响所有的使用。例如,我们可能希望将数字添加到
ALMOST-PI 或放大一个空的场景:

(define ALMOST-PI 3.14159)
; an empty scene:
(define MT (empty-scene 200 800))

我们的大多数示例定义都在右边使用了文字常量,但是最后一个定义使用了表达式。实际上,程序员可以使用任意表达式来计算常量。假设一个程序需要处理一个大小和中心的图像:

(define WIDTH 100)
(define HEIGHT 200)
(define MID-WIDTH (/ WIDTH 2))
(define MID-HEIGHT (/ HEIGHT 2))

它可以在右边使用两个带有文字常量的定义和两个计算过的常量,也就是说,变量的值不仅是文字常量,而且是计算表达式值的结果。

我们再次提出了一个必要的口号:

For every constant mentioned in a problem statement, introduce one constant
definition.

对于问题陈述中提到的每个常量,请引入一个常量定义。

Exercise 30. Define constants for the price optimization program at the
movie theater so that the price sensitivity of attendance (15 people for every
10 cents) becomes a computed constant.

Answer

Exercise 29 (define ATTENDEES-CHANGE 15) ATTENDEES-CHANGE ==“PRICE
SENSITIVITY OF ATTENDANCE”

2.5 程序 Programs

现在可以创建简单的程序了。从编码的角度来看,一个程序就是一堆函数和常量定义。通常会挑出一个函数作为“主”函数,而这个主函数往往会组合其他函数。然而,从启动一个项目的角度来看,有两种不同的类型:

  • 批处理程序——程序由许多附属程序组合在一起,由main程序来运行,最后给出运行结果

  • 交互式程序——用户会输入内容,并驱动事件(event)

    当我们启动一个交互式程序时,主要函数将此描述通知操作系统。一旦输入事件发生,操作系统就调用匹配的事件处理程序。类似地,操作系统从描述中知道何时以及如何将这些函数调用的结果表示为输出。

    这本书主要集中在通过图形用户界面(GUI)交互程序;还有其他种类的互动程序,当你继续学习计算机科学的时候,你会了解这些程序。

    在DrRacket中,我们在交互区启动批处理程序,这样我们就能看到程序在工作了。

    如果程序能够从某些文件中检索输入,并将输出传递给其他文件,那么程序将更加有用。事实上,**“批处理程序”**这个名字可以追溯到早期的计算时代,那时一个程序从一批穿孔卡中读取一个文件(或几个文件),并将结果放在其他一些文件中,也就是一批卡片中。从概念上讲,批处理程序一次读取输入文件并同时生成结果文件。

    这里介绍文件读取read-file和写入write-file的函数,需要添加 2htdp/batch-io 库

  • read-file,
    它以字符串的形式读取整个文件的内容

  • write-file,
    它根据给定的字符串创建一个文件。

    这些函数将字符串写入文件并从中读取字符串:

Before you evaluate these expressions, save the definitions area in a file.

> (write-file “sample.dat” “212”)
“sample.dat”
> (read-file “sample.dat”)
“212”

在第一次交互之后,文件名为 “sample.dat” 包含 212

write-file
的结果是确认已将字符串写入文件。如果文件已经存在,它将其内容替换为给定的字符串;否则,它将创建一个文件,并将给定的字符串作为其内容。第二次交互,(read-file
“sample.dat”),产生了“212”,是读取 “sample.dat” 的内容为字符串。

出于实际原因,
write-file
也接受’stdout
(一种特殊的标记)作为第一个参数。然后在当前交互区域显,示结果文件内容,例如:

名称 'stdout 和 'stdin分别是标准输出设备和标准输入设备的缩写.

> (write-file 'stdout “212\n”)
212
'stdout

同样,read-file
允许 'stdin代替文件名,然后从当前交互区域读取输入。

让我们通过一个简单的示例来演示如何创建批处理程序。假设我们希望创建一个程序,将用华氏温度计测量的温度转换为摄氏温度。别担心,这个问题不是测试你的物理知识;换算公式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zvX9ce8o-1618589248462)(media/72eb4335c490cc9822e2e2af40fa8f0a.png)]

自然,这个公式中f是华氏温度C是摄氏温度。虽然这个公式对于代数课本来说已经足够好了,但是数学家或程序员会在方程的左边写C(f)来提醒读者f是一个给定的值,而C是从f中计算出来的。

直接把这个公式翻译成BSL:

(define (C f)
(* 5/9 (- f 32)))

回想一下,5/9是一个数字,准确地说,是一个有理数,C取决于给定的f,这是函数符号所表达的。

在交互区启动此批处理程序正常工作:

> (C 32)
0
> (C 212)
100
> (C -40)
-40

但是假设我们希望使用这个函数作为程序的一部分,该程序从一个文件中读取华氏温度,将这个数字转换为摄氏温度,然后创建另一个包含结果的文件。

一旦我们有了BSL中的转换公式,创建主函数就意味着将C和现有的原始函数组合在一起:

(define (convert in out)
(write-file out
(string-append
(number->string
(C
(string->number
(read-file in))))
“\n”)))

我们调用主函数convert。它使用两个文件名:in表示发现华氏温度的文件,out表示我们想知道的摄氏度的结果。五个函数的联合计算
convert’s (转换的)结果。让我们一步步分析convert’s的程序:

①(read-file
in) 检索指定的文件的内容作为字符串;

②string->number
将该字符串转换为数字

③C将数字解释为华氏温度,并将其转换为摄氏温度;

④number->string使摄氏温度并将其转换为字符串;

⑤(write-file
out
…)
将此字符串放入名为out的文件中

这个长长的步骤列表可能看起来令人难以承受,而且它甚至不包括string-append
。解释一下:

(string-append

“\n”)

相比之下,代数预备课程的平均函数组成包括两个,可能是三个函数。但是请记住,程序实现了真实世界的目的,而代数练习仅仅说明了函数组合的概念。

在这一点上,我们可以尝试转换。首先,我们使用写文件创建一个输入文件转换:

你可以用文件编辑器建立"sample.dat"

> (write-file “sample.dat” “212”)
“sample.dat”
> (convert “sample.dat” 'stdout)
100
'stdout
> (convert “sample.dat” “out.dat”)
“out.dat”
> (read-file “out.dat”)
“100”

对于第一次交互,我们使用’stdout
,这样我们就可以在DrRacket的交互区域查看转换输出的内容。对于第二个选项,convert的名称为"out.dat"。如预期的那样,转换调用返回以下字符串;从write-file
的描述中,我们还知道它在文件中存储了华氏温度。在这里,我们使用read-file,读取该文件的内容,但您也可以使用文本编辑器查看它。

除了运行批处理程序,它也具有指导意义的一步通过计算。确保文件"sample.dat"
的存在和包含的只是一个数字,然后点击STEP
按钮在DrRacket。这样做会打开另一个窗口,您可以在其中仔细阅读对批处理程序的主函数调用所触发的计算过程。您将看到该过程遵循上述大纲。

Exercise 31. Recall the letter program from Composing
Functions.
Here is how to launch the program and have it write its output to the
interactions area:

> (write-file
'stdout
(letter “Matthew” “Fisler” “Felleisen”))
Dear Matthew,
We have discovered that all people with the
last name Fisler have won our lottery. So,
Matthew, hurry and pick up your prize.
Sincerely,
Felleisen

'stdout

Of course, programs are useful because you can launch them for many different
inputs. Run letter on three inputs of your choice.

Here is a letter-writing batch program that reads names from three files and
writes a letter to one:

(define (main in-fst in-lst in-signature out)
(write-file out
(letter (read-file in-fst)
(read-file in-lst)
(read-file in-signature))))

The function consumes four strings: the first three are the names of input files
and the last one serves as an output file. It uses the first three to read one
string each from the three named files, hands these strings to letter, and
eventually writes the result of this function call into the file named by out,
the fourth argument to main.

Create appropriate files, launch main, and check whether it delivers the
expected letter in a given file.

Answer

(require 2htdp/batch-io)

(define in-fst

(string-append "Dear " “in-fst” “,”))

(define in-lst

(string-append

“We have discovered that all people with the” “\n”

"last name " “in-lst have won our lottery. So,” “\n”

"in-fst , " “hurry and pick up your prize.”))

(define in-signature

(string-append

“Sincerely,”

“\n\n”

“in-signature”

“\n”))

(define (main in-fst in-lst in-signature out)

(write-file out

'stdout

(read-file in-fst)

(read-file in-lst

(read-file in-signature))))

Exercise 32. Most people no longer use desktop computers just to run
applications but also employ cell phones, tablets, and their cars’ information
control screen. Soon people will use wearable computers in the form of
intelligent glasses, clothes, and sports gear. In the somewhat more distant
future, people may come with built-in bio computers that directly interact with
body functions. Think of ten different forms of events that software
applications on such computers will have to deal with. (略)

本节的目的是介绍编写交互式BSL程序的机制。因为书中许多项目风格的例子都是交互式的程序,所以我们慢慢地、仔细地介绍思想。当您处理一些交互式编程项目时,您可能希望返回到本节;第二次或第三次阅读可能会澄清一些高级方面的机制。

就其本身而言,原始计算机是一种无用的物理设备。它被称为硬件,因为你可以触摸它。一旦你安装了软件,即一套程序,这个设备就变得有用了。通常安装在计算机上的第一个软件是操作系统。它的任务是为您管理计算机,包括连接设备,如显示器、键盘、鼠标、扬声器等。它的工作方式是当用户按下键盘上的一个键时,操作系统运行一个处理击键的函数。我们说击键是一个键事件,函数是一个事件处理程序。以同样的方式,操作系统为时钟滴答声、鼠标动作等运行一个事件处理程序。相反,在事件处理程序完成其工作后,操作系统可能不得不更改屏幕上的映像、敲响警钟、打印文档或执行类似的操作。为了完成这些任务,它还运行一些函数,这些函数将操作系统的数据转换为声音、图像、打印机上的操作等等。

当然,不同的程序有不同的需求。一个程序可以将按键解释为控制核反应堆的信号;另一个则将它们传递给文字处理程序。为了使一台通用计算机在这些完全不同的任务上工作,不同的程序安装不同的事件处理程序。也就是说,火箭发射程序使用一种函数来处理时钟滴答声(clock
ticks),而烤箱软件则使用另一种函数。

设计一个交互式程序需要一种方法来指定一些函数来处理键盘事件,另一个函数来处理时钟滴滴涕,第三个函数来以图像的形式显示一些数据,等等。交互程序的主要函数是把这些名称传达给操作系统,也就是程序启动的软件平台。

DrRacket是一个小型操作系统,BSL是它的编程语言之一。后者附带了2htdp/universe库*(require
2htdp/universe)*,它提供了
big-bang机制,告诉操作系统哪个函数处理哪个事件。此外,
big-bang将跟踪程序的状态(state
of the
program
)。为此,它提供了一个必需的子表达式,它的值成为程序的初始状态。除此之外,
big-bang还包括一个必要条款和许多可选条款。这需要
to-draw
告诉DrRacket如何渲染程序的状态,包括初始状态。每个可选子句都告诉操作系统某个函数处理某个事件。在BSL中处理事件意味着该函数使用程序的状态和事件的描述,并生成程序的下一个状态。因此,我们谈到程序的当前状态(current
state
)。

术语
在某种意义上,big-bang
表达描述了一个程序如何与世界的(world)一小部分连接起来。这个世界可能是程序用户玩的游戏,可能是用户观看的动画,也可能是用户用来操作某些注释的文本编辑器。因此,编程语言研究人员经常说,big-bang
是对一个小世界的描述:它的初始状态,状态如何转换,状态如何呈现,以及big-bang
如何决定当前状态的其他属性。本着这种精神,我们也谈论世界的状态(state of
the
world
),甚至把big-bang
程序计划称为世界程序(world programs)解释了世界程序

需要调用两个库:(require 2htdp/image);(require 2htdp/universe)

让我们一步一步地研究这个想法,从这个定义开始:

(define (number->square s)
(square s “solid” “red”))

该函数使用一个正数并生成该大小的实心红方块。点击运行后,对该函数进行实验,如下图:

> (number->square 5)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FTutylT5-1618589251973)(media/5d012cd086c2fc257205cc97083ceaa2.png)]
> (number->square 10)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5XTMqTA0-1618589251974)(media/92dd46b378b1d19842f376a37364bf86.png)]
> (number->square 20)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gQ8fr464-1618589251974)(media/595100507521283b943719bbebdaaf78.png)]

它的行为就像一个批处理程序,消耗一个数字并生成一个图像,DrRacket会给你呈现。

现在在互动领域试试下面的big-bang
表达:

>
(big-bang
100
[to-draw
number->square])

将出现一个单独的窗口,并显示一个[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IN9zWnwa-1618589248464)(media/c20f31633d0243cd38c4dd020a6b3f36.png)]图像红方块。另外,DrRacket交互区域不会再显示提示;就好像程序一直在运行,确实如此。要停止程序,点击DrRacket的停止按钮或窗口的关闭按钮:

> (big-bang 100 [to-draw number->square])
100

当DrRacket停止对big-bang
表达式的计算时,它返回当前状态,在本例中就是初始状态:100。

这里有一个更有趣的big-bang
表达:

> (big-bang 100
[to-draw number->square]
[on-tick sub1] ;减 1
[stop-when zero?])

这个big-bang
表达式在前一个表达式的基础上增加了两个可选的子句:
on-tick子句告诉DrRacket如何处理时钟滴答声,stop-when
子句告诉DrRacket何时停止程序。我们读它,从100的初始状态开始:

  • 每次时钟滴答作响,从当前状态中扣除1;

  • 然后检查是否为
    zero?
    新的状态为真?,如果是,停止;

  • 每当事件处理程序返回一个值时,使用number->square将其呈现为图像。

    现在按下“return” 键,观察会发生什么。最终,表达式的计算终止,DrRacket显示0。

    big-bang
    表达式跟踪当前状态。初始状态是100。每当时钟滴答作响时,它就调用时钟-滴答处理程序并获得一个新状态。因此,大爆炸状态的变化如下:

    100, 99, 98, …, 2, 1, 0

    当状态值变为0时,就完成计算。对于其他状态(从100到1),big-bang
    使用to-draw作为number->square的子句,
    将状态转换为图像。因此,窗口显示一个红色的正方形,经过100个时钟滴答,它从[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eIDg01l-1618589248466)(media/c20f31633d0243cd38c4dd020a6b3f36.png)]图像像素缩小到[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJOCcCH2-1618589248466)(media/8754c43aecf59bd44419dc9e6ff81115.png)]
    图像像素。

    让我们添加一个子句来处理键事件。首先,我们需要一个使用当前状态的函数和描述键事件的字符串,然后返回一个新的状态:

(define (reset s ke)
100)

这个函数丢弃它的参数并返回100,这是我们希望修改的big-bang
表达式的初始状态。其次,我们在big-bang
表达式中添加一个on-key子句:

> (big-bang 100
[to-draw number->square]
[on-tick sub1]
[stop-when zero?]
[on-key reset])

停!解释当你按“回车”,数到10,最后按“a”时会发生什么。

您将看到的是,红方块以每分钟一个像素的速度缩小。但是,只要你按下“a”键,红方块就会重新膨胀为全尺寸,因为重置将对正方形和“a”的当前长度进行调用,并返回100。这个数字成为了big-bang的新状态和数字——>方块使它成为一个全尺寸的红色方块。

为了全面理解big-bang表达式的计算,让我们看一个示意图:

(big-bang cw0
[on-tick tock]
[on-key ke-h]
[on-mouse me-h]
[to-draw render]
[stop-when end?]
…)

这个big-bang表达式指定了三个事件处理程序—tock,
ke-h, 和 me-h—和一个
stop-when
子句.

这个big-bang表达式的计算从cw0开始,cw0通常是一个表达式。我们的操作系统DrRacket将cw0的值安装为当前状态。它使用render(呈现)将当前状态转换为图像,然后将图像显示在单独的窗口中。事实上,渲染是一个big-bang表达式向世界呈现数据的唯一方式。

以下是处理事件的方式:

  • 每当时钟嘀嗒作响时,DrRacket就会对big-bang的当前状态(current
    state)发出嘀嗒声(tock),并接收到一个值作为响应;big-bang将这个返回值作为下一个当前状态。

  • 每次击了一下键,DrRacket就用ke-h表示big-bang的当前状态,并用一个字符串表示这个键;例如,按“a”键用“a”表示,按左箭头键用“left”表示。当ke-h返回一个值时,big-bang将其作为下一个当前状态处理。

  • 每当鼠标进入、离开、移动或点击窗口时,DrRacket就会应用me-h来表示big-bang的当前状态、事件的x和y坐标,以及表示所发生的鼠标事件的类型的字符串;例如,点击鼠标的按钮表示"button-down"
    (“按下按钮”)。当me-h返回一个值时,big-bang将它作为下一个当前状态处理。

    所有事件都按顺序处理;如果两件事似乎同时发生,DrRacket就会充当决定胜负的角色,把它们按一定的顺序排列好。

    事件处理后,big-bang使用皆end?并呈现检查当前状态:

  • (end? cw) 产生一个布尔值。如果这是#true,
    big-bang会立即停止计算。否则执行。

  • (render cw)
    预计将产生一幅图像,big-bang将在一个单独的窗口中显示这幅图像。

current state cw0 cw1
event e0 e1
on clock tick (tock cw0) (tock cw1)
on keystroke (ke-h cw0 e0) (ke-h cw1 e1)
on mouse event (me-h cw0 e0 …) (me-h cw1 e1 …)
its image (render cw0) (render cw1)

图 13: How
big-bang
works

图13中的表简明地总结了这个过程。在第一行,它列出了当前状态名称。第二行列举DrRacket遇到的事件的名称:e0、e1等等。每个ei可能是一个时钟滴答声、一个按键或一个鼠标事件。接下来的三行指定处理事件的结果:

  • 如果e0是一个时钟滴答,big-bang将计算(tock
    cw0)以生成cw1。

  • 如果e0是一个击键事件,则计算 (ke-h cw0
    e0)并生成cw1。处理程序必须应用于事件本身,因为一般来说,程序对每个键的反应都是不同的。

  • 如果e0鼠标事件,
    big-bang
    就会执行(me-h cw0
    e0…)得到cw1。这个调用只是一个草图,因为鼠标事件e0实际上与几段数据——它的性质和协调——相关联,我们只是想说明这一点。

  • 最后,render将当前状态转换为图像,由最后一行指示。DrRacket在单独的窗口中显示这些图像。

    cw1下面的列显示了cw2是如何生成的,具体取决于e1发生的事件类型。

    让我们用特定的事件序列来解释这个表:用户按下“a”键,然后时钟滴答响,最后用户单击鼠标在位置(90,100)触发一个“button
    down”(按下按键)事件。然后,在Racket 中进行注释,

  1. cw1 is the result of (ke-h cw0 “a”);

  2. cw2 is the result of (tock cw1); and

  3. cw3 is the result of (me-h cw2 90 100 “button-down”).

    实际上,我们可以将这三个步骤表示为三个定义的序列:

(define cw1 (ke-h cw0 “a”))
(define cw2 (tock cw1))
(define cw3 (me-h cw2 “button-down” 90 100))

停!big-bang是如何显示这三个状态的呢?

现在让我们考虑一个由三个时钟滴答声组成的序列。在这种情况下,:

  1. cw1是(tock cw0)的结果;

  2. cw2是(tock cw1)的结果;和

  3. cw3是tock cw2的结果。

    或者,用BSL重新表述:

(define cw1 (tock cw0))
(define cw2 (tock cw1))
(define cw3 (tock cw2))

实际上,我们也可以确定cw3通过单个表达式:

(tock (tock (tock cw0)))

这决定了big-bang在三个时钟滴答响后计算的状态。停一下!将第一个事件序列重新表示为表达式。

(define BACKGROUND (empty-scene 100 100))
(define DOT (circle 3 “solid” “red”))
(define (main y)
(big-bang y
[on-tick sub1]
[stop-when zero?]
[to-draw place-dot-at]
[on-key stop]))
(define (place-dot-at y)
(place-image DOT 50 y BACKGROUND))
(define (stop y ke)
0)

简而言之,事件的顺序决定了big-bang在概念上遍历上表中可能的状态,以到达每个时间段的当前状态。当然,big-bang不会触及当前的状态;它只是保护它,并在需要时将其传递给事件处理程序和其他函数。

从这里开始,定义第一个交互式程序就很简单了。参见上图,该程序由两个常量定义和三个函数定义组成:main,它启动一个big-bang交互式程序;place-dot-at,它将当前状态转换为图像;然后stop,它停止它的输入,得到0。

点击RUN后,我们可以让DrRacket评估这些handler函数的应用。这是确认它们工作的一种方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsNObbms-1618589248467)(media/61464ccdbace22e63dba4a2aac75faf5.png)]

放松一下

到目前为止,你可能觉得前两章是压倒性的。他们引入了许多新的概念,包括一种新的语言,它的词汇,它的意思,它的习语,用这个词汇写下文本的工具以及运行这些程序的方法。面对如此多的想法,您可能想知道当出现问题陈述时如何创建程序。为了回答这个中心问题,下一章将后退一步,明确地讨论程序的系统设计。所以休息一下,准备好了再继续。

3. 如何设计程序How to Design Programs

本书的前几章表明,学习编程需要掌握许多概念。一方面,编程需要一种语言,一种符号来表达我们想要计算的东西。尽管获得一种编程语言与获得一种自然语言有一些相同的元素,但用于构建程序的语言是人工构建的。两者都需要词汇、语法和对短语含义的理解。

另一方面,学习如何从一个问题陈述到一个程序是至关重要的。我们需要确定在问题陈述中哪些是相关的,哪些是可以忽略的。我们需要梳理出程序消耗了什么,产生了什么,以及它如何将输入与输出联系起来。我们必须知道或找出所选语言及其库是否为程序要处理的数据提供了某些基本操作。如果没有,我们可能必须开发辅助函数来实现这些操作。最后,一旦我们有了一个程序,我们必须检查它是否实际执行了预期的计算。这可能会揭示出各种各样的错误,我们需要能够理解和纠正这些错误。

所有这些听起来相当复杂,你可能会想,为什么我们不干脆混过去,到处试验,当结果看起来不错的时候,也不去管它。这种编程方法,通常被称为“车库编程”,很常见,并且在很多情况下都很成功;有时,它是初创公司的起步平台。不过,这家初创公司无法出售在车库工作的成果,因为只有最初的程序员和他们的朋友才能使用这些成果。

一个好的程序会附带一个简短的说明,说明它做什么,它期望什么输入,以及它产生什么。理想情况下,它还能保证它确实有效。在最好的情况下,程序与问题陈述的联系是明显的,因此对问题陈述的一个小变化很容易转化为对程序的一个小变化。软件工程师称之为“编程产品”。

所有这些额外的工作是必要的,因为程序员不为自己创建程序。程序员编写程序供其他程序员阅读,有时,人们运行这些程序来完成工作。大多数程序都是大型的、复杂的协作函数集合,没有人能够在一天内编写所有这些函数。程序员加入项目,编写代码,离开项目;其他人接管他们的程序并在上面工作。另一个困难是程序员的客户往往会改变他们真正想要解决的问题的想法。他们通常几乎是对的,但更多时候,他们会把一些细节搞错。更糟糕的是,像程序这样的复杂逻辑结构几乎总是会出现人为错误;简而言之,程序员会犯错误。最终会有人发现这些错误,程序员必须修复它们。他们需要重新阅读一个月前的节目,一年前,或20年前,并改变它们。

Exercise 33. Research the “year 2000” problem.

在这里,我们展示了一个设计方案,它集成了一个循序渐进的过程和围绕问题数据组织程序的方法。对于那些不喜欢长时间盯着空白屏幕的读者来说,这个设计方案提供了一种系统化的方法。对于那些教别人设计程序的人来说,这个配方是一个诊断新手困难的工具。对其他人来说,我们的配方可能是一些他们可以应用到其他领域的东西,比如医学、新闻或工程。对于那些希望成为真正程序员的人,设计配方还提供了一种理解和工作于现有程序的方法,尽管不是所有程序员都使用这种设计配方的方法来编写程序。本章的其余部分将专门介绍进入设计配方世界的第一步;下面的章节和部分以这样或那样的方式对配方进行了精炼和扩展。

3.1 设计函数Designing Functions

资料与数据
程序的目的是描述一个消耗一些信息并产生新信息的计算过程。从这个意义上说,程序就像数学老师给小学生的指令。然而,与学生不同的是,一个程序不仅仅处理数字,它计算导航信息,查找一个人的地址,打开开关,或者检查视频游戏的状态。所有这些信息都来自于现实世界中通常称为程序域的部分,程序计算的结果代表了该域的更多信息。

信息在我们的描述中起着中心作用。把信息看作是关于程序领域的事实。对于一个处理家具目录的程序来说,一张有五条腿的桌子或者一张2米乘2米的方桌都是信息的片段。游戏程序处理一个不同的领域,在五个可能引用每个时钟周期的像素数量,一些对象旅行途中从画布的一部分到另一个。或者,一个工资单程序可能要处理五项扣除。

一个程序要处理信息,就必须用程序语言把信息转换成某种形式的数据;然后它处理数据;一旦完成,它将结果数据再次转化为信息。交互式程序甚至可以将这些步骤混合在一起,根据需要从世界上获取更多的信息,并在两者之间传递信息

我们使用BSL和DrRacket,让你不用担心把信息翻译成数据。在DrRacket的BSL里,你可以把一个函数直接应用到数据上,观察它产生了什么。因此,我们避免了编写将信息转换为数据或将信息转换为数据的函数的严重的鸡生蛋还是蛋生鸡的问题。对于简单种类的信息,设计这样的程序片段是琐碎的;不是简单的信息,你需要知道的关于解析,例如,立即需要大量的程序设计方面的专业知识。

软件工程师使用“模型-视图-控制器”(MVC)来表达BSL和DrRacket将数据处理与信息解析成数据和数据转换成信息分离开来的方式。事实上,现在人们普遍认为,设计良好的软件系统会强制实现这种分离,尽管大多数介绍性书籍仍然将它们结合在一起。因此,与BSL和DrRacket合作可以让你专注于程序核心的设计,当你有足够的经验时,你可以学习设计信息/数据转换部分。

这里我们使用两个预先安装的教包来演示数据和信息的分离:2htdp/batch-io和2htdp/universe。从本章开始,我们为批处理(batch)和交互(interactive)程序开发设计配方,让你了解如何设计完整的程序。请记住,成熟编程语言的库为完整的程序提供了更多的上下文,您将需要适当地调整设计方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZsUG3o8-1618589248468)(media/316f86f99ea221870c769c801494ef1b.png)]

考虑到信息和数据的中心作用,程序设计必须从它们之间的联系开始。具体地说,作为程序员,我们必须决定如何使用我们所选择的编程语言来表示相关的信息片段,以及如何将数据解释为信息。上图用一个抽象图解释了这个想法。

为了使这个想法具体化,让我们通过一些例子。假设您正在设计一个使用和生成数字形式的信息的程序。虽然选择一个表示是容易的,一个解释需要解释什么数字,如42在领域(domain)表示:

  • 42可指从图像域的上边缘开始的像素数;

  • 42可以表示模拟或游戏对象移动的每个时钟滴答的像素数;

  • 42可能是指一个温度,在物理领域是华氏度、摄氏度或开尔文温标;

  • 42如程序的域是家具目录,可指定某些表的大小;或

  • 42只能计算一个字符串中的字符数。

关键是要知道如何从作为信息的数字到作为数据的数字,反之亦然。

因为这些知识对每个人来说都是非常重要的读取程序,我们经常写评论的形式,我们称之为数据定义。数据定义有两个用途。首先,它使用一个有意义的单词来命名一个数据集合——一个类。其次,它告诉读者如何创建这个类的元素,以及如何决定某个任意数据是否属于这个集合。计算科学家使用“class”来表示类似于“数学集”的东西。

以下是上述一个例子的数据定义:

; A Temperature is a Number.
; interpretation represents Celsius degrees

第一行介绍了数据收集的名称,温度,并告诉我们这个类由所有数字组成。举个例子,如果我们问102是一个温度,你可以回答“是的”,因为102是一个数字,数字温度。类似地,如果我们问“cold”是不是一个温度,你会说“no”,因为没有字符串属于温度。如果我们让你设定一个样本温度,你可能会得到-400。

如果您碰巧知道可能的最低温度近似于图像C,您可能想知道是否可以用数据定义来表示此知识。由于我们的数据定义实际上只是类的英文描述,因此您可以用比这里所示更准确的方式定义温度类。在这本书中,我们使用一种程式化的英语这样的数据定义,和下一章介绍了风格等施加约束式样,诸如“larger
than -274.”.

到目前为止,你遇到的名字四类数据:
Number,
String,
Image, and
Boolean.
。这样,制定一个新的数据定义就意味着为现有的数据形式引入一个新的名称,比如,为数字定义“温度”。即使是这些有限的知识,也足以解释我们设计过程的轮廓。

设计过程
一旦您理解了如何表示输入信息,作为数据并将输出数据解释为信息,单个函数的设计按照一个简单的过程进行:
在这一点上,您可能希望重读序言中关于系统程序设计(Systematic Program
Design
)的部分,特别是图1(figure 1.)。

1. 表达您希望如何将信息表示为数据。一行注释就足够了:

; We use numbers to represent centimeters.

为您认为对程序的成功至关重要的数据类制定数据定义,比如温度定义

2. 写下一个署名、一份目的声明和一个函数头。

函数署名 function
signature
是一种注释,它告诉设计的读者您的函数执行了多少输入,从哪些类中抽象它们,以及生成什么类型的数据。这里分别为函数有三个例子:

  • 输入一个字符串并产生一个数字:

; String
-> Number

  • 输入一个温度并产生一个字符串:

;
Temperature
-> String

正如这个署名所指出的,引入数据定义作为现有数据形式的别名,可以很容易地读取署名背后的意图。

管如此,我们还是建议您暂时不要混淆数据定义。这类名称的激增可能会造成相当大的混淆。需要练习来平衡对新名称的需求和程序的可读性,现在还有更重要的思想需要理解。

  • 输入一个数字、一个字符串和一个图像:

; Number
String
Image ->
Image

停一下!这个函数会产生什么?

目的语句(purpose
statement
)是BSL注释,用一行代码总结函数的目的。如果你对一份目的语句有疑问,就把它写得尽可能简短

这个函数计算什么?(what does the function compute?

你的程序的每个读者都应该理解你的函数计算什么,而不需要阅读函数本身。

多函数程序还应该附有目的声明。实际上,优秀的程序员会编写两种目的声明:一种是为可能需要修改代码的读者编写的,另一种是为希望使用程序但不希望阅读它的人编写的。

最后,头部是一个简单的函数定义,也称为存根。为署名中的每一类输入选择一个变量名;函数的主体可以是来自输出类的任何数据块。这三个函数头与上述三个署名匹配

  • (define
    (f a-string) 0)

  • (define
    (g n) “a”)

  • (define
    (h num str img)
    (empty-scene
    100 100))

参数名称反映了参数所表示的数据类型。有时,您可能希望使用表明参数用途的名称。

在表达目的声明时,使用参数名称来明确计算的内容通常很有用。例如,

; Number String Image -> Image
; adds s to img,
; y pixels from the top and 10 from the left
(define (add-image y s img)
(empty-scene 100 100))

此时,您可以单击RUN按钮并试验该函数。当然,结果总是相同的,这使得这些实验非常无聊。

用一些函数性示例演示署名和目的声明。要构造一个函数示例,请从署名中的每个输入类中挑选一段数据,并确定您希望返回的结果。

假设您正在设计一个计算正方形面积的函数。显然,这个函数消耗了正方形的边长,最好用(正数)来表示。假设你已经按照食谱完成了第一个步骤,你在目的陈述和标题之间添加例子,得到如下:

; Number -> Number
; computes the area of a square with side len
; given: 2, expect: 4
; given: 7, expect: 49
(define (area-of-square len) 0)

下一步是做清单,了解已知条件是什么以及我们需要计算什么。对于简单的函数,我们现在正在考虑,我们知道他们是通过参数给定数据。虽然参数是我们还不知道的值的占位符,但是我们知道函数必须从这个未知的数据中计算它的结果。为了提醒自己这一事实,我们用模板替换函数的主体。我们把“库存”这个词归功于Stephen
Bloch.。

现在,模板只包含参数,所以前面的例子看起来像这样:

(define (area-of-square len)
(… len …))

这些点提醒您,这不是一个完整的函数,而是一个模板,一个对组织的建议。

本节的模板看起来很乏味。一旦我们引入了新的数据形式,模板就变得有趣起来。

现在是编写代码的时候了。一般来说,编码意味着编程,但通常是以最窄的方式,即编写可执行表达式和函数定义。

对我们来说,编码意味着用一个表达式替换函数体,该表达式试图从模板中的各个部分计算目的语句所承诺的内容。以下是完整的area-of-square定义:

; Number -> Number
; computes the area of a square with side len
; given: 2, expect: 4
; given: 7, expect: 49
(define (area-of-square len)
(sqr len))
; Number String Image -> Image
; adds s to img, y pixels from top, 10 pixels to the left
; given:
; 5 for y,
; “hello” for s, and
; (empty-scene 100 100) for img
; expected:
; (place-image (text “hello” 10 “red”) 10 5 …)
; where … is (empty-scene 100 100)
(define (add-image y s img)
(place-image (text s 10 “red”) 10 y img))

图16:设计步骤5的完成

要完成add-image函数,需要做更多的工作:请参见图16。特别地,这个函数需要将给定的字符串转换为图像,然后将图像放置到给定的场景中。

正确设计的最后一步是在前面的示例上测试函数。目前,测试工作是这样的。单击RUN按钮,输入与交互区示例匹配的函数应用程序:

> (area-of-square 2)
4
> (area-of-square 7)
49

结果必须与您期望的输出相匹配;你必须检查每个结果,并确保它等于在设计的示例部分所写的内容。如果结果与预期输出不匹配,考虑以下三种可能:

  • 对于某些示例,您错误地计算和确定了错误的预期输出。

  • 或者,函数定义计算了错误的结果。在这种情况下,您的程序中有一个逻辑错误,也称为bug。

  • 例子和函数定义都是错误的。

当您遇到预期结果与实际值不匹配时,我们建议您首先确认预期结果是正确的。如果是,假设错误是在函数定义中。否则,修复的例子,然后再次运行测试。如果您仍然遇到问题,您可能会遇到第三种情况,这种情况比较少见

3.2手指练习:函数 Finger Exercises: Functions

下面的前几个练习几乎是函数中那些练习的副本,尽管后者使用“定义”一词,而下面的练习使用“设计”一词。这种不同意味着你应该通过设计配方来创建这些函数,而你的解决方案应该包括所有相关的部分。

正如本节的标题所暗示的,这些练习是帮助您内化这个过程的练习。直到这些步骤成为第二天性,不要跳过一个步骤,因为这样做会导致很容易避免的错误。在编程中有足够的空间来处理复杂的错误;我们没有必要把时间浪费在愚蠢的事情上。

Exercise 34. Design the function string-first, which extracts the first
character from a non-empty string. Don’t worry about empty strings.

Answer

; Number -> Number

; if string is not empty

; search the first character of the string

; extract the character

(define (string-first s)

(if (> (string-length s) 0)

(substring s 0 1)

“”))

(string-first “hellow world”)

Exercise 35. Design the function string-last, which extracts the last
character from a non-empty string.

Answer

; Number -> Number

; if string is not empty

; search the last character of the string

; extract the character

(define (string-last s)

(if (> (string-length s) 0)

(substring s (- (string-length s)1))

“”))

(string-last “hellow world”)

Exercise 36. Design the function image-area, which counts the number of
pixels in a given image.

Answer

(require 2htdp/image)

; Number -> image

; computes the pixels of image

; structure the image

(define (image-area w)

(rectangle 5 (/ w 5)“solid” “red”))

(image-area 200)

Exercise 37. Design the function string-rest, which produces a string like
the given one with the first character removed.

Answer

Answer

; Number -> string

; search the first character of the string

; delete the character

; produce the new string

(define (string-rest s)

(substring s 1))

(string-rest “hellow world”)

Exercise 38. Design the function string-remove-last, which produces a string
like the given one with the last character removed.

Answer

; Number -> string

; search the last character of the string

; substring the first to (n-1)

; produce the new string

(define (string-remove-last s)

(substring s 0 (- (string-length s) 1)))

(string-remove-last “hellow world”)

3.3领域知识 Domain Knowledge

很自然地会想知道编写函数主体需要哪些知识。稍微想一想就会知道,这一步需要对程序的域有适当的掌握。事实上,这种领域知识有两种形式:

  1. 来自外部领域的知识,如数学、音乐、生物学、土木工程、艺术等等。因为程序员不可能了解计算的所有应用领域,所以他们必须准备好理解各种应用领域的语言,这样他们才能与领域专家讨论问题。数学是许多领域的交集,但不是所有领域。因此,程序员在与领域专家一起解决问题时必须经常学习新的语言。

  2. 了解所选编程语言中的库函数。当您的任务是转换涉及正切函数的数学公式时,您需要知道或猜测您选择的语言附带一个函数,比如BSL的
    tan.。当您的任务涉及到图形时,理解2htdp/image库的可能性将使您受益。

由于您永远无法预测您将从事的领域,或您将不得不使用的编程语言,因此您必须对周围和适合的任何计算机语言的全部可能性有一个坚实的理解。否则一些编程知识不成熟的领域专家将会接管你的工作。

您可以从您制定的数据定义中识别需要领域知识的问题。只要数据定义使用所选编程语言中存在的类,函数体(和程序)的定义主要依赖于该领域的专业知识。后来,当我们引入复杂的数据形式时,函数的设计需要计算机科学知识。

3.4从函数到程序 From Functions to Programs

并不是所有的程序都由单一的函数定义组成。有些需要多种函数;许多人还使用常量定义。无论如何,系统地设计每个函数总是很重要的,尽管全局常量和辅助函数会稍微改变设计过程。

当您定义了全局常量时,您的函数可以使用它们来计算结果。为了提醒自己它们的存在,您可以将这些常量添加到模板中;毕竟,他们毕竟,它们属于可能对函数定义有贡献的清单。

多函数程序的出现是因为交互式程序自动需要处理按键和鼠标事件的函数,以及将状态呈现为音乐的函数,可能还有更多。即使批处理程序也可能需要几个不同的函数,因为它们执行几个独立的任务。有时问题陈述本身就暗示了这些任务;其他时候,当您在设计某个函数时,您会发现需要辅助函数。

由于这些原因,我们建议保留所需函数列表或希望列表。愿望列表中的每个条目应该由三件事情组成:函数的有意义的名称、署名和目的声明。对于批处理程序的设计,将主要函数放在愿望列表中,开始设计。对于交互式程序的设计,可以将事件处理程序、stop-when函数和场景呈现函数放在列表中。只要清单不是空的,选择一个愿望并设计函数。“愿望清单”这个词是约翰·斯通发明的。

3.5 测试 On Testing

测试很快就变成了一项劳动密集型的工作。虽然在交互领域运行小程序很容易,但是这样做需要大量的机械劳动和复杂的检查。当程序员开发他们的系统时,他们希望进行许多测试。很快,这项工作变得势不可当,程序员开始忽视它。与此同时,测试是发现和防止基本缺陷的第一个工具。草率的测试很快会导致错误百出的函数(即带有隐藏问题的函数),而且错误百出的函数通常以多种方式阻碍项目。

因此,机械化测试而不是手动执行测试是至关重要的。像许多编程语言一样,BSL包括一个测试工具,DrRacket知道这个工具。为了介绍这个测试工具,我们再看一下从程序中转换华氏温度到摄氏温度的函数。定义如下:

; Number -> Number
; converts Fahrenheit temperatures to Celsius
; given 32, expect 0
; given 212, expect 100
; given -40, expect -40
(define (f2c f)
(* 5/9 (- f 32)))

测试函数的示例需要对两个数字分别进行三次计算和三次比较。您可以制定这些测试,并将它们添加到DrRacket的定义区域:

(check-expect (f2c -40) -40)
(check-expect (f2c 32) 0)
(check-expect (f2c 212) 100)

当您现在单击RUN按钮时,您将看到来自BSL的报告,该程序通过了所有三个测试——您没有其他事情要做。

除了让测试自动运行之外,
check-expect表单还会在测试失败时显示另一个优势。要了解这是如何工作的,请更改上面的一个测试,例如,使结果是错误的

(check-expect
(f2c -40) 40)

当您现在单击RUN按钮时,会弹出一个额外的窗口。该窗口的文本解释了三个测试中的一个失败。对于失败的测试,窗口显示三个部分:计算值,函数调用的结果(-40);期望值(40);以及一个指向失败测试用例文本的超链接。

; Number -> Number
; converts Fahrenheit temperatures to Celsius temperatures
(check-expect (f2c -40) -40)
(check-expect (f2c 32) 0)
(check-expect (f2c 212) 100)
(check-expect (f2c -40) 40)
(define (f2c f)
(* 5/9 (- f 32)))

图21:在BSL中进行测试

结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLYrmmGB-1618589248469)(media/18720f5d19bb4b4b32c15c8dfc1f49ea.png)]

您可以将
check-expect
规范置于它们所测试的函数定义之上或之下。当你点击运行时,DrRacket会收集所有的
check-expect
规格,并在所有的函数定义都添加到操作的“词汇表”之后对它们进行评估。图21显示了如何利用这种自由来组合示例和测试步骤。您可以将示例直接转换为测试,而不是将其作为注释写下。当您完成了所有函数的设计后,单击RUN执行测试。如果由于某种原因更改了函数,下一次单击将重新测试函数。

最后但同样重要的是,
check-expect
也适用于图像。也就是说,您可以测试图像生成函数。假设您希望设计render函数,该函数将汽车的图像(称为car)放置到一个名为background的背景场景中。为了设计此函数,您可以制定以下测试:

(check-expect (render 50)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QIDnkflr-1618589251976)(media/b46d59eb86e302d59388e7ea5ba53094.png)])
(check-expect (render 200)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ld9E5qnx-1618589251976)(media/911898338c6632f307391f9c1855bf88.png)])

或者,你也可以这样写: 有关制定测试的其他方法,请参见intermezzo 1

(check-expect (render 50)
(place-image CAR 50 Y-CAR BACKGROUND))
(check-expect (render 200)
(place-image CAR 200 Y-CAR BACKGROUND))

这种替代方法可以帮助您了解如何表达函数体,因此更可取。发展这种表达的一种方法是在互动区进行实验。

因为它是如此有用的DrRacket进行测试,而不是自己手动检查一切,我们立即切换到这种风格的测试书的其余部分。这种形式的测试被称为单元测试,BSL的单元测试框架是专门为新手程序员而调优的。有一天你会切换到其他编程语言;您的首要任务之一将是找出它的单元测试框架。

3.6 设计世界程序 Designing World Programs

世界程序是什么鬼?

现在正式介绍世界程序——

虽然上一章以特别的方式介绍了2htdp/universe库需要键入(require
2htdp/univers),但本节演示了该设计配方如何帮助您系统地创建world程序。本文首先基于数据定义和函数署名对2htdp/universe库进行了简要总结。然后,它列出了世界程序的设计配方。

teachpack期望程序员开发一个表示世界状态的数据定义和一个知道如何为世界的每个可能状态创建映像的函数呈现(render)。根据程序的需要,程序员必须设计响应时钟滴答声、击键和鼠标事件的函数。最后,当当前世界属于国家的子类时,交互式程序可能需要停止;结束?识别这些最终状态。图22以简图和简图的方式说明了这个想法。

; WorldState: data representing the current world (cw)
; WorldState -> Image
; when needed, big-bang obtains the image of the current
; state of the world by evaluating (render cw)
(define (render cw) …)
; WorldState -> WorldState
; for each tick of the clock, big-bang obtains the next
; state of the world from (clock-tick-handler cw)
(define (clock-tick-handler cw) …)
; WorldState String -> WorldState
; for each keystroke, big-bang obtains the next state
; from (keystroke-handler cw ke); ke represents the key
(define (keystroke-handler cw ke) …)
; WorldState Number Number String -> WorldState
; for each mouse gesture, big-bang obtains the next state
; from (mouse-event-handler cw x y me) where x and y are
; the coordinates of the event and me is its description
(define (mouse-event-handler cw x y me) …)
; WorldState -> Boolean
; after each event, big-bang evaluates (end? cw)
(define (end? cw) …)

图22: 设计世界程序的愿望列表

假设您对
big-bang的工作原理有一个基本的了解,您就可以专注于设计世界程序的真正重要的问题。让我们为下面的设计配方构建一个具体的例子:

Sample
Problem
设计一个程序,让一辆汽车在世界画布上从左到右移动,每个时钟三个像素。

对于这个问题陈述,很容易想象域的场景:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KGa0YJZ0-1618589248470)(media/8f25635a0eed5f5f7dd0007775c5545d.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AUQS0sbH-1618589248472)(media/ee83a872dccad36736212d10c16e98a1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cRBMvfzP-1618589248473)(media/911898338c6632f307391f9c1855bf88.png)]

在这本书中,我们经常把交互式Big-bang程序的领域称为“World”,我们把这种设计成为“World
program
”。

World
program
的设计方法,就像函数的设计方法一样,是一种从问题陈述系统地转换为工作程序的工具。它由三个大步骤和一个小步骤组成:

  1. 对于世界上所有那些随着时间而保持不变的属性,并且需要将其呈现为图像,引入常量。在BSL中,我们通过定义指定这些常量。为了World
    program
    的目的,我们区分两种常量:

  2. “物理Physical”常量描述了世界上物体的一般属性,比如物体的速度、颜色、高度、宽度、半径等等。当然,这些常量并不是指物理事实,但有许多与现实世界的物理方面类似。

    在我们的sample
    problem
    中,汽车的车轮半径和车轮之间的距离是这样的“物理”常数:(WHEEL-DISTANCE轮距、轮半径WHEEL-DISTANCE)

(define WIDTH-OF-WORLD 200)
(define WHEEL-RADIUS 5)
(define WHEEL-DISTANCE (* WHEEL-RADIUS 5))

注意第二个常数是如何从第一个常数计算出来的。(注意:常量用大写字母表示!!!

  1. 图形
    Graphical常量是世界上物体的图像。程序将它们组合成代表世界完整状态的图像。(图形常量通常需要计算,而计算往往涉及物理常量和其他图像。)

    这里是图形常数为车轮图像的我们的样本车:

    我们建议你在DrRacket的交互区进行实验,以开发出这样的图形常数。

(define WHEEL
(circle WHEEL-RADIUS “solid” “black”))
(define SPACE
(rectangle … WHEEL-RADIUS … “white”))
(define BOTH-WHEELS
(beside WHEEL SPACE WHEEL))

图形常量通常需要计算,而计算往往涉及物理常量和其他图像。

用注释解释常量定义的含义是一个很好的实践。

下图就是在交互区尝试汽车车身的函数程序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fcw99z9s-1618589248474)(media/bac065a3bbc48e0c741c93a4438aa7b0.png)]

(require 2htdp/image) (require 2htdp/universe) (define WIDTH-OF-WORLD 200) (define WHEEL-RADIUS 5) (define WHEEL-DISTANCE (* WHEEL-RADIUS 5)) (define WHEEL (circle WHEEL-RADIUS “solid” “black”)) (define CAR-BODY (above (rectangle 20 5 “solid” “red”) (rectangle 40 10 “solid” “red”))) (define SPACE (rectangle 10 WHEEL-RADIUS “solid” “white”)) (define BOTH-WHEELS (beside WHEEL SPACE WHEEL)) (define CAR (overlay/xy BOTH-WHEELS -5 -10 CAR-BODY))
Welcome to DrRacket, version 7.8 [cs]. Language: Beginning Student [custom]; memory limit: 128 MB. Teachpack: 2htdp/image.rkt. > CAR [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Al0r0mF-1618589251977)(media/52dad9ab6737d9928cea0103193415b2.png)]
(define BACKGROUND (empty-scene WIDTH-OF-WORLD 30 )) ;;这里的用empty-scene,不能用rectangle (define Y-CAR 20) ;; 变量函数表达式 ; CAR’s poition (define (render x) (place-image CAR (+ 20x) Y-CAR BACKGROUND))
>(render 50) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oN3bdUfZ-1618589251978)(media/4b59eb1dd4b479a4d0259eb40eab0bfb.png)] ;; 需要加半个车长20 >
  1. 这些属性会随着时间而改变——对时钟滴答声反馈、击键或鼠标操作都会改变导致World当前状态的改变。你的任务就是为World(领域)上所有可能的状态开发一个数据呈现。开发产生了一个数据定义,该定义附带一个注释,告诉读者如何将World信息表示为数据,以及如何将数据解释为关于World的信息。

    (这部分大部分是包含解释信息和定义WorldState is a ….)

    选择简单的数据形式来表示世界的状态。

    对于运行示例,随着时间变化的是汽车与左边界的距离。虽然到右边边距的距离也改变了,但是很明显,我们只需要其中一个就可以创建一个图像。距离是用数字来度量的,下面是一个足够的数据定义:

; A WorldState is a Number.
; interpretation the number of pixels between
; the left border of the scene and the car

另一种方法是计算已经过的时钟滴答声的数量,并使用这个数字作为世界的状态。我们将这种设计变体留作练习。

  1. 一旦有了世界状态的数据呈现,就需要设计一些函数,以便形成有效的“Big-bang”表达式。
  • 首先,你需要一个函数将任何给定的状态映射成图像,这样big
    bang就可以将状态序列呈现(render)为图像:

    ; render

  • 接下来,您需要决定哪一种事件应该改变世界状态的哪一方面。根据您的决定,您需要设计以下三个函数中的部分或全部:

; clock-tick-handler
; keystroke-handler
; mouse-event-handler
  • 最后,如果问题陈述建议程序应该在World具有某些属性时停止,则必须进行设计

    ; end?

    有关这些函数的通用署名和目的表达,请参见图22。将这些通用目的表达应用于您解决的特定问题,以便读者知道他们计算了什么。

    简而言之,设计一个交互式程序的愿望自动创建几个初始条目为您的愿望列表。一个一个地解决它们,你就得到了一个完整的World
    program

    让我们完成sample
    program
    的这一步。虽然big-bang规定我们必须设计一个呈现函数,但我们仍然需要弄清楚是否需要任何事件处理函数。既然汽车应该从左到右移动,我们肯定需要一个处理时钟滴答声的函数。因此,我们得到了这个愿望列表:

; WorldState -> Image
; places the image of the car x pixels from
; the left margin of the BACKGROUND image
(define (render x)
BACKGROUND)
; WorldState -> WorldState
; adds 3 to x to move the car right
(define (tock x)
x)

请注意,我们是如何在理解big-bang将如何使用这些函数的情况下,针对手头的问题定制目的声明的。

  1. 最后,需要一个主函数。与所有其他函数不同,World
    program
    的主要函数不需要设计或测试。它存在的唯一原因是你可以方便地从DrRacket的交互区启动你的World
    program

    你必须作出的一个决定涉及到main的论点。对于我们的样本问题,我们选择一个参数:世界的初始状态。我们开始吧:

; WorldState -> WorldState
; launches the program from some initial state
(define (main ws)
(big-bang ws
[on-tick tock]
[to-draw render]))

因此,您可以在交互区启动这个程序

> (main 13)

观察汽车从距左侧13像素处开始。当你关闭大爆炸的窗口时,它就会停止。请记住,big-bang在评估停止时返回世界的当前状态。

自然,您不必对表示世界状态的数据类使用名称“WorldState”。任何名称都可以,只要对事件处理函数的署名使用一致。而且,你不是必须使用名字tock,
end?, 或者 render.
您可以任意命名这些函数,只要在写下大爆炸表达式的子句时使用相同的名称即可。最后,您可能已经注意到,只要首先列出初始状态,就可以以任何顺序列出大爆炸表达式的子句。

现在让我们使用到目前为止所阐明的函数设计配方和其他设计概念来完成程序设计过程的其余部分。

Exercise 39. Good programmers ensure that an image such as CAR can be
enlarged or reduced via a single change to a constant definition. We started the
development of our car image with a single plain definition:

Good programmers establish a single point of control for all aspects of their
programs, not just the graphical constants. Several chapters deal with this
issue.

(define
WHEEL-RADIUS 5)

The definition of WHEEL-DISTANCE is based on the wheel’s radius. Hence, changing
WHEEL-RADIUS from 5 to 10 doubles the size of the car image. This kind of
program organization is dubbed single point of control, and good design
employs this idea as much as possible.

Develop your favorite image of an automobile so that WHEEL-RADIUS remains the
single point of control.

(require 2htdp/image) (require 2htdp/universe) (define WIDTH-OF-WORLD 300) (define WHEEL-RADIUS 10) (define WHEEL-DISTANCE (* WHEEL-RADIUS 5)) (define WHEEL (circle WHEEL-RADIUS “solid” “black”)) (define CAR-BODY (above (rectangle (* 4 WHEEL-RADIUS) WHEEL-RADIUS “solid” “red”) (rectangle (* 8 WHEEL-RADIUS) (* 2 WHEEL-RADIUS) “solid” “red”))) (define SPACE (rectangle (* 2 WHEEL-RADIUS) WHEEL-RADIUS “solid” “white”)) (define BOTH-WHEELS (beside WHEEL SPACE WHEEL)) (define CAR (overlay/xy BOTH-WHEELS (- WHEEL-RADIUS) (- (* 2 WHEEL-RADIUS)) CAR-BODY))
> CAR [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SPJQLvR7-1618589251979)(media/d458fe0553351f302a5e72f32030a42b.png)]
(require 2htdp/image) (require 2htdp/universe) ;; 常量函数表达式(常数项用大写字母) (define WIDTH-OF-WORLD 300) (define WHEEL-RADIUS 5) (define WHEEL-DISTANCE (* WHEEL-RADIUS 5)) (define WHEEL (circle WHEEL-RADIUS “solid” “black”)) (define CAR-BODY (above (rectangle (* 4 WHEEL-RADIUS) WHEEL-RADIUS “solid” “red”) (rectangle (* 8 WHEEL-RADIUS) (* 2 WHEEL-RADIUS) “solid” “red”))) (define SPACE (rectangle (* 2 WHEEL-RADIUS) WHEEL-RADIUS “solid” “white”)) (define BOTH-WHEELS (beside WHEEL SPACE WHEEL)) (define CAR (overlay/xy BOTH-WHEELS (- WHEEL-RADIUS) (- (* 2 WHEEL-RADIUS)) CAR-BODY)) (define BACKGROUND (empty-scene WIDTH-OF-WORLD (* 6 WHEEL-RADIUS) )) (define Y-CAR 20) ;; 变量函数表达式 (define (render x) (place-image CAR (+ 20 x) Y-CAR BACKGROUND)) (define (tock x) (+ x 3)) (define (main ws) (big-bang ws [on-tick add1] [to-draw render])) (main 13)
;;have modified definition:(place-image CAR (+ 20 x) Y-CAR BACKGROUND)

Exercise 40. Formulate the examples as BSL tests, that is, using the
check-expect
form. Introduce a mistake. Re-run the tests.

(require 2htdp/image) (require 2htdp/universe) ;; 常量函数表达式(常数项用大写字母) (define WIDTH-OF-WORLD 300) (define WHEEL-RADIUS 5) (define WHEEL-DISTANCE (* WHEEL-RADIUS 5)) (define WHEEL (circle WHEEL-RADIUS “solid” “black”)) (define CAR-BODY (above (rectangle 20 5 “solid” “red”) (rectangle 40 10 “solid” “red”))) (define SPACE (rectangle 10 WHEEL-RADIUS “solid” “white”)) (define BOTH-WHEELS (beside WHEEL SPACE WHEEL)) (define CAR (overlay/xy BOTH-WHEELS -5 -10 CAR-BODY)) (define BACKGROUND (empty-scene WIDTH-OF-WORLD 30 )) (define Y-CAR 20) ;; 变量函数表达式 ; CAR’s poition ; WorldState: data representing the current world (cw) (define (render x) (place-image CAR x Y-CAR BACKGROUND)) (check-expect(render 50) (place-image CAR 50 Y-CAR BACKGROUND)) (check-expect(render 100) (place-image CAR 100 Y-CAR BACKGROUND)) (check-expect(render 120) (place-image CAR 150 Y-CAR BACKGROUND))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyGkcCEt-1618589251980)(media/dda12353e59e8f32545b08993037dc0b.png)]

Exercise 41. Finish the sample problem and get the program to run. That is,
assuming that you have solved
exercise 39,
define the constants BACKGROUND and Y-CAR. Then assemble all the function
definitions, including their tests. When your program runs to your satisfaction,
add a tree to the scenery. We used

(define tree
(underlay/xy (circle 10 “solid” “green”)
9 15
(rectangle 2 20 “solid” “brown”)))

to create a tree-like shape. Also add a clause to the
big-bang
expression that stops the animation when the car has disappeared on the right
side.

Exercise 42. Modify the interpretation of the sample data definition so that
a state denotes the x-coordinate of the right-most edge of the car.

Exercise 43. Let’s work through the same problem statement with a time-based
data definition:

; An AnimationState is a Number.
; interpretation the number of clock ticks
; since the animation started

Like the original data definition, this one also equates the states of the world
with the class of numbers. Its interpretation, however, explains that the number
means something entirely different.

Design the functions tock and render. Then develop a
big-bang
expression so that once again you get an animation of a car traveling from left
to right across the world’s canvas.

How do you think this program relates to
animate
from Prologue: How to
Program?

Use the data definition to design a program that moves the car according to a
sine wave. (Don’t try to drive like that.)

Exercise 44. Formulate the examples as BSL tests. Click RUN and watch them
fail.

3.7虚拟宠物世界 Virtual Pet Worlds

这个练习部分介绍了一个虚拟宠物游戏的前两个元素。一开始是一只猫不停地在画布上走。当然,所有的散步都会让猫不开心,也会让它不开心。和所有的宠物一样,你可以试着抚摸它们,这有帮助,或者你可以试着喂它们,这有更大的帮助。

Exercise 45. Design a “virtual cat” world program that continuously moves
the cat from left to right. Let’s call it cat-prog and let’s assume it consumes
the starting position of the cat. Furthermore, make the cat move three pixels
per clock tick. Whenever the cat disappears on the right, it reappears on the
left. You may wish to read up on the
modulo
function.

(require 2htdp/image) (require 2htdp/universe) ;; cat-prog (define cat1 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z5whkZ3G-1618589251981)(media/2f42043a506b96a8fb53d4eb6d70d37c.png)]) (define (render x) (place-image cat1 x 100 BACKGROUND)) (define BACKGROUND (empty-scene 300 200)) (define (tock x) x) (define (main ws) (big-bang ws [on-tick add1] [to-draw render])) (main 0)

Exercise 46. Improve the cat animation with a slightly different image:

(define cat2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vIOqvXE5-1618589248475)(media/fb8a9c10337268d8f7c8fb0fb61607c4.png)])

Adjust the rendering function from
exercise 45
so that it uses one cat image or the other based on whether the x-coordinate is
odd. Read up on
odd?
in the HelpDesk, and use a
cond
expression to select cat images.

(require 2htdp/image) (require 2htdp/universe) ;; cat-prog,使用module 求第一个数被第二个数除的余数,差点儿被odd?误导: (define cat1 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1T49TCgG-1618589251982)(media/2f42043a506b96a8fb53d4eb6d70d37c.png)]) (define cat2 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OJB1c8uE-1618589251982)(media/fb8a9c10337268d8f7c8fb0fb61607c4.png)] ) (define (render x) (cond [(= (modulo x 2) 0) (place-image cat1 x 100 BACKGROUND)] [ else (place-image cat2 x 100 BACKGROUND)])) (define BACKGROUND (empty-scene 300 200)) (define (tock x) x) (define (main ws) (big-bang ws [on-tick add1] [to-draw render])) (main 0)

Exercise 47. Design a world program that maintains and displays a “happiness
gauge.” Let’s call it gauge-prog, and let’s agree that the program consumes the
maximum level of happiness. The gauge display starts with the maximum score, and
with each clock tick, happiness decreases by -0.1; it never falls below 0, the
minimum happiness score. Every time the down arrow key is pressed, happiness
decreases by 1/5; every time the up arrow is pressed, happiness jumps by 1/3.

To show the level of happiness, we use a scene with a solid, red rectangle with
a black frame. For a happiness level of 0, the red bar should be gone; for the
maximum happiness level of 100, the bar should go all the way across the scene.

Note When you know enough, we will explain how to combine the gauge program
with the solution of
exercise 45.
Then we will be able to help the cat because as long as you ignore it, it
becomes less happy. If you pet the cat, it becomes happier. If you feed the cat,
it becomes much, much happier. So you can see why you want to know a lot more
about designing world programs than these first three chapters can tell you.
(暂略)

4 间隔、枚举和分项 intervals, Enumerations, and Itemizations

目前,将信息表示为数据有四种选择: numbers, strings, images, and Boolean
values(布尔值)。对于许多问题,这已经足够了,但是对于更多的问题,这四种BSL(或其他编程语言)数据集还不够。实际的设计人员需要额外的方法来将信息表示为数据。

至少,优秀的程序员必须学会在对这些内置集合进行限制的情况下设计程序。限制的一种方法是枚举集合中的一组元素,并说这些是将用于某个问题的唯一元素。枚举元素仅在元素数量有限时才有效。为了适应具有“无限”多个元素的集合,我们引入了区间,它是满足特定属性的元素集合。

定义枚举和间隔意味着区分不同类型的元素。在代码中进行区分需要条件函数,即根据某个参数的值选择不同的计算结果的方法的函数。Many
Ways to
Compute和Mixing
It Up with
Booleans的方法都通过如何编写这类函数的例子来说明。然而,这两个部分都没有使用设计。两篇文章都只是介绍了您最喜欢的编程语言(即BSL)中的一些新构造,并提供了一些如何使用它的示例。Infinite的意思可能是“太大了,枚举元素完全不切实际。”

在这一章中,我们讨论了枚举和间隔的一般设计,数据描述的新形式。我们首先看一下cond表达式。然后我们介绍三种不同的数据描述:枚举、间隔和分项。枚举列出了属于它的每个数据片段,而间隔指定了数据的范围。最后一个是itemizations,混合了前两个定义,在定义的一个子句中指定范围,在另一个子句中指定特定的数据片段。本章以这种情况下的一般设计策略结束。

4.1 条件编程 Programming with Conditionals

回想一下序言中对条件表达式的简要介绍:如何编程。由于cond是本书中最复杂的表达形式,让我们来仔细看看它的大致形状:
方括号使
cond
线突出。使用(…)很好代替[…]]。

(cond
[ConditionExpression1 ResultExpression1]
[ConditionExpression2 ResultExpression2]
… [ConditionExpressionN ResultExpressionN])

cond
表达式以(
cond
,其关键字,以 ) 结尾。遵循关键字,程序员写许多
cond
行需要;每个
cond
行由两个表达式组成,分别用左括号和右括号以[ 和 ] 括起来。

cond
行也称为
cond
子句。下面是一个使用条件表达式的函数定义:

(define (next traffic-light-state)
(cond
[(string=? “red” traffic-light-state) “green”]
[(string=? “green” traffic-light-state) “yellow”]
[(string=? “yellow” traffic-light-state) “red”]))

与 Prologue: How to
Program,中的数学例子一样,这个例子说明了使用
cond
表达式的便利性。在许多问题上下文中,函数必须区分几种不同的情况。使用
cond
表达式,可以为每种可能使用一行代码,从而提醒读者在不同的情况下使用该代码。

**语句要点:**在Mixing It Up with
Booleans中比较
cond表达式和if表达式,后者将一种情况与其他情况区别开来。因此,如果表达方式不太适合多场景的语境;当我们只想说“一个或另一个”时,最好使用它们。因此,当我们希望提醒代码的读者,一些不同的情况直接来自数据定义时,我们总是使用
cond
。对于其他代码段,我们使用任何最方便的构造。


cond
表达式中的条件变得过于复杂时,您偶尔会希望使用类似于“在所有其他情况下”的语句。对于这类问题,
cond
表达式允许在
cond
最后一行使用else关键字:

(cond
[ConditionExpression1 ResultExpression1]
[ConditionExpression2 ResultExpression2]
[else DefaultResultExpression])

如果你犯的错误使用其他一些
cond
线,BSL在DrRacket信号错误:

> (cond
[(> x 0) 10]
[else 20]
[(< x 10) 30])

cond: 找到不是其cond表达式中的最后一个子句的else子句

也就是说,BSL拒绝语法不正确的短语,因为弄清楚这样的短语可能意味着什么没有意义。

设想设计一个函数,作为游戏程序的一部分,在游戏结束时计算一些奖励。下面是它的标题:

; A PositiveNumber is a Number greater than/equal to 0.
; PositiveNumber -> String
; computes the reward level from the given score s

这里有两种不同的版本来进行对比:

(define (reward s)
(cond
[(<= 0 s 10)
“bronze”]
[(and (< 10 s)
(<= s 20))
“silver”]
[(< 20 s)
“gold”]))
(define (reward s)
(cond
[(<= 0 s 10)
“bronze”]
[(and (< 10 s)
(<= s 20))
“silver”]
[else
“gold”]))

左边的变体使用具有三个完整条件的
cond
;在右边,函数带有一个else子句。要表示左边函数的最后一个条件,必须计算(< 20
s)是否成立,因为

  • s是正数

  • (<= 0 s 10)是#false

  • (and (< 10 s) (<= s 20))也计算为#false。

虽然在这种情况下计算看起来很简单,但很容易犯小错误并在程序中引入bug。因此,如果你知道你想要在
cond
中得到之前所有条件的完全相反(称为互补),那么最好将函数定义如右图所示。

4.2 计算条件 Computing Conditionally

通过阅读多种计算方法并将其与布尔值混合在一起,您大致知道DrRacket是如何计算条件表达式的。让我们更精确地复习一下cond表达式的概念。再看看这个定义:

(define (reward s)
(cond
[(<= 0 s 10) “bronze”]
[(and (< 10 s) (<= s 20)) “silver”]
[else “gold”]))

这个函数使用一个数值分数——一个正数——并生成一种颜色。

仅看
cond
表达式,你无法预测三个
cond
子句中的哪一个将被使用。这就是函数的重点。这个函数处理许多不同的输入,例如2、3、7、18、29。对于每一个输入,它可能必须以不同的方式进行。区分不同类型的输入是
cond
表达式的目的。

举个例子

(reward 3)

你知道DrRacket在用参数代替参数后,用函数的身体代替了函数的应用。因此,

(reward 3) ; say “equals”
==
(cond
[(<= 0 3 10) “bronze”]
[(and (< 10 3) (<= 3 20)) “silver”]
[else “gold”])

此时,DrRacket一次只评估一种情况。对于第一个要计算为#true的值,它继续使用结果表达式:

(reward 3)
==
(cond
[(<= 0 3 10) “bronze”]
[(and (< 10 3) (<= 3 20)) “silver”]
[else “gold”])
==
(cond
[#true “bronze”]
[(and (< 10 3) (<= 3 20)) “silver”]
[else “gold”])
==
“bronze”

这里第一个条件成立因为3在0和10之间。

下面是第二个例子:

(reward 21)
==
(cond
[(<= 0 21 10) “bronze”]
[(and (< 10 21) (<= 21 20)) “silver”]
[else “gold”])
==
(cond
[#false “bronze”]
[(and (< 10 21) (<= 21 20)) “silver”]
[else “gold”])
==
(cond
[(and (< 10 21) (<= 21 20)) “silver”]
[else “gold”])

注意,第一个条件这次是如何被计算为#false的,并且正如前面提到的,在许多计算整个
cond
子句的方法中被删除了。其余的计算按预期进行:

(cond
[(and (< 10 21) (<= 21 20)) “silver”]
[else “gold”])
==
(cond
[(and #true (<= 21 20)) “silver”]
[else “gold”])
==
(cond
[(and #true #false) “silver”]
[else “gold”])
==
(cond
[#false “silver”]
[else “gold”])
==
(cond

Racket 学习笔记相关推荐

  1. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  2. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  3. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  4. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  5. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

  6. 知识图谱学习笔记(1)

    知识图谱学习笔记第一部分,包含RDF介绍,以及Jena RDF API使用 知识图谱的基石:RDF RDF(Resource Description Framework),即资源描述框架,其本质是一个 ...

  7. 计算机基础知识第十讲,计算机文化基础(第十讲)学习笔记

    计算机文化基础(第十讲)学习笔记 采样和量化PictureElement Pixel(像素)(链接: 采样的实质就是要用多少点(这个点我们叫像素)来描述一张图像,比如,一幅420x570的图像,就表示 ...

  8. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  9. MongoDB学习笔记(入门)

    MongoDB学习笔记(入门) 一.文档的注意事项: 1.  键值对是有序的,如:{ "name" : "stephen", "genda" ...

  10. NuGet学习笔记(3) 搭建属于自己的NuGet服务器

    文章导读 创建NuGetServer Web站点 发布站点到IIS 添加本地站点到包包数据源 在上一篇NuGet学习笔记(2) 使用图形化界面打包自己的类库 中讲解了如何打包自己的类库,接下来进行最重 ...

最新文章

  1. 四种JOIN简单实例
  2. Mysql VARCHAR(X) vs TEXT
  3. IOS之同步请求、异步请求、GET请求、POST请求
  4. Javascript中的\r\n
  5. 权限操作-springSecurity快速入门-使用自定义页面
  6. k8s实战为aspnetcore.webapi微服务注入配置信息
  7. oracle 创建一揽子协议,Oracle PO - 模块一揽子采购协议小结
  8. GNU C - Using GNU GCC __attribute__ mechanism 01 Function Attribute
  9. 安装版本swf文件转换其他视频格式工具(例:swf to mp4) ,转换后的视频无水印...
  10. leetcode ---双指针+滑动窗体
  11. dell刷sn_戴尔电脑强刷 BIOS 的方法
  12. msxml6_x64 下载
  13. 判断一个整数是否是7的倍数
  14. 微信小程序获取微信用户步数
  15. 吉林省注册公司流程:第一步 企业名称预先核准流程。
  16. 北大核心2020_2020年北大核心论文如何发表
  17. hive尚硅谷实战案例统计youtube视频热度
  18. Liferay的学习
  19. python空间数据处理_基于Python的空间数据批量处理方法
  20. NS2 队列管理机制

热门文章

  1. 计算机专业论文周进展300字,论文周进展怎么写(论文周进展情况记录8篇
  2. 联想用u盘重装系统步骤_练习联想使用u盘重装win7教程
  3. 保密协议与竞业限制协议,在劳动法上有什么相关规定吗?
  4. 好玩Spring之TransactionSynchronization相关的几个类
  5. MySQL时间分区案例
  6. web留言板整蛊网站愚人节
  7. 计算机图形学画简单图形,计算机图形学 基本图形绘制 Koch雪花绘制
  8. 1.2 编程语言选择 | 排行榜、对比、现状,java c++语言对比,哪个工资高、难度更高,mysql数据库对比,java入门怎么学
  9. C#编程学习27: C#操作Excel从入门到精通
  10. 注意力模型(Attention Model)理解和实现