少し前に、Javaの代わりにScalaを使い始めました。私にとって言語間の「変換」プロセスの一部は、Either(チェックされた)Exceptions の代わりにs を使用することを学ぶことでした。私はしばらくこの方法でコーディングしてきましたが、最近、それが本当に良い方法かどうか疑問に思い始めました。
1つの大きな利点Eitherは、Exceptionパフォーマンスの向上です。Exceptionスタックトレースを構築する必要があり、スローされています。ただし、私が理解している限りでは、投げることExceptionは難しい部分ではありませんが、スタックトレースを構築することは重要です。
しかし、その後、常にを使用してExceptionsを構築/継承することができます。scala.util.control.NoStackTraceさらにEither、実際にはの左側が実際にException(パフォーマンスブーストを控える)であるケースがたくさんあります。
もう1つの利点Eitherは、コンパイラの安全性です。ScalaコンパイラExceptionは、(Javaのコンパイラとは異なり)ハンドルされていないs について文句を言いません。しかし、私が間違っていなければ、この決定は、このトピックで議論されているのと同じ推論で推論されます。
構文の面では、Exception-styleの方がはるかに明確だと感じています。次のコードブロックを調べます(どちらも同じ機能を実現しています)。
Either スタイル:
def compute(): Either[String, Int] = {
val aEither: Either[String, String] = if (someCondition) Right("good") else Left("bad")
val bEithers: Iterable[Either[String, Int]] = someSeq.map {
item => if (someCondition(item)) Right(item.toInt) else Left("bad")
}
for {
a <- aEither.right
bs <- reduce(bEithers).right
ignore <- validate(bs).right
} yield compute(a, bs)
}
def reduce[A,B](eithers: Iterable[Either[A,B]]): Either[A, Iterable[B]] = ??? // utility code
def validate(bs: Iterable[Int]): Either[String, Unit] = if (bs.sum > 22) Left("bad") else Right()
def compute(a: String, bs: Iterable[Int]): Int = ???
Exception スタイル:
@throws(classOf[ComputationException])
def compute(): Int = {
val a = if (someCondition) "good" else throw new ComputationException("bad")
val bs = someSeq.map {
item => if (someCondition(item)) item.toInt else throw new ComputationException("bad")
}
if (bs.sum > 22) throw new ComputationException("bad")
compute(a, bs)
}
def compute(a: String, bs: Iterable[Int]): Int = ???
後者は私にはかなりきれいに見え、障害を処理するコード(パターンマッチングon Eitherまたはtry-catch)はどちらの場合も非常に明確です。
だから私の質問は-なぜEither(チェック済み)を使用するのExceptionですか?
更新
答えを読んだ後、私は自分のジレンマの核心を示すことに失敗したかもしれないことに気付きました。私の懸念は;の欠如ではありませんtry-catch。Exceptionwithを「キャッチ」するTryか、catchを使用して例外をラップできますLeft。
Either/に関する主な問題はTry、途中で多くの点で失敗する可能性のあるコードを書くときに発生します。これらのシナリオでは、障害が発生した場合、その障害をコード全体に伝播する必要があるため、コードがより面倒になります(前述の例に示すように)。
Exceptionを使用してs なしでコードを壊す別の方法が実際にあります(実際にreturnはScalaの別の「タブー」です)。コードはまだEitherアプローチよりも明確Exceptionであり、スタイルよりも少しクリーンではありますが、キャッチされないという恐れはありませんException。
def compute(): Either[String, Int] = {
val a = if (someCondition) "good" else return Left("bad")
val bs: Iterable[Int] = someSeq.map {
item => if (someCondition(item)) item.toInt else return Left("bad")
}
if (bs.sum > 22) return Left("bad")
val c = computeC(bs).rightOrReturn(return _)
Right(computeAll(a, bs, c))
}
def computeC(bs: Iterable[Int]): Either[String, Int] = ???
def computeAll(a: String, bs: Iterable[Int], c: Int): Int = ???
implicit class ConvertEither[L, R](either: Either[L, R]) {
def rightOrReturn(f: (Left[L, R]) => R): R = either match {
case Right(r) => r
case Left(l) => f(Left(l))
}
}
基本的に、return Leftreplaces throw new Exception、およびいずれかの暗黙的なメソッドrightOrReturnは、スタックの自動例外伝播の補足です。
Tryです。Eithervs に関する部分は、メソッドの他のケースが「非例外的」である場合にsが使用されるべきであるとException述べているだけEitherです。まず、これは非常に曖昧な定義です。第二に、それは本当に構文上のペナルティに値するのでしょうか?Eitherつまり、構文のオーバーヘッドがなければ、s を使用してもかまいません。
Eitherそれ自体はモナドではありません。左側または右側への射影はモナドですがEither、それ自体はそうではありません。ただし、左または右のいずれかに「バイアス」することで、モナドにすることができます。ただし、その後、の両側に特定のセマンティックを付与しますEither。Scala Eitherはもともと偏りがありませんでしたが、最近では偏っていたため、最近では実際にはモナドですが、「モナド」は固有のプロパティでEitherはなく、偏った結果です。