0Pricing
Scala for Backend Engineering & Functional Programming · 课时

展开 for

map 与 flatMap

展开 for 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

什么是 for 推导式

Scala 中的 for 推导式是一种用于处理集合及其他容器类型的特殊语法。它看起来像命令式循环,但实际上是语法糖,编译器会将其改写为对 map、flatMap、filter 和 foreach 的调用。

理解展开过程后,即使面对自定义类型,您也能分析任何 for 推导式。

object Main {
  def main(args: Array[String]): Unit = {
    val result = for (x <- List(1, 2, 3)) yield x * 10
    println(result)
  }
}

单个生成器会变成 map

当 for 推导式恰好有一个生成器并使用 yield 时,编译器会将其改写为一次 map 调用。

  • for (x <- xs) yield f(x)
  • 会变成 xs.map(x => f(x))
object Main {
  def main(args: Array[String]): Unit = {
    val a = for (x <- List(1, 2, 3)) yield x + 1
    val b = List(1, 2, 3).map(x => x + 1)
    println(a == b)
  }
}

yield 关键字

yield 关键字会告诉 Scala 将结果收集到一个同类型的新集合中。

没有 yield 时,for 推导式会对每个元素执行副作用,并返回 Unit(它会变成 foreach)。

object Main {
  def main(args: Array[String]): Unit = {
    // No yield: side effect only
    for (x <- List("a", "b")) println(x)
    // With yield: builds a new List
    val upper = for (x <- List("a", "b")) yield x.toUpperCase
    println(upper)
  }
}

两个生成器会变成 flatMap + map

有两个或更多生成器时,外层生成器会变成 flatMap,最内层生成器会变成 map。

  • for (x <- xs; y <- ys) yield f(x, y)
  • 会变成 xs.flatMap(x => ys.map(y => f(x, y)))
object Main {
  def main(args: Array[String]): Unit = {
    val pairs = for {
      x <- List(1, 2)
      y <- List("a", "b")
    } yield (x, y)
    println(pairs)
  }
}

查看等价的 flatMap 写法

这里我们用两种方式编写相同的逻辑。证明它们等价,可以说明 for 推导式没有增加任何魔法,只是提高了可读性。

object Main {
  def main(args: Array[String]): Unit = {
    val sugar = for {
      x <- List(1, 2)
      y <- List(10, 20)
    } yield x + y

    val desugared = List(1, 2).flatMap(x => List(10, 20).map(y => x + y))

    println(sugar)
    println(sugar == desugared)
  }
}

为什么外层生成器使用 flatMap

每个外层元素都会产生一整组内层结果。如果外层生成器使用 map,我们就会得到类似 List(List(...), List(...)) 的嵌套集合。

flatMap 会将这些嵌套列表展平为一个平面结果。

object Main {
  def main(args: Array[String]): Unit = {
    val nested = List(1, 2).map(x => List(10, 20).map(y => x + y))
    val flat   = List(1, 2).flatMap(x => List(10, 20).map(y => x + y))
    println(nested)
    println(flat)
  }
}

没有 yield 的 for 会变成 foreach

没有 yield 的 for 循环会被改写为 foreach。它会执行循环体产生的副作用,并返回 Unit。

  • for (x <- xs) doSomething(x)
  • 会变成 xs.foreach(x => doSomething(x))
object Main {
  def main(args: Array[String]): Unit = {
    val r: Unit = for (x <- List(1, 2, 3)) print(x + " ")
    println()
    println("Return type is Unit")
  }
}

结果类型取决于第一个生成器

结果的类型由第一个生成器的集合决定。对 List 使用 for 会产生 List;对 Set 使用会产生 Set;对 Option 使用会产生 Option。

object Main {
  def main(args: Array[String]): Unit = {
    val fromList = for (x <- List(1, 2, 2, 3)) yield x
    val fromSet  = for (x <- Set(1, 2, 2, 3)) yield x
    println(fromList)
    println(fromSet)
  }
}

使用 = 绑定值

您可以在 for 推导式中使用 = 绑定中间值。这样可以避免重复计算,并提高可读性。

它会展开为一个携带额外值的 map,这些值会放在元组中。

object Main {
  def main(args: Array[String]): Unit = {
    val result = for {
      x <- List(1, 2, 3)
      doubled = x * 2
    } yield doubled + 1
    println(result)
  }
}

适用于任何具有 map/flatMap 的类型

由于展开过程只依赖 map 和 flatMap,因此只要某个类型定义了这些方法,for 推导式就适用于它:Option、Either、Try、Future 以及您自己的类。

object Main {
  def main(args: Array[String]): Unit = {
    val combined = for {
      a <- Some(2)
      b <- Some(3)
    } yield a * b
    println(combined)
  }
}

真正的优势在于可读性

将深度嵌套的 flatMap/map 调用链与简洁的 for 推导式进行比较。它们会编译成相同的结果,但 for 写法可以像配方一样从上到下阅读。

object Main {
  def main(args: Array[String]): Unit = {
    val chained = List(1, 2).flatMap(a => List(3, 4).map(b => a * b))
    val readable = for {
      a <- List(1, 2)
      b <- List(3, 4)
    } yield a * b
    println(chained == readable)
  }
}

快速检查

对于包含两个生成器和一个 yield 的 for 推导式,编译器会如何展开它?

回顾

您已经学会 for 推导式如何展开:

  • 一个生成器 + yield → map
  • 多个生成器 + yield → flatMap(外层)+ map(内层)
  • 没有 yield → 返回 Unit 的 foreach
  • val = 绑定用于添加中间值
  • 结果类型取决于第一个生成器

任何具有 map 和 flatMap 的类型都可以用于 for 推导式。

常见问题解答

「展开 for」课时是免费的吗?

是的 — 「展开 for」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

「展开 for」这节课中我会学到什么?

map 与 flatMap 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「展开 for」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?

能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 展开 for
  2. 使用 if 筛选
  3. 多个生成器
  4. 与 Option 和 Future 一起使用
← 返回 Scala for Backend Engineering & Functional Programming