学习目标

  • 能够编写对基本数据类型(包括数字、字符串、图像和布尔值)进行操作的表达式
  • 能够编写常量和函数定义
  • 能够逐步写出简单表达式(包括函数调用)的求值过程
  • 能够使用步进器自动逐步执行表达式的求值过程
  • 能够使用 DrRacket Help Desk 来发现新的基本操作
以下内容涉及到的edX链接均不保证可访问性

Expressions

首先,在打开 DrRacket 并确保顶部工具栏Language > Choose Language打开后,对话框内选择的是Teaching Languages > Beginning Student,点击OK保存。

DrRacket 上方编写代码的部分被称为定义区 (Definitions Area),下方的输出部分则是交互区 (Interaction Area)

我们可以在定义区编写一个简单的表达式:

1
(+ 3 4)

之后点击右上角的Run按钮,就可以在交互区看到其,即7

从这个例子可以看出,Racket 是通过计算表达式来得到的。

Expressions

表达式 (Expression) 是程序中被运算 (Evaluate) 以产生值 (Value) 的元素,语法为(<primitive> <expression>)。例如 (+ 3 4) -> 7(+ 3 (* 2 3)) -> 9(/ 12 (* 2 3)) -> 2

上述表达式中的<primitive>+ * /等,它们被称为基本操作符 (Primitive Operator)

关键的是,数字本身也是表达式。

ps: 以防有人不知道,在大多数编程语言中,/是除法

之后,我们可以选中目前已经写好的表达式们,点击Racket > Comment Out with ";",将你选中的表达式注释掉。

Comment

在 Racket 中,一行代码前的分号;后的所有内容都是注释 (Comment),注释旨在向人传达关于程序的重要信息。Racket 在运行时会忽略这些注释。

ps: 将所有表达式注释掉后,运行将不会有任何值输出

在加减乘除之外,本节还会涉及两个基本操作符,第一个是sqr,即平方;以及sqrt,即开平方。后面的表达式可以传递参数 (Argument) 给这两个基本操作符以将其平方/开平方。

1
2
3
4
5
6
7
(sqr 3)
; 此处 sqr 是基本操作符。3 是参数,同时也是表达式
> 9

(sqrt 16)
; 此处 sqrt 是基本操作符。16 是参数,同时也是表达式
> 4

之后会学到 Parameter 的概念,极容易与 Argument 混淆,这里打个预防针

接下来是课后小练习,我们可以先下载来自edX的 pythag-starter.rkt 文件。将这个文件拖动到 DrRacket 中,或者在上方工具栏File > Open...中选择该文件打开。之后就能看到一个带有题目的代码文件。

题目
题目

你可以选中你想要将其变成注释一部分的表达式们,然后点击Racket > Comment Out with a Box,将它们变成注释块。

Solution

已知直角三角形的两条直角边长,求斜边长度,可用勾股定理解决。

(sqrt (+ (sqr 3) (sqr 4))) -> 5

之后我们可以尝试一下运行(sqrt 2)这个表达式,结果的前面会有#i。这是因为2的平方根是个无理数,Racket 语言会通过附加#i来表示这是一个不精确 (Inexact) 的结果。

Evaluation

上一节提到的都是很简单的表达式,但当我们遇到很复杂的程序代码时,我们需要尝试去理解、分析并计算它们。

对于一个表达式(+ 2 (* 3 4) (- (+ 1 2) 3)),我们知道它的值是14

分析该表达式可以得到它是由以下元素组成的:

  • +: 加法运算符 (Operator)
  • 2, (* 3 4), (- (+ 1 2) 3): 都是参与运算的操作数 (Operand)
  • (+ 2 (* 3 4) (- (+ 1 2) 3)): 整个表达式是一次对基本操作 (Primitive) 的调用 (Call)

ps: 操作数里有两个还能深入分析的表达式,可以同理得到它们的基本操作符、操作数和调用

Primitive Call

Primitive 在这里代指的是诸如+ - * /一类的基本操作,而 Call,即调用则意味着该操作被执行了,所以这个词组在这里更像是描述一个过程。

在计算一个 Primitive Call 时,我们需要随着括号将操作数都算出来,整体运算顺序为从左至右,从里至外,具体运算顺序如下:

  1. (+ 2 (* 3 4) (- (+ 1 2) 3))
  2. (+ 2 12 (- (+ 1 2) 3))
  3. (+ 2 12 (- 3 3))
  4. (+ 2 12 0)
  5. 14

