Either[Scala with Cats]の説明

#初めに
<b>`Either`とは…`Left`と`Right`の2つの具体的な値が存在するデータ型です。大抵の場合、`Left`はエラー値が格納されているときの`Either`の型、`Right`は正常な値が格納されているときの`Either`の型とみなすことが多いです。</b>

Scala with Catsの`Either`について[公式ドキュメント](https://typelevel.org/cats/datatypes/either.html)を自分なりに翻訳して説明していきます！！
※ところどころ、分かりやすいように文を付け加えてます！

#モチベーション
日々プログラミングをしていて、失敗する可能性のある関数を書いていることに気がつくことがあるでしょう。 たとえば、サービスにクエリを実行すると、接続の問題や予期しないJSON応答が発生する可能性があります。

これらのエラーを伝達するために、例外をスローすることが一般的です。ただし、例外は、Scalaコンパイラによって、いかなる方法、形状、形式でも追跡されません。関数がスローする可能性のある例外の種類を確認するには、ソースコードを詳しく調べる必要があります。次に、これらの例外を処理するために、呼び出し元でそれらを確実にキャッチする必要があります。例外をスローする手順を作成しようとすると、これはさらに扱いにくくなります。

```Scala
val throwsSomeStuff: Int => Double = ???

val throwsOtherThings: Double => String = ???

val moreThrowing: String => List[Char] = ???

val magic = throwsSomeStuff.andThen(throwsOtherThings).andThen(moreThrowing)
```

コードで例外を喜んでスローするとします。型を見てみると、これらの関数はどれも例外を複数スローする可能性がありますが、調べないと分かりません。生成する際にいずれかの生成する関数から例外がスローされる可能性もあるし、同じ種類の例外（`IllegalArgumentException`など）をスローする可能性もあります。そのため、その例外がどこから発生したかを正確に追跡するのは難しい場合があります。

では、どのようにしてエラーを伝えましょうか…
データ型で明示的に表しましょう！

#Either
##`Either` vs `Validated`
一般的に、Validatedはエラーを累積するために使用され、Eitherは最初のエラー時に計算するために使用されます。詳細については、[Validatedのドキュメント](https://typelevel.org/cats/datatypes/validated.html)の[「Validated vs Either」セクション](https://typelevel.org/cats/datatypes/validated.html#validated-vs-either)を参照してください。

##構文
Scala2.10.xおよび2.11.xでは、`Either`はバイアスがありません。つまり、`flatMap`や`map`などの通常のコンビネータが欠落しています。その代わりに、`.right`または`.left`を呼び出して、コンビネータを持つ`RightProjection`または`LeftProjection`を（それぞれ）取得します。プロジェクションの向きはバイアスの向きを示します。例えば、`RightProjection`で`map`を呼び出すと、`Either`の`Right`に作用します。

```Scala
val e1: Either[String, Int] = Right(5)
// e1: Either[String, Int] = Right(5)
e1.right.map(_ + 1)
// res0: Either[String, Int] = Right(6)

val e2: Either[String, Int] = Left("hello")
// e2: Either[String, Int] = Left("hello")
e2.right.map(_ + 1)
// res1: Either[String, Int] = Left("hello")
```

戻り値の型自体が`Either`に戻ることに注意してください。したがって、`flatMap`または`map`をまた呼び出したい場合は、もう一度`left`または`right`を呼び出す必要があります。

しかし大抵の場合、`Either`はrightにバイアスを掛けます。実際にScala2.12.xでは、`Either`はデフォルトでrightにバイアスを掛けています。

通常、私たちは片側にバイアスを掛け、終わりにしたいですが、慣例により、大抵の場合、rightが選択されます。<b>（筆者「`Either`は主に`Right`で値を扱うよ！ってことだと思います！`Either`を使う場合、`Left`値をエラー値、`Right`値を正常な値とみなすことが多いです。」）</b>

Scala2.12.xでは、この規則は標準ライブラリに実装されています。 Catsは2.10.xと2.11.xに基づいて構築されているため、`cats.syntax.either._`または`cats.implicits._`で利用可能な構文拡張によってギャップが埋められています。

```Scala
import cats.implicits._

val right: Either[String, Int] = Right(5)
// right: Either[String, Int] = Right(5)
right.map(_ + 1)
// res2: Either[String, Int] = Right(6)

val left: Either[String, Int] = Left("hello")
// left: Either[String, Int] = Left("hello")
left.map(_ + 1)
// res3: Either[String, Int] = Left("hello")
```

このチュートリアルの残りの部分では、構文拡張がrightにバイアスを掛けた`Either`と他の便利なコンビネータ（`Either`とコンパニオンオブジェクトの両方）を提供している範囲内であると仮定します。

`Either`はrightにバイアスを掛けているため、モナドインスタンスを定義することができます。`Right`の場合にのみ計算を続行する必要があるため、leftの型パラメータを修正し、rightのパラメーターを空けておきます。

注：以下の例は、kind-projectorコンパイラプラグインの使用を想定しており、プロジェクトでこちらを使用されていない場合はコンパイルされません。

```Scala
import cats.Monad

implicit def eitherMonad[Err]: Monad[Either[Err, ?]] =
  new Monad[Either[Err, ?]] {
    def flatMap[A, B](fa: Either[Err, A])(f: A => Either[Err, B]): Either[Err, B] =
      fa.flatMap(f)

    def pure[A](x: A): Either[Err, A] = Either.right(x)

    @annotation.tailrec
    def tailRecM[A, B](a: A)(f: A => Either[Err, Either[A, B]]): Either[Err, B] =
      f(a) match {
        case Right(Right(b)) => Either.right(b)
        case Right(Left(a)) => tailRecM(a)(f)
        case l@Left(_) => l.rightCast[B] // Cast the right type parameter to avoid allocation
      }
  }
```

##使用例：ラウンド１
実行例として、文字列を整数に変換し、逆数を取得してから、逆数を文字列に変換する一連の関数があります。

例外をスローするコードでは、次のようになります。

```Scala
object ExceptionStyle {
  def parse(s: String): Int =
    if (s.matches("-?[0-9]+")) s.toInt
    else throw new NumberFormatException(s"${s} is not a valid integer.")

  def reciprocal(i: Int): Double =
    if (i == 0) throw new IllegalArgumentException("Cannot take reciprocal of 0.")
    else 1.0 / i

  def stringify(d: Double): String = d.toString
}
```

これを`Either`を用いて、関数の一部が戻り値の型で明示的に失敗する可能性があるという事実を作りましょう。

```Scala
object EitherStyle {
  def parse(s: String): Either[Exception, Int] =
    if (s.matches("-?[0-9]+")) Either.right(s.toInt)
    else Either.left(new NumberFormatException(s"${s} is not a valid integer."))

  def reciprocal(i: Int): Either[Exception, Double] =
    if (i == 0) Either.left(new IllegalArgumentException("Cannot take reciprocal of 0."))
    else Either.right(1.0 / i)

  def stringify(d: Double): String = d.toString
}
```

これで、`flatMap`や`map`などのコンビネータを使用して、関数を一緒に構成できます。

```Scala
import EitherStyle._

def magic(s: String): Either[Exception, String] =
  parse(s).flatMap(reciprocal).map(stringify)
```

合成関数を使用すると、文字列を渡してから、例外でパターンマッチングを行うことができます。`Either`はsealed型（代数的データ型またはADT(抽象データ型)と呼ばれることが多い）であるため、`Left`と`Right`の両方のケースをチェックしないと、コンパイラは文句を言います。

```Scala
magic("123") match {
  case Left(_: NumberFormatException) => println("not a number!")
  case Left(_: IllegalArgumentException) => println("can't take reciprocal of 0!")
  case Left(_) => println("got unknown exception")
  case Right(s) => println(s"Got reciprocal: ${s}")
}
// Got reciprocal: 0.008130081300813009
```

これは悪くは無いですね。これらの句を１つでも省略すると、コンパイラは必要に応じて怒ります。ただし、`Left(_)`句に注意してください。`Either[Exception, String]`型を指定していると、`NumberFormatException`でも`IllegalArgumentException`でもない`Left`値が存在する可能性があるため、これを省略するとコンパイラは文句を言います。ただし、ソースコードを調べることで、スローされる例外はそれらだけであることが分かるため、他の例外を考慮する必要があるのは奇妙に思えます。これは、まだ改善の余地があることってことですね。

##使用例：ラウンド２
エラー値として例外を使用する代わりに、プログラムで問題が発生する可能性のあるものを明示的に列挙しましょう。

```Scala
import cats.implicits._

object EitherStyle {
  sealed abstract class Error
  final case class NotANumber(string: String) extends Error
  case object NoZeroReciprocal extends Error

  def parse(s: String): Either[Error, Int] =
    if (s.matches("-?[0-9]+")) Either.right(s.toInt)
    else Either.left(NotANumber(s))

  def reciprocal(i: Int): Either[Error, Double] =
    if (i == 0) Either.left(NoZeroReciprocal)
    else Either.right(1.0 / i)

  def stringify(d: Double): String = d.toString

  def magic(s: String): Either[Error, String] =
    parse(s).flatMap(reciprocal).map(stringify)
}
```

このモジュールでは、発生する可能性のあるすべてのエラーを列挙します。次に、エラー値として例外クラスを使用する代わりに、列挙されたケースの1つを使用します。これで、パターンマッチングを行うとより適切なマッチングが得られます。さらに、`Error`は`sealed`であるため、外部コードは、処理に失敗する可能性のある派生型を追加できません。

```Scala
import EitherStyle._

magic("123") match {
  case Left(NotANumber(_)) => println("not a number!")
  case Left(NoZeroReciprocal) => println("can't take reciprocal of 0!")
  case Right(s) => println(s"Got reciprocal: ${s}")
}
// Got reciprocal: 0.008130081300813009
```

#Eitherを使用するときの問題
すべてのエラー処理に`Either`を使い始めると、２つの別々のモジュールを呼び出す必要があり、別々の種類のエラーが返されるという問題が発生する可能性があります。

```Scala
sealed abstract class DatabaseError
trait DatabaseValue

object Database {
  def databaseThings(): Either[DatabaseError, DatabaseValue] = ???
}

sealed abstract class ServiceError
trait ServiceValue

object Service {
  def serviceThings(v: DatabaseValue): Either[ServiceError, ServiceValue] = ???
}
```

データベースの処理を実行してから、データベースの値を取得してサービスの処理を実行するアプリケーションがあるとします。型を見ると、`flatMap`がそれを行うように見えます。

```Scala
def doApp = Database.databaseThings().flatMap(Service.serviceThings)
```

このコードは、2.12またはそれ以前のバージョンのScalaを使用しているかどうかに関係なく、期待どおりにコンパイルおよび機能します。ここで取得する`flatMap`（Scala2.10および2.11のCatsの`Either`構文によって提供されるか、Scala 2.12では`Either`のメソッドによって提供される）には、次の署名があります。

```Scala
def flatMap[AA >: A, Y](f: (B) => Either[AA, Y]): Either[AA, Y]
```

この`flatMap`は、`List`や`Option`にあるものとは異なります。例えば、２つの型パラメータがあり、別のAAパラメータを使用すると、leftに異なる型の`Either`に`flatMap`することができます。この動作は`Either`の共分散と一致しており、便利な場合もありますが、オブジェクトがleftの型として推測されるなど、厄介な分散の問題が発生しやすくなります。

##解決策1：アプリケーション全体のエラー
そんなとき、アプリケーション全体でエラーデータ型を共有させたくなるかもしれません。

```Scala
sealed abstract class AppError
case object DatabaseError1 extends AppError
case object DatabaseError2 extends AppError
case object ServiceError1 extends AppError
case object ServiceError2 extends AppError

trait DatabaseValue

object Database {
  def databaseThings(): Either[AppError, DatabaseValue] = ???
}

object Service {
  def serviceThings(v: DatabaseValue): Either[AppError, ServiceValue] = ???
}

def doApp = Database.databaseThings().flatMap(Service.serviceThings)
```

これは正しく機能します。もしくは、少なくともコンパイルされます。ただし、別のモジュールがデータベースのみを使用して、`Either[AppError, DatabaseValue]`を取得する場合を考えてみます。エラーを検査する場合は、データベースが`DatabaseError1`または`DatabaseError2`を使用することのみを目的としていたとしても、全ての`AppError`ケースを検査する必要があります。

##解決策2：ADT(抽象データ型)をさらに深くへ
全てのエラーを1つの大きなADTにまとめる代わりに、それらを各モジュールに対してローカルに保ち、必要な各エラーADTをラップするアプリケーション全体のエラーADTを作成できます。

```Scala
sealed abstract class DatabaseError
trait DatabaseValue

object Database {
  def databaseThings(): Either[DatabaseError, DatabaseValue] = ???
}

sealed abstract class ServiceError
trait ServiceValue

object Service {
  def serviceThings(v: DatabaseValue): Either[ServiceError, ServiceValue] = ???
}

sealed abstract class AppError
object AppError {
  final case class Database(error: DatabaseError) extends AppError
  final case class Service(error: ServiceError) extends AppError
}
```

これで、外部アプリケーションで、各モジュール固有のエラーを`AppError`にラップ/リフトしてから、通常どおりコンビネータを呼び出すことができます。`Either`は、 `Either.leftMap`と呼ばれる、これを助ける便利なメソッドを提供しています。これは、`map`と同じと考えることができますが、`Left`に対して行います。

```Scala
def doApp: Either[AppError, ServiceValue] =
  Database.databaseThings().leftMap[AppError](AppError.Database).
  flatMap(dv => Service.serviceThings(dv).leftMap(AppError.Service))
```

やった！各モジュールは、本来あるべき独自のエラーのみを考慮し、さらに複合モジュールには、各構成モジュールのエラーADTをカプセル化する独自のエラーADTを持つようになりました！これを行うことで、個々のエラーのパターンマッチを行う代わりに、エラーのクラス全体に対してアクションを実行することもできます。

```Scala
def awesome =
  doApp match {
    case Left(AppError.Database(_)) => "something in the database went wrong"
    case Left(AppError.Service(_))  => "something in the service went wrong"
    case Right(_)                   => "everything is alright!"
  }
```

#exception-yコードの操作
あなたの素敵な`Either`コードが例外をスローするコードと相互作用しなければならない時が必然的に来るでしょう。このような状況への対処は簡単です。

```Scala
val either: Either[NumberFormatException, Int] =
  try {
    Either.right("abc".toInt)
  } catch {
    case nfe: NumberFormatException => Either.left(nfe)
  }
// either: Either[NumberFormatException, Int] = Left(
//   java.lang.NumberFormatException: For input string: "abc"
// )
```

ただし、これはすぐに面倒になる可能性があります。`Either`には、コンパニオンオブジェクトに（構文エンリッチメントを介して）`catchOnly`メソッドがあり、キャッチする例外の型とともに関数を渡すことができ、上記を実行します。

```Scala
val either: Either[NumberFormatException, Int] =
  Either.catchOnly[NumberFormatException]("abc".toInt)
// either: Either[NumberFormatException, Int] = Left(
//   java.lang.NumberFormatException: For input string: "abc"
// )
```

全ての（致命的ではない）スローアブルをキャッチしたい場合は、 `catchNonFatal`を使用できます。

```Scala
val either: Either[Throwable, Int] = Either.catchNonFatal("abc".toInt)
// either: Either[Throwable, Int] = Left(
//   java.lang.NumberFormatException: For input string: "abc"
// )
```

以上で[公式ドキュメント](https://typelevel.org/cats/datatypes/either.html)よりScala with Catsの`Either`について説明終わりです！

ありがとうございました！！🙇‍♂️