Scala Cats: хвостовой рекурсивный метод tailRecM для экземпляра задачи Monad [Validated [String ,?] - PullRequest
0 голосов
/ 04 октября 2018

В 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 Ответ

0 голосов
/ 08 октября 2018

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))
}
...