Strings and Images

这一节会带来两类新的值 (Primitive Value),即字符串 (String)图像 (Image),同时也会围绕这两类值进行一些操作。

字符串形如生活中的句子、单词等,在描述一串字符的时候,我们通常用"some words"来表示。出现在字符串两端的应当是双引号,而其内部则是真正要表达的一串字符。

可以尝试在 DrRacket 中执行一行"Apple",它的输出结果也仅仅是一行"Apple"

我们可以围绕字符串做一些操作,比如string-append,这个操作可以将所有字符串拼接在一起。

1
2
3
4
(string-append "Hello" "World")
> "HelloWorld"
(string-append "Hello" " " "World") ; 多少参数都是可以用的
> "Hello World"

从字符串的定义要求我们知道,它需要两个双引号来包裹自己,所以可以以此清晰的发现123"123"的区别(一个是数字,一个是字符串)。

数字的一些操作,比如(+ 1 123),我们知道其值是124。但当你尝试(+ 1 "123")的时候,会发现下方的交互区出现了一个异常/错误 (Exception),这类异常十分常见于将数字和字符串弄混的时候。

+: expects a number, given “123”

第二个有关字符串的操作就是获取其长度,即string-length,可以试一下诸如(string-length "hello")看看其值是什么。

第三个操作是substring,它的意思是从字符串中截取一段字符串,我们看看以下例子:

1
2
3
(substring "Caribou" 2             4)
; ↑ 字符串 ↑ 起始位置 ↑截止位置(不包含)
> "ri"

如果是第一次接触编程,可能会疑惑其值为什么不是"ar",这是因为在绝大多数的编程语言中,索引/位置 (Index) 都是从0开始算的,所以在这个字符串中,所有字符的位置如下:

1
2
"Caribou"
;0123456

以上就是字符串的相关内容,Racket 也对作图做了支持,本节会涉及一种。

为了能方便地操作多个代码文件,我们可以在 DrRacket顶部工具栏中的File > New Tab来创建新标签页。

在编辑区中,先输入(require 2htdp/image),这一行意为从2htdp这个地方引入image相关的函数(近似于 Operator)

然后我们在第二行输入(circle 10 "solid" "red"),点击运行,就可以在交互区发现一个红色的实心圆,以此可以得知这一行的意思是创建一个半径为10的红色实心圆。

还可以输入(rectangle 30 60 "outline" "blue"),得到一个高60宽30的蓝色空心矩形。

除了图形之外,也可以渲染文字作为图形:(text "hello" 24 "orange"),得到一个字号为24的橙色Hello。

接下来我们围绕图形做一些操作,尝试如下代码:

1
2
3
(above (circle 10 "solid" "red")
(circle 20 "solid" "yellow")
(circle 30 "solid" "green"))
垂直堆叠
垂直堆叠

运行后,就能在交互区发现三个不同颜色、不同大小的圆垂直堆叠在了一起。那么如何将它水平排列

1
2
3
(beside (circle 10 "solid" "red")
(circle 20 "solid" "yellow")
(circle 30 "solid" "green"))
水平排列
水平排列

或是将它们堆在一起,逐层覆盖:

1
2
3
(overlay (circle 10 "solid" "red")
(circle 20 "solid" "yellow")
(circle 30 "solid" "green"))
堆在一起,逐层覆盖
堆在一起,逐层覆盖

Constant Definitions

在这一节,我们会涉及到常量 (Constant) 的概念。它与之前我们接触到的数字、字符串等很像,但它的特性是:自它被定义之初,之后就不会改变。

或许在其他编程语言的教程中,初学者会先接触到变量的概念,然后再与常量对比。但在这里,我们会先接触常量,一个或许更接近现实生活的概念。

ps: 其他语言中的常量很可能和这里不太一样,这里的常量更像是 只读的变量

比如Π (≈3.1415) 这个众所周知的常量,它自定义以来就不会变。在程序中也是一样,我们也可以定义一个不变量,并赋予一个名字。

接上一节绘图部分,我们可以定义如下:

1
2
3
(require 2htdp/image)
(define WIDTH 400) ; 注意:常量的名字应当是大写的,当然它可以由很多字符组成
(define HEIGHT 600) ; 常量之后跟的是它的值

