2.2 按顺序组合ZIO
正如上小节所述,ZIO效果是描述并发工作流的蓝图,并且我们通过转换和组合更小,更简单的效果来构建更复杂的效果,从而解决业务问题。
我们看到了如何使用 ZIO 的 delay 运算符将一个效果转换为另一种效果,并将其执行延迟到将来。 除了延迟之外,ZIO还拥有数十种其他强大的运算符,它们可以转换并组合效果以解决现代应用程序开发中的常见问题。
我们将在随后的章节中学习大多数这些运算符,但是我们需要介绍的最重要的运算符之一就是flatMap。ZIO效果的flatMap方法表示两个效果的顺序组合,使我们可以根据第一个效果的输出来创建第二个效果。
flatMap的简化类型签名如下所示:
trait ZIO[R, E, A] { ...
def flatMap[B](andThen: A => ZIO[R, E, B]): ZIO[R, E, B] = ... ...
}
实际上,flatMap的意思是说:“先运行第一个效果,然后根据第一个运行的结果运行第二个效果”。 使用此顺序运算符,我们可以描述一个简单的工作流,该工作流读取用户输入,然后将输入显示给用户,如以下代码片段所示:
// 下面的程序将两个没有关系的effect通过flatMap串成一个effect
import scala.io.StdIn
val readLine = ZIO.effect(StdIn.readLine())
def printLine(line: String) =ZIO.effect(println(line))
val echo = readLine.flatMap(line => printLine(line))
请注意,我们在控制台上打印的内容取决于我们从控制台读取的内容:因此,我们依次执行两件事,而我们所做的第二件事取决于我们所做的第一件事所产生的结果。
flatMap基本的运算符,因为它捕获了在程序中语句的执行方式:后面的语句取决于前面的语句计算的结果。
如果我们通过过程式的代码编写这段程序就是:
val line = Console.readLine Console.println(line)
过程编程与flatMap运算符之间的这种关系是如此精确,我们实际上可以通过将每个简单的语句包装在诸如ZIO.effect之类的构造函数中,然后使用flatMap将这些语句粘合在一起,从而将任何程序程序转换为ZIO。
例如,假设我们在以下代码段中显示了程序程序:
val data = doQuery(query)
val response = generateResponse(data) writeResponse(response)
我们可以将上面的代码转变为下面的形式:
ZIO
.effect(doQuery(query))
.flatMap(
data =>
ZIO
.effect(generateResponse(data))
.flatMap(response => ZIO.effect(writeResponse(response)))
)
尽管是一种简单的转换,但是如果连续超过两个或三个flatMap操作,代码的嵌套将变得有些困难。 幸运的是,Scala具有for推导的功能,该功能使我们能够以类似于过程编程的方式表达顺序组合。
在下一节中,我们将详细探讨各种理解。
2.2.1
通过 for 推导,我们可以将下面的代码:
readLine.flatMap(line => printLine(line))
重写成下面的形式:
import zio._
val echo = for {
line <- readLine
_ <- printLine(line) } yield ()
}
从这个简短的代码片段中可以看出,没有嵌套,并且理解中的每一行看起来都类似于过程编程中的一条语句。
for推导具有如下的结构:
- 它们由关键字for引入,后跟一个代码块,并以关键字yield结束,关键字yield之后是单个参数,代表effect的执行成功的返回值。
- for 推导的每一行都使用格式 result <-effect 编写,其中effect是一个作用,而result是一个变量,代表该作用的成功值。 如果不需要作用的结果,则可以将下划线用作变量名称。
Scala将具有n行的for推导转换为对效果的flatMap方法的n-1次调用,最后对最后一个作用调用map方法。
例如:
for {
x <- doA
y <- doB(x)
z <- doC(x, y)
} yield x + y + z
scala程序会将上面的代码转为下面的语句
doA.flatMap(x => doB(x).flatMap(y => doC(x, y).map(z => x + y + z)))
许多Scala开发人员发现,for推导比起一连串的FlatMap嵌套调用更容易理解。 在本书中,除了非常简短的摘录外,我们更喜欢for推导而不是显式调用flatMap。