Scala学习系列-表达式1:for表达式

本篇我们将详细介绍当我们在Scala中使用for表达式时背后的故事。

for表达式的一般式

其中:

  • yield关键字的for表达式我们称为for推导式,不带yield关键字的for表达式我们称为for循环;
  • seq代表的是生成器、值定义、过滤器所组成的序列,它们之间用分号进行分隔,并且seq必须以生成器作为开始元素。

推导式示例:

1
2
for (p <- persons; n = p.name; if (n startsWith "To"))
yield n

或者

1
2
3
4
5
for {
p <- persons // 生成器
n = p.name // 定义
if (n startsWith "To") // 过滤器
} yield n

生成器

值定义

过滤器

for表达式的转换

转换生成器中的模式

在具体介绍转换规则之前,我们先来介绍转换任何一个生成器都需要做的第一步转换操作。我们知道生成器箭号左边是一个模式,对于每个型如:的模式,都会首先被转换为:的形式,它代表的含义是:对生成器的表达式expr进行模式判断,只有跟pat匹配上才会进一步应用后续转换环节;否则该生成器将被忽略,而不会抛出任何MatchError。当任何生成器进行该步转换之后,接着再使用我们后面介绍的规则进行相应的转换。

转换单个生成器的for推导式

表达式:,其中x为变量。

使用map转换生成器:

转换单个生成器的for循环

表达式:,其中x为变量。

使用foreach转换生成器:

转换以一个生成器和一个过滤器开始的for推导式

表达式:,其中x为变量。

首先,使用withFilter转换过滤器:

然后,使用map转换生成器:

转换以一个生成器和一个过滤器开始的for循环

表达式:,其中x为变量。

首先,使用withFilter转换过滤器:

然后,使用foreach转换生成器:

转换以两个生成器开始的for推导式

表达式:,其中x、y为变量,seq为任意的生成器、值定义和过滤的拼接序列。

首先,使用flatMap转换第一个生成器:

然后,按照已经介绍的上述规则继续转换剩余的生成器和seq

转换以两个生成器开始的for循环

表达式:,其中x、y为变量。

转换为:

转换for推导式中的值定义

表达式:,其中x、y为变量,seq为任意的生成器、值定义和过滤的拼接序列。

首先,转换“定义”:,可以看出,每当生成一个新的xexpr2就会被计算一次,所以,如果定义变量的时候没有使用到之前生成器绑定的变量,那么这样的定义最好不要定义在for表达式中。

并非只有集合才支持for表达式

TODO

Notes

本文基于Scala2.12.13

参考

  1. https://scala-lang.org/files/archive/spec/2.12/06-expressions.html#for-comprehensions-and-for-loops
  2. https://docs.scala-lang.org/tour/for-comprehensions.html
  3. 《Scala编程 第3版》23章