Either を使う時の逆引き辞典
前提
def someDomainLogic(foo: Foo): Either[FooError, Result]
という Either
を返すドメインロジックがあり、
val list: List[Foo] = ...
という複数の Foo
が存在している状況において、目的に合わせて someDomainLogic
をどの様に呼び出せばいいかを示します。
ここでは Scala 3.3.1 と cats-core 2.10.0 を使用します。
サンプルコードには以下の import が含まれているものとします。
import cats.*
import cats.data.*
import cats.implicits.*
List
として、失敗は最初の一つだけ得たい場合
全て成功すれば結果を list
内の全ての Foo
に対して someDomainLogic
を適用し、いずれか一つでも FooError
を返した場合は最初の FooError
を得、 FooError
が一つもなければ List[Result]
を得たい場合。
つまり fail fast の場合。
traverse
を使います。
def someDomainLogic(foo: Foo): Either[FooError, Result] = ...
val list: List[Foo] = ...
val results: Either[FooError, List[Result]] = list.traverse(someDomainLogic)
List
とし、失敗は全て集約して得たい場合
全て成功すれば結果を list
内の全ての Foo
に対して someDomainLogic
を適用し、FooError
を返すものがあった場合は全ての FooError
を得、 FooError
が一つもなければ List[Result]
を得たい場合。
この場合はエラーの集約を行うため、 Either
を扱うより Validated
を使う方がお勧めです。
もし someDomainLogic
の戻り値型を Validated
に変更する事が可能であれば最初にそれを検討しましょう。
someDomainLogic
のシグネチャを変更できない場合は Either
から Validated
に変換して traverse
しましょう。
// ドメインロジックを Validated に変更できた場合
def someDomainLogic(foo: Foo): ValidatedNec[FooError, Result] = ...
val list: List[Foo] = ...
val results: ValidatedNec[FooError, List[Result]] = list.traverse(someDomainLogic)
// ドメインロジックを Validated に変更できなかった場合
def someDomainLogic(foo: Foo): Either[FooError, Result] = ...
val list: List[Foo] = ...
val results: ValidatedNec[FooError, List[Result]] =
list.traverse(foo => someDomainLogic(foo).toValidatedNec)
成功したものと失敗したものをそれぞれ集めたい場合
List[FooError]
と List[Result]
の両方を得たい場合。
partitionMap
を使います。
def someDomainLogic(foo: Foo): Either[FooError, Result] = ...
val list: List[Foo] = ...
val (errors, results) = list.partitionMap(someDomainLogic)
println(errors) // List[FooError]
println(results) // List[Result]
ちなみに partitionMap
は標準APIにあるので cats を使っていなくても利用できます。
失敗は集約し、成功は件数だけ得たい場合
例えば someDomainLogic
の副作用だけが重要で結果は必要なく成功した件数だけ得たい場合。
もちろん 成功したものと失敗したものをそれぞれ集めたい場合 と同様に partitionMap
をして count しても良いですが、件数が多い等何らかの事情で無駄なオブジェクトを作りたくない場合には foldMap
を使います。
def someDomainLogic(foo: Foo): Either[FooError, Result] = ...
val list: List[Foo] = ...
val (errors, successCount) = list.foldMap { foo =>
someDomainLogic(foo).fold(
e => (Chain.one(e), 0L),
_ => (Chain.empty, 1L)
)
}
println(errors) // Chain[FooError]
println(successCount) // Long