【译】【OCaml函数式编程教程】第二章:条件及模式匹配

872 阅读6分钟

II:条件及匹配模式

if.jpg

1. if 和 else

(上面的图片是一枝if(if在法语中是红豆杉的意思),就很有趣。)

和在Python中一样,OCaml里也存在条件结构,关键词同样为ifelse。 但是语法却有些不一样,因为OCaml不使用:来进入条件区块(OCaml中,:一般用来指出类型),但我们会使用关键词then。因此,语法如下所示:

if x then a else b

为了看起来更清楚一点,我们通常会将此表达式分几行书写,并且要使用缩进符:

if x then
    a
else
    b

从这个简单的例子中,我们可以发现:

  • x是一个bool类型的表达式。如果其值为true,则将会返回a。否则,将会返回b
  • a可以是任意类型的表达式。
  • b是另外一个表达式,但其类型要同a一致。 事实上,在OCaml中,所有的表达式都要有一个统一且定义好的类型。 if/else结构也是一个表达式,所以也只能有一种类型。根据这条规则,我们同样不能忽视else:这是必需的。 如果我们不写else,那么我们的表达式将会有两种可能,要么和a有一样的类型,要么没有类型(在OCaml中,记作())。因此,类型就不一致了。

条件作为表达式这一事实,有时而言是很实用的。比如说,我们可以使用条件来定义一个常数:

let number_departement = 38
let name_departement = if number_departement = 38 then
    "Isere"
else
    "Unknown"

更显而易见的方式,我们也可以使用elif来创建等式:else后面的表达式也可以是一个条件:

let number_departement = 38
let name_departement = if number_departement = 38 then
  "Isere"
else if number_departement = 19 then
  "Correze"
else
  "Unknown"

为了更直观地看到else if是如何运作的,我们可以使用括号来重新写一下:

let number_departement = 38
let name_departement = if number_departement = 38 then
  "Isere"
else (
  if number_departement = 19 then
    "Correze"
  else
    "Unknown"
)

这两种写法是等价的,这正是我们使用else if时,OCaml所做的。

match.png

2. 匹配模式

除了if/else之外,OCaml还为我们提供另一种更强有力的创造条件的方式:匹配模式。通常而言是列出所有可能的情况,并将一种情况与一个值联结在一起,这和if/else是有些相像的。 匹配模式有趣的地方就在于,它可以准确地辨认出模式,而不仅仅是简单的等式。

«模式»和一个广义上的等式目前看起来并没有什么区别,但是一旦我们知道了如何创建自己的类型,你就会明白其中的区别了。

OCaml中,匹配模式的语法会使用关键词matchwith

match x with
| case1 -> resultat1
| case2 -> resultat2

x是将要被测试的值。如果这个值与情况列表中的某一项相匹配,那么就会返回这个情况所对应的结果。这些情况都应该有和x相同的类型,并且所对应的不同的结果也只能有一种类型。我们可以继续使用上一节所用的例子:

let number_departement = 38
let name_departement =
  match number_departement with
  | 1 -> "Ain"
  | 19 -> "Correze"
  | 38 -> "Isere"

此处,OCaml将测试我们所提供的的所有的模式(按顺序),一旦找到了同被测试值一样的模式(在这里为number_departement),它就会返回对应的结果。所以,在我们的例子中,name_departement就对应"Isere"

现在,假象上述的例子中35取代了38的位置。这将会发生什么?OCaml将会提示错误,但是将我们运行程序时,而不是编译时(我们称这些错误为异常):

Exception: Match_failure

事实上,它通常会在某些情况下发出警告,因为match并不能包含所有您所列出的情况(伴随着Warning: this pattern-matching is not exhaustive)。为了避免这种情况,我们通常使用一个必然会匹配上的基层情况,以防止之前的匹配都不成功。 按照协议,我们将这种情况记为_

(* 我写了一个函数以便于更好的理解 *)
let name_departement number =
  match number with
  | 1 -> "Ain"
  | 19 -> "Correze"
  | 38 -> "Isere"
  | _ -> "Unknown"

如果我们想要得到刚刚匹配中的数值,我们可以使用标识符并且用匹配的数值«创建»一个新的常数:

let name_departement number =
  match number with
  | 1 -> "Ain"
  | 19 -> "Correze"
  | 38 -> "Isere"
  | others -> "Department number" ^ (string_of_int others)

这样的话,name_departement 35会返回"Department number 35"。 同样,我们也可以将几种不同的情况和同一个结果联结,并使用|来分隔不同的情况:

let region number =
  match number with
  | 1 | 15 | 38 | 63 | 69 -> "Auvergne Rhone-Alpes"
  | 19 | 24 | 33 | 40 | 64 -> "Nouvelle Aquitaine"
  | _ -> "Region Unknown"

##附加:关键词function 正如在本课程的介绍中指出的,这个关键词并不在INF201的课程范围内,但是因为它可能会被用到所以介绍一下。下文中所出现的其它«附加»也是出于同样的情况。

如果您写了一个只有一个参数的函数,并且也只想用该参数匹配。那么您可以使用关键词function,这样可以缩短一些您的代码:

let f = function
  | case1 -> resultat1
  | case2 -> resultat2

这些代码等价于:

let f x =
  match x with
  | case1 -> resultat1
  | case2 -> resultat2

##第二个附加:计时器

这里同样存在一个语句可以使得if和匹配«结合» ,我们可以在一个布尔表达式后使用关键词when

(* 一个为了知道下一辆有轨电车什么时候到来的函数,单位是分钟。
 * (这些数字基本是随机抽取,等车的时候不要依赖这个功能)
 *)
let wait day hour =
  match day with
  | "Monday" | "Tuesday" | "Wednesday" | "Sunday" when hour < 21 -> 5 (* 白天的时候五分钟一班 *)
  | "Monday" | "Tuesday" | "Wednesday" | "Sunday" -> 20 (* 晚上的时候二十分钟一班 *)
  | _ -> 5 (* 其余的日子什么时候都有车, 哪怕是晚上 *)

3. 课后练习

正如在上一章节中一样,我们还有个小测来测试您是否理解了以及还有哪一部分需要巩固一下。

我们观察一下下列函数:

let tariff_concert age =
  if age < 16 then
    5
  else if age > 60 then
    10
  else
    15

下列表达式的数值为多少?

tariff_concert 12
<答案点我>
    5
tariff_concert 38
<答案点我>
    15
tariff_concert 127
<答案点我>
    10
tariff_concert -15
<答案点我>
    5

____中填入合适的语句使得这些函数正确运行。

let greet language =
  match language with
  | "fr" -> "Bonjour"
  | "es" -> "Hola"
  | "de" -> "Hallo"
  | ______ -> "Hello"
  | others -> "I don't speak " ^ others
<答案点我>
"en"|"anglais"|"an"|"english"
let country capital =
  match country with
  | "Germany" -> "Berlin"
  | "Italy" -> "Rome"
  | "Spain" -> "Madrid"
  | "France" -> ______
  | others -> "I don't know the capital of " ^ others
<答案点我>
"paris"|"Paris"

写出下列函数:

  1. 一个计算整数绝对值的函数。
  2. 一个联结其名称与整数的函数。(您不必写完所有数字,十个就足够了)。
<答案点我>
let abs (x : int) : int =
  if x > 0 then
    x
  else
    -x

let write_nombre (x : int) : string =
  match x with
  | 0 -> "Zero"
  | 1 -> "One"
  | 2 -> "Two"
  | 3 -> "Three"
  | 4 -> "Four"
  | 5 -> "Five"
  | _ -> "I don't know how to count beyond 5 :(".