Scala Cats:Task [Validated [String ,?]的Monad实例的尾部递归tailRecM方法

时间:2018-10-04 00:00:35

标签: monads tail-recursion scala-cats monix tagless-final

cats中,当使用Monad特性创建Monad时,理想情况下应提供方法tailRecM的尾递归实现以确保堆栈安全。 / p>

我正在使用无标签最终方法,希望对我的程序产生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 :(得分: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))
}