Как мне сделать Scalaz ZIO ленивым? - PullRequest
0 голосов
/ 19 декабря 2018

У меня есть функция с тяжелыми побочными эффектами (например, вызов базы данных), которую я хочу использовать в качестве ленивого значения, чтобы ее вызывали только при первом использовании (и не вызывали вообще, если никогда не использовали).

Как мне сделать это с ZIO?

Если моя программа выглядит так, функция вызывается только один раз (но даже результат не используется вообще):

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Предположим, мне нужно передать IO, который производит Int вместо уже материализованного Int, но если я передам исходный IO, который просто вызывает базу данных, он будет вызываться повторно:

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)

}

Есть ли способ "обернуть" longRunningDbAction в нечто, что делает его ленивым?

Ответы [ 2 ]

0 голосов
/ 15 июля 2019

ZIO имеет памятка сейчас.

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

По сути, это то же самое, что и этот ответ : источник выглядит следующим образом

/**
   * 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

0 голосов
/ 20 декабря 2018

Я придумал следующее:

 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
  }

Обновленная версия для ZIO 1.0-RC4 (с поддержкой среды) :

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
}

Так что этоIO, который берет IO и возвращает его ленивую версию.

Он работает, начиная fiber, который запускает исходный io, но только после того, как Обещание (barrier) было выполнено.

Ленивый IO сначала завершает этот barrier (который, если он сделает это первым, разблокирует fiber, который, в свою очередь, запускает свернутый io), а затем присоединяет fiber кполучить результат вычисления.

С этим я могу сделать

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

И вывод консоли показывает, что действительно ленивое значение получается дважды, но только первое вызывает «доступ к базе данных».:

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