Scala 如何实现不使用';不要在ZIO中使用大量潜在的heapspace

Scala 如何实现不使用';不要在ZIO中使用大量潜在的heapspace,scala,zio,Scala,Zio,我知道ZIO维护自己的堆栈,即ZIO.internal.FiberContext#stack,它保护递归函数,如 def getNameFromUser(askForName:UIO[String]):UIO[String]= 为了{ resp在递归函数中使用循环。基本上,每次调用getNameFromUser将对象分配给堆时,堆永远无法释放这些对象,因为在t1上创建的对象需要在t2中创建的对象来解析,但t2中的对象需要t3上的对象无限解析。 与循环不同,您应该使用ZIO组合器,就像永远或您可以

我知道ZIO维护自己的堆栈,即
ZIO.internal.FiberContext#stack
,它保护递归函数,如

def getNameFromUser(askForName:UIO[String]):UIO[String]=
为了{

resp在递归函数中使用循环。基本上,每次调用
getNameFromUser
将对象分配给堆时,堆永远无法释放这些对象,因为在t1上创建的对象需要在t2中创建的对象来解析,但t2中的对象需要t3上的对象无限解析。

与循环不同,您应该使用ZIO组合器,就像
永远
或您可以在上找到的任何其他组合器一样

导入zio.Schedule
val getNameFromUser:RIO[控制台,字符串]=用于{

_从上面重写函数的推荐方法是使用

def getNameFromUserSchedule(askForName:UIO[String]):UIO[String]=
askForName.repeat(Schedule.doWhile(u.isEmpty))
这既简洁又可读,并且只消耗恒定数量的ZIO堆栈帧

但是,您不必使用

def getNameFromUser(askForName:UIO[String]):UIO[String]=
为了{
响应
if(resp.isEmpty)getNameFromUser(askForName)else ZIO.success(resp)
}.地图(身份)
唯一的区别是最终的
映射(标识)
。当解释此函数生成的ZIO值时,解释器必须在堆栈上推送
标识
,计算
平面图
,然后应用
标识
。但是,要计算
平面图
,同样的过程可能会重复,迫使解释器推送尽可能多的
标识在堆栈上,因为我们有循环迭代。这有点烦人,但解释器无法知道,它推到堆栈上的函数实际上是标识。您可以通过使用编译器插件消除它们,而无需删除nice
for
语法,该插件能够优化最终的
映射(标识)
为理解而脱胶时

如果没有
map(identity)
,解释器将执行
askForName
,然后使用闭包

resp=>
if(resp.isEmpty)getNameFromUser(askForName)else ZIO.success(resp)
获取用于解释的下一个ZIO值。此过程可能重复任意次数,但解释程序堆栈的大小将保持不变

总而言之,以下是关于ZIO解释器何时使用其内部堆栈的简要讨论:

  • 计算链式
    flatMap
    ,如
    io0.flatMap(f1).flatMap(f2).flatMap(f3)
    。要计算这样的表达式,解释器将在堆栈上按
    f3
    ,查看
    io0.flatMap(f1).flatMap(f2)
    。然后将
    f2
    放在堆栈上,查看
    io0.flatMap(f1)
    。最后将
    f1
    放在堆栈上,并对
    io0
    进行评估(解释器中存在一个优化,可能会在此处采用快捷方式,但这与讨论无关)。在对
    io0
    进行求值后,从堆栈中弹出
    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)
    ,或链式折叠和链式
    平面图的混合。如果没有错误,折叠的行为类似于
    平面图
    ,因此关于ZIO堆栈的分析非常相似。您应该预计ZIO堆栈的最大深度至少是链的长度
  • 在应用上述规则时,请记住,有许多组合符直接或间接地实现在
    flatMap
    foldCauseM
    之上:
    • map
      as
      zip
      zipWith
      foldLeft
      foreach
      都是在
      flatMap
      之上实现的
    • fold
      foldM
      catchSome
      catchAll
      maperor
      是在
      foldCauseM
      之上实现的
  • 最后但并非最不重要的一点:您不应该太担心ZIOs内部堆栈的大小,除非

    • 您正在实现一个算法,其中迭代次数可能会变得任意大,而对于中等大小的输入数据,甚至是恒定大小的输入数据
    • 您正在遍历不适合内存的非常大的数据结构
    • 用户只需很少的努力就可以直接影响堆栈深度(例如,这意味着无需通过网络向您发送大量数据)
     import zio.Schedule
    
     val getNameFromUser: RIO[Console, String] = for {
      _    <- putStrLn("Waht is your name")
      name <- zio.console.getStrLn
     } yield name
    
     val runUntilNotEmpty = Schedule.doWhile[String](_.isEmpty)
    
     rt.unsafeRun(getNameFromUser.repeat(runUntilNotEmpty))
    
    import zio._
    import zio.console._
    import scala.io.StdIn
    
    object ConsoleEx extends App {
    
      val getNameFromUser = for {
        _    <- putStrLn("What is your name?")
        name <- getStrLn
        _    <- putStr(s"Hello, $name")
      } yield ()
    
      override def run(args: List[String]) =
        getNameFromUser.fold(t => {println(t); 1}, _ => 0)
    
    }