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

与 Option 和 Future 一起使用

单子式串联

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

超越集合

for 推导式并不局限于列表。任何提供 map 和 flatMap 的类型都可以使用,包括 Option、Either、Try 和 Future。这称为单子链式调用。

object Main {
  def main(args: Array[String]): Unit = {
    val sum = for {
      a <- Some(10)
      b <- Some(20)
    } yield a + b
    println(sum)
  }
}

Option 遇到 None 会提前终止

对于 Option,只有当每个生成器都是 Some 时,推导式才会产生结果。如果其中任何一个是 None,整个结果就是 None,后续步骤也会被跳过。

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

    val missing = for {
      a <- Some(2)
      b <- None: Option[Int]
    } yield a * b

    println(ok)
    println(missing)
  }
}

串联可选查找

一种常见模式是:在 Map 中查找键,而每次查找都会返回一个 Option。只有所有查找都成功时,for 推导式才会产生值。

object Main {
  def main(args: Array[String]): Unit = {
    val prices = Map("apple" -> 3, "bread" -> 5)
    val total = for {
      a <- prices.get("apple")
      b <- prices.get("bread")
    } yield a + b
    println(total)

    val missing = for {
      a <- prices.get("apple")
      c <- prices.get("milk")
    } yield a + c
    println(missing)
  }
}

为什么它优于嵌套的 if-else

如果不使用 for 推导式,您就需要嵌套模式匹配或空值检查。单子形式将那座金字塔压平成清晰的线性序列。

object Main {
  def parse(s: String): Option[Int] = s.toIntOption
  def main(args: Array[String]): Unit = {
    val result = for {
      x <- parse("40")
      y <- parse("2")
    } yield x + y
    println(result)
  }
}

Guard 同样适用于 Option

Option 推导式中的守卫会在条件不满足时将值转换为 None,从而充当内联验证。

object Main {
  def main(args: Array[String]): Unit = {
    def validate(age: Int): Option[Int] =
      for {
        a <- Some(age)
        if a >= 18
      } yield a
    println(validate(25))
    println(validate(15))
  }
}

引入 Future

Future 表示一个稍后才能获得的值。和 Option 一样,它也有 map 和 flatMap,因此 for 推导式可以按顺序执行异步步骤。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

object Main {
  def main(args: Array[String]): Unit = {
    val f = for {
      a <- Future(10)
      b <- Future(20)
    } yield a + b
    println(Await.result(f, 2.seconds))
  }
}

Future 在这里按顺序运行

当 for 推导式中的一个生成器依赖另一个生成器时,Future 会按顺序运行,因为每个 flatMap 都会等待前一步的结果。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

object Main {
  def main(args: Array[String]): Unit = {
    val f = for {
      a <- Future(5)
      b <- Future(a * 2)
    } yield a + b
    println(Await.result(f, 2.seconds))
  }
}

并行运行 Future

若要并行运行 Future,请在 for 推导式之前启动它们,然后再进行组合。独立的工作会彼此重叠,而不是相互等待。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

object Main {
  def main(args: Array[String]): Unit = {
    val fa = Future(100)
    val fb = Future(200)
    val combined = for {
      a <- fa
      b <- fb
    } yield a + b
    println(Await.result(combined, 2.seconds))
  }
}

使用 Either 表示更丰富的错误

Either[E, A] 在失败时携带错误值。在 for 推导式中,第一个 Left 会提前终止计算并被返回。

object Main {
  def parse(s: String): Either[String, Int] =
    s.toIntOption.toRight(s"not a number: $s")
  def main(args: Array[String]): Unit = {
    val good = for { a <- parse("3"); b <- parse("4") } yield a + b
    val bad  = for { a <- parse("3"); b <- parse("x") } yield a + b
    println(good)
    println(bad)
  }
}

相同的形式,适用于多种类型

请注意,无论使用 Option、Either、Try 还是 Future,推导式的外观都完全相同。不同之处仅在于失败语义。这种统一性正是单子链式调用的强大之处。

import scala.util.Try

object Main {
  def main(args: Array[String]): Unit = {
    val result = for {
      a <- Try("21".toInt)
      b <- Try("2".toInt)
    } yield a * b
    println(result)
  }
}

不允许混合类型

同一个推导式中的所有生成器必须属于同一个单子。您不能直接将 Option 生成器与 Future 生成器混用;请先转换其中一个(例如使用 Future.fromTry,或将 Option 包装起来)。

object Main {
  def main(args: Array[String]): Unit = {
    // Convert the Option to keep types consistent
    val maybe: Option[Int] = Some(7)
    val result = for {
      a <- maybe
      b <- maybe.map(_ + 1)
    } yield a + b
    println(result)
  }
}

快速检查

在 Option for 推导式中,如果某个生成器产生 None,会发生什么?

回顾

您学习了使用 for 推导式进行单子链式调用:

  • 适用于任何具有 map 和 flatMap 的类型
  • Option 遇到 None 会提前终止
  • Either 和 Try 在失败时提前终止,并携带错误信息
  • Future 会按顺序执行异步步骤;若要并行执行,请尽早启动它们
  • 所有生成器都必须共享相同的单子类型

常见问题解答

「与 Option 和 Future 一起使用」课时是免费的吗?

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

「与 Option 和 Future 一起使用」这节课中我会学到什么?

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

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

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

「与 Option 和 Future 一起使用」课时需要多长时间?

大多数 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