少し前に、Javaの代わりにScalaを使い始めました。私にとって言語間の「変換」プロセスの一部は、Either
(チェックされた)Exception
s の代わりにs を使用することを学ぶことでした。私はしばらくこの方法でコーディングしてきましたが、最近、それが本当に良い方法かどうか疑問に思い始めました。
1つの大きな利点Either
は、Exception
パフォーマンスの向上です。Exception
スタックトレースを構築する必要があり、スローされています。ただし、私が理解している限りでは、投げることException
は難しい部分ではありませんが、スタックトレースを構築することは重要です。
しかし、その後、常にを使用してException
sを構築/継承することができます。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
。Exception
withを「キャッチ」する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 Left
replaces throw new Exception
、およびいずれかの暗黙的なメソッドrightOrReturn
は、スタックの自動例外伝播の補足です。
Try
です。Either
vs に関する部分は、メソッドの他のケースが「非例外的」である場合にsが使用されるべきであるとException
述べているだけEither
です。まず、これは非常に曖昧な定義です。第二に、それは本当に構文上のペナルティに値するのでしょうか?Either
つまり、構文のオーバーヘッドがなければ、s を使用してもかまいません。
Either
それ自体はモナドではありません。左側または右側への射影はモナドですがEither
、それ自体はそうではありません。ただし、左または右のいずれかに「バイアス」することで、モナドにすることができます。ただし、その後、の両側に特定のセマンティックを付与しますEither
。Scala Either
はもともと偏りがありませんでしたが、最近では偏っていたため、最近では実際にはモナドですが、「モナド」は固有のプロパティでEither
はなく、偏った結果です。