正如我们在写数学公式时,涉及有关圆、球一类的运算,可能会将Π写上一样,我们也可以在代码中用它们的名字来体现它的值:(* WIDTH HEIGHT) -> 240000

常量定义的语法是:(define <name> <expression>)

或许会发现一些有意思的地方,由于定义常量时最后的参数是<expression>,我们在定义时并不一定需要填一个确定的数、字符串进去,诸如1/2一类的表达式也是可以的。

所有的值都是表达式。

我们可以从 HtDP 找到一只猫,如图:

之后将这只猫的图片复制粘贴到 DrRacket 中(define CAT <图片粘贴处>)这里。

ps: 这编辑器能塞图片属实惊到我了

小猫
小猫

之后我们就可以对这只猫进行一些绘图相关的操作了,比如让它旋转-10°: (rotate -10 CAT),点击运行后,就可以在交互区看到一只斜着的猫。

再让它旋转10°: (rotate 10 CAT),就能再看到另一只斜着的猫。

我们也可以各自赋予他们一个名字,比如向右倾斜的猫是RCAT,向左倾斜的猫是LCAT:

1
2
(define RCAT (rotate -10 CAT))
(define LCAT (rotate 10 CAT))

这两只猫就被存在了两个常量中,它们不再是直接的 (图片),而是被名字代指的表达式/值

这一节非常非常重要,不亚于小时候第一次学到 用字母表示数 对数学思想的改变 —— 用名字表示表达式

很多时候我们编写的代码并不能像前几节一样一句推出结果,而是好几步。中间的运算结果需要我们通过define来存储并使用。

Function Definitions

这一节会引入一个新的概念 —— 函数。这个概念在数学学习中早已耳熟能详,在计算机里会发现其功能和数学当中的函数很像。

再开始之前,先下载edX 的 function-definitions-starter.rkt 文件,并在 DrRacket 中打开。代码如下:

function-definitions-starter.rkt
1
2
3
4
5
6
7
(require 2htdp/image)

;; function-definitions-starter.rkt

(above (circle 40 "solid" "red")
(circle 40 "solid" "yellow")
(circle 40 "solid" "green"))

该代码文件执行后,应当是形同红绿灯一样的三个圆出现在交互区。

我们观察三行绘图代码,会发现有一些地方是重复出现的,而只有最后的字符串是会变的。这里就能用到函数的第一个功能,削减代码冗余 (Redundancy)

该如何做到这一点呢?回顾曾经学过数学意义上的函数:f(x) = 2*x,如果x=2,结果是4;如果x=6,结果就是12

编程里的函数和这一过程很像:

  • 可以重复使用
  • 可以更改传入的值以得到不同的结果

在 Racket 语言中,函数定义语法如下:

1
(define (<function-name> <parameter> <body>))

我们在代码后添加如下函数:

1
2
(define (bulb c)  ; bulb 是函数名,c 是参数
(circle 40 "solid" c)) ; 函数体

在这个函数里,c就代表着自变量。之后我们就可以再写一行(bulb "purple")调用该函数,运行后就会多出一个紫色的圆。

之后,一开始的那三行代码都可以被这个函数简化掉,变成如下最终效果相同的代码:

1
(above (bulb "red") (bulb "yellow") (bulb "green"))

以后使用函数就和用其他学过的基本操作符 (如string-append) 一样了。举个例子,string-append 的作用是拼接字符串,如(string-append "re" "d")的值是字符串"red"。我们就可以将(bulb "red")变成等效的(string-append "re" "d"),层层嵌套。

Booleans and if Expressions

生活中有许多答案是对或错的问题,回答它们或许很简单,但也很具有决定性。对于 Racket 和其他编程语言也一样,对或错的答案会影响程序后续运行的走向。

这一节会引入布尔 (Boolean) 的概念,如果你是第一次听到这个词的话,现在只需要知道它和数字、字符串等概念并列(它也是个类型);但它与其它类型的不同在于,它只有两个可能的值:真 (True) 与 假 (False)

在 Racket 中,这两个布尔值的表述为truefalse注意小写。和数字、字符串等一样,可以直接在 DrRacket 中运算。在编辑器中单输入两行truefalse,交互区就会出现一行true和一行false

