How do I make a Scalaz ZIO lazy?

 ̄綄美尐妖づ 提交于 2019-12-10 19:56:19

问题


I have a heavy side-effecting function (think database call) that I want to use as a lazy value, so that it gets called only on first use (and not at all if never used).

How do I do this with ZIO?

If my program looks like this, the function gets called only once (but even the result is not used at all):

import scalaz.zio.IO
import scalaz.zio.console._

object Main extends scalaz.zio.App {

  def longRunningDbAction: IO[Nothing, Integer] = for {
    _ <- putStrLn("Calling the database now")
  } yield 42

  def maybeUseTheValue(x: Integer): IO[Nothing, Unit] = for {
    _ <- putStrLn(s"The database said ${x}")
  } yield ()

  def maybeNeedItAgain(x: Integer): IO[Nothing, Unit] = for {
    _ <- putStrLn("Okay, we did not need it again here.")
  } yield ()

 override def run(args: List[String]): IO[Nothing, Main.ExitStatus] = for {
    valueFromDb <- longRunningDbAction
    _ <- maybeUseTheValue(valueFromDb)
    _ <- maybeNeedItAgain(valueFromDb)
  } yield ExitStatus.ExitNow(0)

}

I suppose I have to pass an IO that produces the Int instead of the already materialized Int, but if I pass in the original IO that just calls the database, it will be called repeatedly:

object Main extends scalaz.zio.App {

  def longRunningDbAction: IO[Nothing, Integer] = for {
    _ <- putStrLn("Calling the database now")
  } yield 42


  def maybeUseTheValue(x: IO[Nothing, Integer]): IO[Nothing, Unit] = for {
    gettingItNow <- x
    _ <- putStrLn(s"The database said ${gettingItNow}")
  } yield ()

  def maybeNeedItAgain(x: IO[Nothing, Integer]): IO[Nothing, Unit] = for {
    gettingItNow <- x
    _ <- putStrLn(s"Okay, we need it again here: ${gettingItNow}")
  } yield ()

  override def run(args: List[String]): IO[Nothing, Main.ExitStatus] = for {
    _ <- maybeUseTheValue(longRunningDbAction)
    _ <- maybeNeedItAgain(longRunningDbAction)
  } yield ExitStatus.ExitNow(0)

}

Is there a way to "wrap" the longRunningDbAction into something that makes it lazy?


回答1:


I came up with the following:

 def lazyIO[E,A](io: IO[E,A]): IO[Nothing, IO[E, A]] = {
    for {
      barrier <- Promise.make[Nothing, Unit]
      fiber <- (barrier.get *> io).fork
    } yield barrier.complete(()) *> putStrLn("getting it") *> fiber.join
  }

Updated version for ZIO 1.0-RC4 (with Environment support):

def lazyIO[R, E, A](io: ZIO[R, E, A]): ZIO[R, Nothing, ZIO[R, E, A]] = {
  for {
    barrier <- Promise.make[Nothing, Unit]
    fiber <- (barrier.await *> io).fork
  } yield barrier.succeed(()) *> fiber.join
}

So this is an IO that takes an IO and returns a lazy version of it.

It works by starting a fiber that runs the original io, but only after a Promise (barrier) has been completed.

The lazy IO first completes that barrier (which if it was the first one to do this will unblock the fiber which in turn runs the wrapped io) and then joins the fiber to retrieve the calculation result.

With this, I can do

override def run(args: List[String]): IO[Nothing, Main.ExitStatus] = for {
    valueFromDb <- lazyIO(longRunningDbAction)
    _ <- maybeUseTheValue(valueFromDb)
    _ <- maybeNeedItAgain(valueFromDb)
  } yield ExitStatus.ExitNow(0)

And the console output shows that indeed the lazy value gets pulled twice but only the first one triggers the "database access":

getting it
Calling the database now
The database said 42
getting it
Okay, we need it again here: 42



回答2:


ZIO has memoize now.

override def run(args: List[String]): IO[Nothing, Main.ExitStatus] = for {
   valueFromDb <- ZIO.memoize(longRunningDbAction)
   _ <- maybeUseTheValue(valueFromDb)
   _ <- maybeNeedItAgain(valueFromDb)
} yield ExitStatus.ExitNow(0)

It does essentially the same thing as this answer: Source looks like this

/**
   * Returns an effect that, if evaluated, will return the lazily computed result
   * of this effect.
   */
  final def memoize: ZIO[R, Nothing, IO[E, A]] =
    for {
      r <- ZIO.environment[R]
      p <- Promise.make[E, A]
      l <- Promise.make[Nothing, Unit]
      _ <- (l.await *> ((self provide r) to p)).fork
    } yield l.succeed(()) *> p.await



来源:https://stackoverflow.com/questions/53851349/how-do-i-make-a-scalaz-zio-lazy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!