从上面重写函数的推荐方法是使用适当的Schedule,正如toxicafunk所建议的那样,导致
def getNameFromUserSchedule(askForName: UIO[String]): UIO[String] =
askForName.repeat(Schedule.doWhile(_.isEmpty))
这既简洁又易读,并且只消耗恒定数量的 ZIO 堆栈帧。
但是,您不必使用Schedule来制作
def getNameFromUser(askForName: UIO[String]): UIO[String] =
for {
resp <- askForName
name <- if (resp.isEmpty) getNameFromUser(askForName) else ZIO.succeed(resp)
} yield name
消耗恒定数量的 ZIO 堆栈帧。也可以这样做:
def getNameFromUser(askForName: UIO[String]): UIO[String] =
askForName.flatMap { resp =>
if (resp.isEmpty) getNameFromUser(askForName) else ZIO.succeed(resp)
}
这个函数看起来几乎就像是脱糖形式的原始函数,它是
def getNameFromUser(askForName: UIO[String]): UIO[String] =
askForName.flatMap { resp =>
if (resp.isEmpty) getNameFromUser(askForName) else ZIO.succeed(resp)
}.map(identity)
唯一的区别是决赛map(identity)
。在解释从该函数生成的 ZIO 值时,解释器必须将 压入identity
堆栈,计算flatMap
,然后应用identity
. 但是,为了计算flatMap
,可能会重复相同的过程,从而迫使解释器将与identities
循环迭代一样多的数量推入堆栈。这有点烦人,但解释器不知道它压入堆栈的函数实际上是身份。for
您可以通过使用better-monadic-for编译器插件来消除它们而不会放弃漂亮的语法,该插件能够在为map(identity)
推导式脱糖时优化最终结果。
没有map(identity)
,解释器将执行askForName
,然后使用闭包
resp =>
if (resp.isEmpty) getNameFromUser(askForName) else ZIO.succeed(resp)
以获得下一个 ZIO 值进行解释。此过程可能会重复任意次数,但解释器堆栈的大小将保持不变。
总结一下,这里简要讨论一下 ZIO 解释器何时会使用其内部堆栈:
- 当计算链式
flatMaps
时,就像io0.flatMap(f1).flatMap(f2).flatMap(f3)
。为了评估这样的表达式,解释器将推入f3
堆栈,并查看io0.flatMap(f1).flatMap(f2)
. 然后它将放在f2
堆栈上并查看io0.flatMap(f1)
。最后f1
将被放入堆栈并被io0
评估(解释器中有一个优化,可能会在这里采取捷径,但这与讨论无关)。在评估io0
to之后r0
,f1
从堆栈中弹出,并应用于 的结果r0
,给我们一个新的 ZIO 值,io1 = f1(r0)
。现在io1
被评估r1
并f2
从堆栈中弹出,以获得下一个 ZIO 值io2 = f2(r1)
。最后,io2
被评估为r2
,f3
从堆栈中弹出以获取io3 = f3(r2)
并io3
解释为r3
表达式的最终结果。因此,如果您有一个通过链接在一起工作的算法,flatMaps
您应该期望 ZIO 堆栈的最大深度至少是您的链的长度flatMaps
。
- 在计算链式折叠时,例如
io.foldM(h1, f1).foldM(h2, f2).foldM(h3, f3)
或链式折叠和链式的混合flatMaps
。如果没有错误,折叠行为类似于flatMaps
,因此关于 ZIO 堆栈的分析非常相似。您应该期望 ZIO 堆栈的最大深度至少是您的链的长度。
- 应用上述规则时,请记住,有许多组合子直接或间接地在
flatMap
and之上实现foldCauseM
:
map
, as
, zip
, zipWith
, <*
, *>
, foldLeft
,foreach
是在flatMap
fold
, foldM
, catchSome
, catchAll
,mapError
是在上面实现的foldCauseM
最后但并非最不重要的一点:你不应该太担心 ZIO 内部堆栈的大小,除非
- 您正在实现一种算法,其中迭代次数可能会变得任意大,仅适用于中等甚至恒定大小的输入数据
- 您正在遍历非常大的数据结构,这些数据结构不适合内存
- 用户可以毫不费力地直接影响堆栈深度(例如,这意味着无需通过网络向您发送大量数据)