没有问题只有答案十分无趣。让我们在编辑器中定义WIDTH100HEIGHT100

1
2
(define WIDTH 100)
(define HEIGHT 100)

接下来提问:WIDTHHEIGHT大吗?

如何让 Racket 回答问题呢?在数学中我们知道比较两个数的大小可以用包括但不限于> < =等符号,在 Racket 中也一样:

1
(> WIDTH HEIGHT)  ; 使用大于号判断

当然,也可以试试其他的基本操作符,比如(= WIDTH HEIGHT)(>= WIDTH HEIGHT)之类的值都是true

让这种基本操作符或者函数运算得到truefalse的行为被称为断言(Predicate/Assert)

字符串的逻辑运算也可以使用,比如(string=? "foo" "bar") -> falsestring=?可以比较两个字符串是否相同。

同样的,图像的一些属性也可以拿来比较,比如两个图像的宽度(使用image-width,故image-height同理):

1
2
3
4
5
6
(require 2htdp/image)

(define I1 (rectangle 10 20 "solid" "red"))
(define I2 (rectangle 20 10 "solid" "blue"))

(< (image-width I1) (image-width I2))

布尔促成了程序控制流 (Control Flow) 的存在,我们可以借助布尔让程序的运行结果不再一成不变。在 Racket 中,可以借助if表达式让程序运行出现分支,其语法如下:

1
2
3
4
(if <expression>  ; 一个值为布尔的条件
<expression> ; 如果布尔值为 true,执行这个表达式
<expression> ; 如果布尔值为 false,执行这个表达式
)

保留之前的代码,让我们来尝试一下if表达式:

1
2
3
4
5
6
7
8
(require 2htdp/image)
(define I1 (rectangle 10 20 "solid" "red"))
(define I2 (rectangle 20 10 "solid" "blue"))

(if (< (image-width I1) (image-width I2)) ; 该表达式的值是布尔类型的
"tall" ; 如果 I1 的宽度小于 I2 的,那么输出 tall
"wide" ; 否则输出 wide
)

ps: 如果第一个表达式,即条件,值不是布尔类型的,会报错,可以试试

当然,(if true "true" "false") -> true(if false "true" "false") -> false


本节的最后一个概念是逻辑运算符,即 (And) (Or) (Not) 等等,它们很重要,因为单纯一个逻辑运算符在很多时候难以表达我们的意思,比如:

  • 当你的银行账户密码输入正确 你的账户内资金足额,那么你就可以取钱。
  • 如果你考到托福 110 分 你在英语国家生活4年以上,那么你就可以免除英语要求
  • 世上有两种人,一类是懂二进制的人,一类是 不懂 二进制的人。

回顾刚刚我们留下的两行图像定义,如果我们需要同时比较I1I2的高与I1I2的宽:

1
2
(> (image-height I1) (image-height I2))
(< (image-width I1) (image-width I2))

将它们用and的逻辑关系运算,得到最终的值,我们可以写:

1
2
(and (> (image-height I1) (image-height I2))
(< (image-width I1) (image-width I2)))

and接受两个表达式,且两个表达式的值只能是布尔类型,其运算过程如下:

1
2
3
4
5
6
7
8
9
10
; Step 1
(and (> (image-height I1) (image-height I2))
(< (image-width I1) (image-width I2)))
; Step 2
(and true
(< (image-width I1) (image-width I2)))
; Step 3
(and true true)
; Step 4
true

短路机制

观察代码的第二、三步,你会发现and似乎没有同时将两个表达式运算出来,而是有先后的。

这个现象在各编程语言都存在 —— 考虑到性能问题,它们会先看前面的表达式的值是不是false如果是的话就不需要运算后面的表达式了,因为对于and来说,它需要两个表达式同时为true,所以只要有一个不是trueand运算就可以立即终止了。

这一现象在ornot这类需要将所有表达式都得运算一遍的逻辑运算符身上不存在。

Using the Stepper

在介绍步进器 (Stepper) 之前,先下载来自edX的 stepper-starter.rkt 文件,即:

stepper-starter.rkt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(require 2htdp/image)

;; stepper-starter.rkt

(+ (* 3 2) 1)



(define (max-dim img)
(if (> (image-width img) (image-height img))
(image-width img)
(image-height img)))


(max-dim (rectangle 10 20 "solid" "blue"))

