1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ScalaのEitherT[F[_], A, B]に入門する

Posted at

仕事で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を使用することで処理が簡便に書けることがわかりました。

公式リファレンス

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?