1.4 1.5
pointeur## 4.函数
正如我们之前提到过的,函数是OCaml语言的核心。接下来我们将学习如何创建以及调用函数。
### 创建函数
在OCaml中,我们使用关键词`let`来定义一个函数:
```ocaml
let f x y = x + y
```
为了更好理解代码行的含义,以下是在Python中的相同程序:
```python
def f(x, y):
return x + y
```
在此我们标注几点不同:
- 参数并不写在括号中,而是直接写到函数名后面;
- 参数之间也不用逗号隔开,用空格分隔即可;
- 函数的主体从符号`=`开始;
- 函数主体只包含一个表达式,而不是一系列指令(instruction)。
- 唯一的指令(instruction)永远是函数返回的结果。
这种极简的句法在最初会看起来有些陌生,但我们会很快习惯它,并且当我们在OCaml中更进一步时会发现它很方便(参照附加部分)。
如果在OCaml编译器中输入以下命令,我们将会得到函数的类型:
```ocaml
f
```
我们可以看到这个函数的类型为:`int -> int -> int`。关于这个注释我们需要知道,前两个类型为参数的类型,而最后一个则是返回的值的类型。这里,参数`x`和`y`为`int`,它们的和也为`int`。如果你想了解为什么选择了这样不太直观的注释,我们将会在柯里化这一节中讨论。
一个实用的做法(在学校的考试中也是这样要求的)是明确批注出函数的类型。针对这一点,我们使用句法`(参数:类型)`来代替`参数`。对于返回的值,我们在`=`前使用`:type`来指出其类型。
```ocaml
let f (x : int) (y : int) : x + y
```
这个注释会更长一些,但它的优点是可以明确地显示出不同表达式的类型。同样,这也使得我们更容易理解这个函数,因为我们可以很清楚地看到参数以及返回的值可以取什么样的值。
### 小窍门:乘法的定义
我们也可以一次同时定义几个函数,只要用`and`(注意:与&&无关)来分开它们的声明即可。
```ocaml
let identite x = x and carre x = x * x and cube x = x * x * x
```
但是在此句法中,我们不能使用一行中靠前的函数来定义后面的函数。 比如,我们不能写成这样:
```ocaml
let carre x = x * x and cube x = (carre x) * x
```
因为当我们定义`cube`时,函数`carre`并不存在在环境中(还没有被运行)。
### 调用函数
现在我们拥有了第一个函数(不要犹豫去创建其它函数),来看一下我们是如何调用它的。
```ocaml
f 1 2
```
就这样!通常来讲,编译器将会回复`3`。
如果你需要使用更复杂的参数,那么你可以用括号括起来:
```ocaml
f (4 * 3) 7
```
### 以及变量?
事实上,变量的概念并不存在于OCaml中[^refs]。但是,我们可以有没有参数的函数常量。
```ocaml
let pi = 3.1415
```
此外,我们还可以使用表达式来限制函数,那么就可以仅仅使用本地方式来定义一个函数。对此,在声明完函数之后,我们加入关键词`in`以及一个表达式,这样我们就可以使用刚刚定义好的函数。
```ocaml
let cube x = x * x * x in (cube 2) + (cube 3)
(*在不定义cube的情况下,我们可以这样写:*)
(2 * 2 * 2) + (3 * 3 * 3)
```
通常我们会在`in`之后换行来使得代码更易于阅读。同理我们也可以嵌套`let`和`let/in`:
```ocaml
(*这个函数计算了平面中的范数,已知坐标*)
let norme (X : float) (y : float) : float =
let carre a = a * a in
let x_carre = carre x in
let y_carre = carre y in
sqrt (x_carre + y_carre) (*sqrt是OCaml中的基础函数*)
```
[^refs]: 事实上,这和'引用(reference)'的概念是类似的,但是我们会避免使用它,并且在本教程中也不会涉及。
## 5.课后练习
为了验证你是否已经理解了到目前为止的内容,你可以尝试回答下列问题。如果答对的话,你的回答会从红色变为绿色。
(因为编者是在网页上撰写的,但是掘金上并不支持这样的代码格式,所以译者稍加改动。可以打开开头的源网页来测试。)
下列表达式的类型分别是什么?如果其类型不成立,则输入`错误`。
- 2 + 2
<details>
<summary><答案点我></summary>
int
</details>
- 2e3 +. 2.5 *. (float_of_int 4)
<details>
<summary><点我></summary>
float
</details>
- 'H'
<details>
<summary><点我></summary>
char
</details>
- 12 > 3 && 7
<details>
<summary><点我></summary>
错误
</details>
- "你" ^ "好" ^ "!"
<details>
<summary><点我></summary>
string
</details>
- 42 > 12 || 19 > 38
<details>
<summary><点我></summary>
bool
</details>
- let ajouter x y = x + y
ajouter (*我们询问的是ajouter的类型*)
<details>
<summary><点我></summary>
int -> int -> int
</details>
- 'A' + 3
<details>
<summary><点我></summary>
错误
</details>
- (*我们假设'x'和'y'是之前定义过的常量
*但是我们并没有为其赋值
*并且:这个表达式并不是一个错误类型
*)
x +. (y *. y)
<details>
<summary><点我></summary>
float
</details>
下列的布尔类型表达式是真(`true`)或假(`false`)?
- 42 > 12 || 19 > 38
<details>
<summary><点我></summary>
true
</details>
- 4 * 5 + 2 = 28
<details>
<summary><点我></summary>
false
</details>
- 'a' < 'e'
<details>
<summary><点我></summary>
true
</details>
- let x = 15
x > 2 && x < 17
<details>
<summary></summary>
true
</details>
- "Zoé" > "Alice"
<details>
<summary><点我></summary>
true
</details>
如果你还想要尝试更多的练习,下列是一个真正的习题。
实现下列函数:
- 使一个浮点数`x`与其立方关联的函数;
- 一个有三个参数的函数,其参数分别为`a`、`b`和`ε`,并使其分辩在给定的` ε`条件下,`a`是否等于`b`。为了简化这项任务,我们默认`a`总是大于`b`;
- 该函数的参数为一个字符与一个整数,并确认在ASCII表中,该字符对应的编码是否与这个整数一致。你可以先测试以下字符`A`,其在ASCII表中对应编码为`65`。
<details>
<summary><点我></summary>
```ocaml
let cube (x : float) : float = x * x * x
let egal (a : float) (b : float) (epsilon : float) : bool =
let diff = a -. b in
diff < epsilon
let est_ascii (c : char) (i : int) : bool =
(int_of_char c) = i
```
</details>
当你完成之后,你就可以进入下一章进行学习了。
如果你有很多问题的话,毫无疑问,最好的解决办法是重新阅读一遍第一章的内容,并且自己测试一下所有例子中的代码,然后自己再举一些另外的例子。
如果你觉得有些地方讲的不是很清楚,需要解释的话可以给这个邮箱地址发邮件。
`ocaml @ gelez . xyz`