1. 简介

在本教程中,我们将深入探讨 Scala 中的 for 循环及其丰富的功能特性。

2. for 循环的基本使用

for 循环本质上是一种控制流语句,用于重复执行一段代码。当我们需要多次执行某段逻辑时,它非常有用。

其基本语法如下:

for ( receiver <- generator ) {
    statement
}

其中:

  • receiver 是每次迭代中从 generator 接收新值的变量;
  • generator 通常是一个 Range(范围)Collection(集合)Map(映射),我们稍后会介绍更高级的用法。

2.1. 使用 Range 控制循环次数

如果想控制循环的次数,可以使用 Range,同时还能获取到循环计数器。

Range 是一个有序的整数序列,由起始值和结束值定义:

val range = 1 to 3
val rangeUntil = 1 until 3
  • to 表示包含结束值的范围:
for (num <- range) {
    println(num)
}

输出结果为:

1
2
3
  • until 表示不包含结束值的范围:
for (num <- rangeUntil) {
    println(num)
}

输出结果为:

1
2

每次迭代时,变量 num 会依次接收 Range 中的下一个值,直到范围结束。

2.2. 多个生成器(Multiple Generators)

在 Java 中,如果要嵌套循环,通常需要写多层 for 循环。但在 Scala 中,我们可以在一个 for 中使用多个生成器,简洁又高效:

for {
    i <- range
    j <- rangeUntil
} {
    println (s"$i, $j")
}

输出结果如下:

1, 1
1, 2
2, 1
2, 2
3, 1
3, 2

⚠️ 注意:这里我们使用了大括号 {} 而不是小括号 (),这样可以省略分号 ; 分隔不同的生成器。

每增加一个生成器,就相当于增加一层嵌套循环。最终的迭代次数为 range.size * rangeUntil.size = 6

2.3. 遍历 Collection

对任何 Collection 进行遍历,语法也是一样的:

val colorList = Seq("R", "G", "B")
for (color <- colorList) {
    println(color)
}

输出:

R
G
B

我们再举个例子:使用多个生成器,生成所有由 "R", "G", "B" 组成的三字母组合:

for (c1 <- colorList; c2 <- colorList; c3 <- colorList) {
    print(s"$c1$c2$c3 ")
}

输出结果为:

RRR RRG RRB RGR RGG RGB RBR RBG RBB GRR GRG GRB GGR GGG GGB GBR GBG GBB BRR BRG BRB BGR BGG BGB BBR BBG BBB

但如果我们要避免像 "RRR" 这样的重复组合,就需要引入守卫(Guard)。

2.4. for 循环中的守卫(Guard)

守卫就是 for 循环中的条件判断语句。

我们可以在循环中加入守卫来过滤掉不满足条件的组合:

for {
    c1 <- colorList
    c2 <- colorList
    if c2 != c1
    c3 <- colorList
    if c3 != c2 && c3 != c1
} {
    print(s"$c1$c2$c3 ")
}

输出结果为:

RGB RBG GRB GBR BRG BGR

✅ 每次 c2 获取新值时,都会与 c1 比较,若相等则跳过后续迭代;
✅ 我们可以在一个 for 中使用多个守卫,条件可以任意复杂。

2.5. 遍历 Map

遍历 Map 时,接收变量会接收到一个包含 key 和 value 的二元组(Tuple):

val map = Map("R" -> "Red", "G" -> "Green", "B" -> "Blue")
for ((key,value) <- map) {
    println(s"""$key is for $value""")
}

输出:

R is for Red
G is for Green
B is for Blue

如果 Map 的值是 List,我们也可以使用多个生成器来展开:

val deck = Map("♣" -> List("A", "K", "Q"),
               "♦" -> List("J", "10"),
               "♥" -> List("9", "8", "7"),
               "♠" -> List("A", "K", "J", "6"))

通过一个 for 循环打印所有牌:

for {
    (suit, cardList) <- deck
    card <- cardList
} {
    println(s"""$card of $suit""")
}

输出:

A of ♣
K of ♣
Q of ♣
J of ♦
10 of ♦
9 of ♥
8 of ♥
7 of ♥
A of ♠
K of ♠
J of ♠
6 of ♠

2.6. 使用 yield 的 for 循环

前面的 for 循环只是执行语句,但如果我们想把每个元素转换成新的值,就需要使用 yield 关键字。

语法如下:

val result = for ( generator ) yield {
    yield_statement
}

yield 会将每次执行的结果收集起来,形成一个新的集合;
✅ 我们可以将结果赋值给变量,供后续使用。

示例:将数字列表转换为字符串表达式:

val numberList = List(1, 2, 3)
val equation = for (number <- numberList) yield {
    s"""$number + $number = ${number + number}"""
}

结果为:

equation: List[String] = List(1 + 1 = 2, 2 + 2 = 4, 3 + 3 = 6)

3. for-comprehension(for 推导式)

for + yield 的组合在 Scala 中被称为 for-comprehension(for 推导式)
✅ 它是 mapflatMap 的语法糖。

只要某个容器类型提供了 map 方法,就可以用于 for-comprehension;
如果使用多个生成器,还需要提供 flatMap 方法。

在 Scala 中,大多数集合类型(List、Map 等)都实现了这两个方法。

在范畴论中,这类容器被称为 Monad(单子)。Scala 中常见的 Monad 还包括:OptionEitherFuture 等。

Option 为例:

val someIntValue = Some(10)
val someStringValue = Some("Ten")

使用 for-comprehension:

val result = for {
    intValue <- someIntValue
    stringValue <- someStringValue
} yield {
    s"""$intValue is $stringValue"""
}

结果为:

result: Option[String] = Some(10 is Ten)

这等价于:

val result = someIntValue.flatMap(intValue => someStringValue.map(stringValue => s"""$intValue is $stringValue"""))

4. 副作用(Side-Effects)

如果循环中的代码修改了外部状态(如打印、修改变量等),我们就说它产生了 副作用(Side-Effect)

例如,前面的 println 就是副作用,因为输出会影响程序外部状态。

而纯函数式编程中,我们更推崇的是 纯迭代(Pure Iteration):即只对元素进行映射,不改变外部状态。

⚠️ 混合使用副作用和纯迭代会让代码难以调试和测试;
⚠️ 带副作用的迭代也往往可读性较差。

5. 总结

本教程我们深入讲解了 Scala 中的 for 循环及其扩展用法:

for 循环支持 Range、Collection、Map 等多种数据结构;
✅ 可以使用多个生成器、守卫来控制流程;
✅ 结合 yield 可以实现强大的数据转换(for-comprehension);
✅ for-comprehension 是 mapflatMap 的语法糖,适用于所有 Monad 类型;
✅ 要注意副作用和纯迭代的区别,合理使用可提升代码质量。

示例代码可以在 GitHub 找到,建议重点关注测试中如何处理副作用。


原始标题:For Loops in Scala