#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)(условие), которое довольно хорошо подходит для моего случая.