这一大段代码看着很困惑,可以先运行下看看会输出什么:720

大多数时候,我们可以逐句分析代码的过程并在脑海中构思,但对于一些复杂的情况,我们需要借助 Racket 自己的工具来帮我们还原代码执行过程。

我们点开Run按钮左边的Step,会有一个新窗口弹出:

Stepper 窗口 description
Stepper 窗口 description

在窗口的左边是Racket 正在运算的东西,右边则是Racket 对这一步的运算结果

比如第一步,左边的绿色部分会运算成右边的紫色部分。点击上方的Next就可以让 Racket 执行下一步运算,显然,这一行表达式的最终值是7

我们直接到了最后一行,即(max-dim (rectangle 10 20 "solid" "blue")),里面的rectangle在右边会被运算成一个小蓝色矩形。

再下一步,神奇的事情出现了,上面对max-dim的定义内容全部都复制到了刚刚的蓝色矩形上 —— max-dimimg都被替换成了蓝色矩形,参与运算。

之后,Racket 开始处理if表达式的条件,这一行的image-widthimage-height变成了1020。然后(> 10 20)的值显然是false,最后就走向了第二个表达式 —— 算蓝色矩形的高度,值为20,并输出到交互区。

程序结束了,如果对于上面的步骤不太清楚,可以点击Previous来返回上一步。

Stepper 让我们能更清晰的知道 Racket 是如何一步步处理我们的程序的,这在学习该语言中帮助很大。

ps: 在其他语言当中,类似的功能是 Debugger,即调试器

Discovering Primitives

在前面的小节中,许多基本操作符是渐进地出现的:

  • 在学习数字时,同时认识到+ - * /
  • 在学习字符串时,我们知道用string-append等来操作它们
  • 在学习图像时,image-widthimage-height可以获取它们的宽高
  • 在学习布尔运算时,我们接触了< > =and or not

但教学内容是有限的,Racket 语言自带的基本操作符还有很多很多,我们该如何接触那些从未认识过的基本操作符呢?

其中一个方法是:。很多时候,编程语言的标准是一致的,可以基于它的命名规范来猜测,比如在之前的图像一节中,我们学过rectanglecircle来绘制,比如:

1
2
3
4
5
(require 2htdp/image)

(overlay (circle 10 "solid" "red")
(circle 20 "solid" "yellow")
(circle 30 "solid" "green"))

那么我们就可以合理猜测:triangle是否存在?假设它存在,那么对于一个三角形来说,它也许需要尺寸、是否实心、颜色什么的:(triangle 40 "solid" "purple")

将这一行代码放进编辑器中运行发现,交互区竟然真的出现一个紫色三角形。但疑惑的事情出现了,什么是三角形的尺寸?

接下来让我们把鼠标指针放在triangle上:

  • 如果你是 Windows 系统,请右键
  • 如果你是 MacOS 系统,请摁住Ctrl的同时按下鼠标

在弹出的框中,点击Search in Help Desk for "triangle",之后会出现一个网页。

Racket 官方文档查阅
Racket 官方文档查阅

从之前的图像绘制中我们知道这一切都是因为我们导入了2htdp/image这个库,让我们点击上图中的第一行:triangle provided from 2htdp/image,进入到详情页:

Racket 官方文档 - Polygons - Triangle
Racket 官方文档 - Polygons - Triangle

我们观察文档上描述triangle的语法,会发现第一个参数的意思是side-length,即边长。这下破案了,我们刚刚填的40实际上是指三角形的边长。


接下来是第二种探究方法,我们先开个新标签页,填入:(/ 3 4),根据之前的学习,当然知道它的值是0.75

在一些场景中,我们需要知道一次除法运算结果四舍五入 (Round) 后的值,该去哪找呢?

我们再次以同样方式,点击/Search in Help Desk for "/",选中网页内的/ provided from lang/htdp-beginner

里面有大量的基本操作符,我们需要极具耐心地在这一页寻找四舍五入的写法。可以借助浏览器的全局搜索(一般来说可以按下 Ctrl + F ),然后输入round。之后我们就会被定位到round部分的所在位置。

四舍五入的写法:(round <real number>),可以直接复制下来到 DrRacket 尝试,(round (/ 3 4)) -> 1

借助文档是学习并深入了解一门编程语言几乎最重要的方式。

