Scala Cats: хвостовой рекурсивный метод tailRecM для экземпляра задачи Monad [Validated[String,?]
В cats
, когда Monad
создается с использованием Monad
черта, в идеале хвостовая рекурсивная реализация для метода tailRecM
должны быть обеспечены для обеспечения безопасности стека.
Я использую конечный подход без тегов и хочу иметь эффект Task[Validated[String, ?]]
(Monix Task
) для моей программы.
Я не могу понять, как написать хвостовую рекурсивную реализацию. Мое нерекурсивное решение:
import cats.Monad
import cats.data.Validated
import cats.data.Validated.{Invalid, Valid}
import monix.eval.Task
final case class TaskValidated[A](value: Task[Validated[String, A]])
implicit val taskValidatedMonad: Monad[TaskValidated] =
new Monad[TaskValidated] {
override def flatMap[A, B](fa: TaskValidated[A])(f: A => TaskValidated[B]): TaskValidated[B] =
new TaskValidated[B](
fa.value.flatMap {
case Valid(a) => f(a).value
case Invalid(s) => Task(Invalid(s))
}
)
override def pure[A](a: A): TaskValidated[A] = TaskValidated(Task(Valid(a)))
// @annotation.tailrec
def tailRecM[A, B](init: A)(fn: A => TaskValidated[Either[A, B]]): TaskValidated[B] = {
TaskValidated(fn(init).value.flatMap {
case Invalid(s) => Task.now(Invalid(s))
case Valid(Right(b)) => Task.now(Valid(b))
case Valid(Left(a)) => tailRecM(a)(fn).value
})
}
}
1 ответ
Решение
Task
имеет свой tailRecM
, так что имеет смысл использовать его. Пытаться
def tailRecM[A, B](init: A)(fn: A => TaskValidated[Either[A, B]]): TaskValidated[B] = {
def aux(fn: A => Task[Validated[String, Either[A, B]]]): Task[Validated[String, B]] = {
def fn1(a: A): Task[Either[A, B]] = fn(a).flatMap {
case Valid(either) => Task.now(either)
case Invalid(s) => Task.raiseError(new RuntimeException(s))
}
Task.tailRecM(init)(fn1).map(Valid(_))
}
TaskValidated(aux(fn(_).value))
}