Как я могу повторить расписание Zio до тех пор, пока не будет создано определенное значение?

#scala #zio

#scala #zio

Вопрос:

Предположим, у меня есть операция ввода-вывода (поэтому небезопасная), которая либо вернет true , либо false . Я хочу использовать механизм планирования Zio для выполнения этого до тех пор, пока значение не будет true , но не более N раз. Принять код из документации и изменить его на то, чего я пытаюсь достичь…

 import zio._
import zio.duration._
import zio.console._
import zio.clock._
import java.util.Random

object API {
  // our API method will return true about 30% of the time, but
  // return false the rest of the time (instead of throwing an
  // exception, as is shown in documentation)
  def makeRequest: Task[Boolean] = Task.effect {
    new Random().nextInt(10) > 7
  }
}

object ScheduleUtil {
  def schedule[A] = Schedule.spaced(1.second) amp;amp; Schedule.recurs(4).onDecision({
    case Decision.Done(_)                 => putStrLn(s"done trying")
    case Decision.Continue(attempt, _, _) => putStrLn(s"attempt #$attempt")
  })
}

import ScheduleUtil._
import API._

object ScheduleApp extends scala.App {

  implicit val rt: Runtime[Clock with Console] = Runtime.default

  rt.unsafeRun(makeRequest.retry(schedule).foldM(
    ex => putStrLn("Exception Failed"),
    v => putStrLn(s"Succeeded with $v"))
  )
}

// run the app
ScheduleApp.main(Array())
 

Конечно, это не работает. Вывод либо Succeeded with false или (иногда) Succeeded with true . Я попытался добавить Schedule.recurUntilEquals к Schedule определению, но безрезультатно.

 object ScheduleUtil {
    def schedule[A] = Schedule.spaced(1.second) amp;amp; Schedule.recurUntilEquals(true) amp;amp; Schedule.recurs(4).onDecision({
      case Decision.Done(_)                 => putStrLn(s"done trying")
      case Decision.Continue(attempt, _, _) => putStrLn(s"attempt #$attempt")
    })
  }

import ScheduleUtil._

// re-define ScheduleApp in the exact same way as above, and the following error results:

cmd93.sc:5: polymorphic expression cannot be instantiated to expected type;
 found   : [A]zio.Schedule[zio.console.Console,Boolean,((Long, Boolean), Long)]
    (which expands to)  [A]zio.Schedule[zio.Has[zio.console.Console.Service],Boolean,((Long, Boolean), Long)]
 required: zio.Schedule[?,Throwable,?]
  rt.unsafeRun(makeRequest.retry(schedule).foldM(
 

Как я могу реализовать такой вариант использования с помощью планировщика Zio? Конечно, я могу переопределить makeRequest задачу, чтобы намеренно генерировать исключение вместо возврата false , и это работает так же, как в документации. Но я надеялся избежать ненужной генерации / обработки исключений.

 object API {
    // our API method will return true about 30% of the time, but
    // return false the rest of the time (instead of throwing an
    // exception, as is shown in documentation)
    def makeRequest = Task.effect {
      if (new Random().nextInt(10) > 7) true else throw new Exception("Not true")
    }
  }
 

Ответ №1:

Ваша проблема в том, что вы используете retry effect вместо repeat того, что вам нужно, поскольку вы будете явно обходить канал ошибок, как вы упомянули.

Так что просто измените makeRequest.retry(schedule) на makeRequest.repeat(schedule) , и это должно сработать.

Для более подробного описания рассмотрим подписи ниже:

 // Schedule.spaced
def spaced(duration: Duration): Schedule[Any, Any, Long]

// Schedule.recurs
def recurs(n: Int): Schedule[Any, Any, Long]

// Schedule.recurUntilEquals
def recurUntilEquals[A](a: => A): Schedule[Any, A, A]
 

Schedule имеет три параметра типа, -Env , -In , Out , Env тот же, что и стандартный R тип, который является частью ZIO , но In и Out отличается от стандартного E и A других ZIO типов. Это связано Schedule с тем, что в соответствии с документами описывается «повторяющееся расписание, которое использует значения типа In и которое возвращает значения типа Out «. Для spaced и recurs ввод Any указывает, что он примет любое входное значение и, соответственно, также не ограничивает значение. Вы можете увидеть это, составив два вместе:

 val s: Schedule[Any, Any, (Long, Long)] = Schedule.spaced(1.second) amp;amp; Schedule.recurs(1)
 

Именно поэтому он не вызывает никаких ошибок компилятора при использовании как часть retry , потому что у них нет каких-либо особых требований к каналу ошибок, когда они его не используют. Но это также скрывает вашу проблему, потому retry что расписание используется только в случае ошибки, но поскольку вы возвращались true или false не получили сообщение об ошибке, и ваше расписание никогда не вызывалось.

После добавления recurUntilEquals ограничения ввода в расписание добавляется ограничение ввода:

 val s: Schedule[Any, Boolean, ((Long, Long), Boolean)] = Schedule.spaced(1.second) amp;amp; Schedule.recurs(1) amp;amp; Schedule.recurUntilEquals(true)
 

Теперь вы говорите, что входные данные, которые должны быть введены в, на самом Schedule деле являются логическими, но retry имеют подпись:

 def retry[R1 <: R, S](policy: Schedule[R1, E, S])(implicit ev: CanFail[E]): ZIO[R1 with Clock, E, A]
 

Обратите внимание, что вторая позиция Schedule в policy аргументе — это E параметр, который является типом ошибки, и поскольку Throwable =!= Boolean в результате вы получаете ошибку компилятора.

Соответственно, это подпись для repeat

 def repeat[R1 <: R, B](schedule: Schedule[R1, A, B]): ZIO[R1 with Clock, E, B]
 

Здесь мы видим, что на самом schedule деле принимает A тип, который в данном случае будет ответом от вашего API или Boolean который соответствует тому, что вы ожидаете в предоставленном вами расписании.

Комментарии:

1. Я не могу отблагодарить вас за этот ответ, который действительно помогает мне понять Zio.

2. Рад, что смог помочь!

Ответ №2:

Я использую ZIO.repeatWhile(task)(условие), которое довольно хорошо подходит для моего случая.