Practice Problems

这一章的 Recommended Problems:

BSL P1 - More Arithmetic Expressions 题解

预计耗时:5 min / 简单

这道题让我们用两个表达式算出3, 5, 7的乘积。

第一个表达式应当为简单的三个数相乘,我们需要知道*是可以接受不止2个参数的:

1
(* 3 5 7)

第二个表达式即限制了*只接受两个参数,让我们嵌套着写,先算3*5,再算15*7

1
(* 7 (* 3 5))

BSL P3 - Tile 题解

预计耗时:5 min / 简单

这道题是让我们还原一个蓝黄矩形,我们可以考虑使用abovebeside拼凑。为了方便,我们先定义一个黄色正方形和蓝色正方形:

1
2
(define BLUE (rectangle 20 20 "solid" "blue"))
(define YELLOW (rectangle 20 20 "solid" "yellow"))

之后会发现这个矩形的拼凑逻辑可以是:

示意图
示意图

实现代码如下:

1
2
3
4
5
(require 2htdp/image)
(define BLUE (rectangle 20 20 "solid" "blue"))
(define YELLOW (rectangle 20 20 "solid" "yellow"))

(above (beside BLUE YELLOW) (beside YELLOW BLUE))

BSL P5 - Compare Images 题解

预计耗时:7 min / 简单

这道题让我们对图像进行三次带有逻辑运算的比较。

第一个:判断IMAGE1是否比IMAGE2高?

1
(> (image-height IMAGE1) (image-height IMAGE2)) 

第二个:判断IMAGE1是否比IMAGE2窄 (宽度小)?

1
(< (image-width IMAGE1) (image-width IMAGE2)) 

第三个:判断IMAGE1IMAGE2的宽高是否都相同?

1
2
3
(and (= (image-height IMAGE1) (image-height IMAGE2)) 
(= (image-width IMAGE1) (image-width IMAGE2))
)

BSL P6 - More Foo Evaluation 题解

预计耗时:7 min / 简单

比较讨厌的逐次人脑运算,从(foo (+ 3 4))开始:

  • 从运算顺序来讲,(+ 3 4)先被算出为7,得到(foo 7)
  • 之后它调用了foo函数,那么我们就把函数的内容(即(* n n))复制到下面,并把7填进去,得到(* 7 7)
  • 最后计算(* 7 7)得到49

结果为:

1
2
3
4
5
6
7
(foo (+ 3 4))

(foo 7)

(* 7 7)

49

BSL P15 - Function Writing 题解

预计耗时:5 min / 简单

还是讨厌的逐次人脑运算,从(foo (substring "abcde" 0 3))开始:

  • 从运算顺序来讲,(substring "abcde" 0 3)先被算出为"abc",得到(foo "abc")
  • 之后它调用了foo函数,那么我们就把函数的内容复制到下面,并把"abc"填进去,得到:
1
2
3
(if (string=? (substring "abc" 0 1) "a")
(string-append "abc" "a")
"abc")
  • 这是个if表达式,我们先计算它的条件,即(string=? (substring "abc" 0 1) "a"),得到:
1
2
3
(if (string=? "a" "a")
(string-append "abc" "a")
"abc")
  • 这个条件的值为true,故:
1
2
3
(if true
(string-append "abc" "a")
"abc")
  • 那么我们就可以提取出if表达式条件为true时所运行的表达式了,即(string-append "abc" "a")
  • 它的值是"abca"

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(foo (substring "abcde" 0 3))

(foo "abc")

(if (string=? (substring "abc" 0 1) "a")
(string-append "abc" "a")
"abc")

(if (string=? "a" "a")
(string-append "abc" "a")
"abc")

(if true
(string-append "abc" "a")
"abc")

(string-append "abc" "a")

"abca"

BSL P16 - Foo Evaluation 题解

预计耗时:15 min / 中等

这道题需要自定义一个函数:接受两个数,返回较大的数。也就是说这道题需要用到if表达式,那么我们可以先构思这个条件判断:

1
2
3
4
(if (> a b)  ; 判断 a 是否大于 b
a ; 大于就输出 a
b ; 否则就输出 b
)

之后我们将这个if表达式放进一个define里面,将该函数命名为foo (其他什么的也可以)

1
2
(define (foo a b)
(if (> a b) a b))