仕事でScalaのコードを書いているのですが、EitherTをよく見かけるのでおさらいしておきたいと思います。
EitherTとは
EitherTはEitherとF[Either]の軽量ラッパーで、EitherをOptionやFutureなどの型に内包する処理をする場合、EitherTを使うことで処理が簡便に書ける。
EitherTにはcatsやscalazといった関数型ライブラリに用意されていますが、ここではcatsのEitherTをメインに扱って行きたいと思います。
以下importします。
import cats.implicits._
import cats.data.Either
以下公式サイトの例です。
Future[Either[A, B]]を非同期で合成させています。
import scala.util.Try
import cats.implicits._
def parseDouble(s: String): Either[String, Double] =
Try(s.toDouble).map(Right(_)).getOrElse(Left(s"$s is not a number"))
def divide(a: Double, b: Double): Either[String, Double] =
Either.cond(b != 0, a / b, "Cannot divide by zero")
def parseDoubleAsync(s: String): Future[Either[String, Double]] =
Future.successful(parseDouble(s))
def divideAsync(a: Double, b: Double): Future[Either[String, Double]] =
Future.successful(divide(a, b))
// EitherTを使用しない場合
def divisionProgramAsync(inputA: String, inputB: String): Future[Either[String, Double]] =
parseDoubleAsync(inputA) flatMap { eitherA =>
parseDoubleAsync(inputB) flatMap { eitherB =>
(eitherA, eitherB) match {
case (Right(a), Right(b)) => divideAsync(a, b)
case (Left(err), _) => Future.successful(Left(err))
case (_, Left(err)) => Future.successful(Left(err))
}
}
}
// EitherTを使用する場合
def divisionProgramAsync(inputA: String, inputB: String): EitherT[Future, String, Double] =
for {
a <- EitherT(parseDoubleAsync(inputA))
b <- EitherT(parseDoubleAsync(inputB))
result <- EitherT(divideAsync(a, b))
} yield result
divisionProgramAsync("4", "2").value
// res2: Future[Either[String, Double]] = Future(Success(Right(2.0)))
divisionProgramAsync("a", "b").value
見てお分かりの通りEitherTを使用することでスッキリ書くことができていますし、EitherTを使用しない場合だとエラー処理とプログラムが一緒ごたになっています。
EitherT少し難しいと感じていましたがやはり必要なんだと感じました。
AからEitherTへの変換
val num: Int = 5
val error: String = "Not a number"
numEitherT: EitherT[Option, String, Int] = EitherT.rightT(num) // EitherT(Some(Right(5)))
errorEitherT: EitherT[Option, String, Int] = EitherT.leftT(error) // EitherT(Left("Not a number"))
F[A] からEitherTへの変換
val numOpt = Option[Int] = Some(5)
val errOpt = Option[String] = Some("Not a number")
val numEitherT: EitherT[Option, String, Int] = EitherT.right(numOpt) // EitherT(Some(Right(5)))
val errEitherT: EitherT[Option, String, Int] = EitherT.left(error) // EitherT(Left("Not a number"))
Either[A, B]またはF[Either[A, B]]からEitherTへの変換
val numE: Either[String, Int] = Right(100)
val errE: Either[String, Int] = Left("Not a number")
val numFE = List[Either[String, Int]]= List(Right(250))
val numET: EitherT[List String, Int] = EitherT.fromEither(numE) //EitherT(List(Right(100)))
val errEt: EitherT[List, String, Int] = EitherT.fromEither(errE) // EitherT(List(Left("Not a number")))
val numFET: EitherT[List, String, Int] = EitherT(numFE) // EitherT(List(Right(250)))
EitherTからF[Either[A, B]]を抽出する
val errT: EitherT[Future, String, Int] = EitherT.left("hoge") // EitherT(Future(Success(Left("hoge"))))
val err: Future[Either[String, Int]] = errT.value // Future(Success(Left("hoge")))
cats公式リファレンスをもとにEitherTの理解を深めることができました。
少しむずかしい書き方をしていますが、EitherTを使用することで処理が簡便に書けることがわかりました。