1. ApplicativeError

ApplicativeError 

Scala にはエラー状態を表す方法が複数ある。Cats は、エラーの発生とエラーからのリカバリーを表す ApplicativeError という型クラスを提供する。

trait ApplicativeError[F[_], E] extends Applicative[F] {
  def raiseError[A](e: E): F[A]

  def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]


  def recover[A](fa: F[A])(pf: PartialFunction[E, A]): F[A] =
    handleErrorWith(fa)(e => (pf.andThen(pure(_))).applyOrElse(e, raiseError[A](_)))
  def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] =
    handleErrorWith(fa)(e => pf.applyOrElse(e, raiseError))
}

ApplicativeError としての Either 

import cats._, cats.syntax.all._

{
  val F = ApplicativeError[Either[String, *], String]
  F.raiseError("boom")
}
// res0: Either[String, Nothing] = Left(value = "boom")
{
  val F = ApplicativeError[Either[String, *], String]
  val e = F.raiseError("boom")
  F.recover(e) {
    case "boom" => 1
  }
}
// res1: Either[String, Int] = Right(value = 1)

エラー型の Throwable とハッピーな状態の型 A が入れ替わる try-catch と違って、ApplicativeErrorEA もデータとして保持しなければいけないことに注目してほしい。

ApplicativeError としての scala.util.Try 

import scala.util.Try

{
  val F = ApplicativeError[Try, Throwable]
  F.raiseError(new RuntimeException("boom"))
}
// res2: Try[Nothing] = Failure(exception = java.lang.RuntimeException: boom)
{
  val F = ApplicativeError[Try, Throwable]
  val e = F.raiseError(new RuntimeException("boom"))
  F.recover(e) {
    case _: Throwable => 1
  }
}
// res3: Try[Int] = Success(value = 1)

ApplicativeError としての IO 

IO はファイバー内で走る必要があるので、scala.util.TryFuture のようにエラー状態を捕捉することができる。

import cats.effect.IO

{
  val F = ApplicativeError[IO, Throwable]
  F.raiseError(new RuntimeException("boom"))
}
// res4: IO[Nothing] = Error(t = java.lang.RuntimeException: boom)
{
  val F = ApplicativeError[IO, Throwable]
  val e = F.raiseError(new RuntimeException("boom"))
  val io: IO[Int] = F.recover(e) {
    case _: Throwable => 1
  }
}