for式の中のEitherでifを使いたい
scala 2.12ではJava8対応の変更が多く、派手な変更は多くはありませんでした。そんな中でEitherがright-biasedになったのは割とキャッチーなのではないでしょうか。Eitherには今までflatMapなどのメソッドがなかったので、for式の中で使うときなど、いったんRightProjectionに変換する必要があったのですが、それが必要なくなりました。
for { x <- Right(3).right } yield x
これが
for { x <- Right(3) } yield x
こう書けるようになりました。
これは便利ですね。まあrightが取れるだけなんですが、私もよく .right
つけろよとコンパイラに怒られていたので嬉しいです。
さて、これでscalaのEitherが実用的になったという声も聞こえるのですが、実際 .right
が不要になったから実用的かというと疑問で、どちらかというとscalaのEitherで困るのはfor式の中でifが使えないことだと思います。私はwithFilter問題と勝手に呼んでいてRightProjectionにwithFilterがないせいなんですが、この問題は2.12.xでもまだ健在です。
scala> for { | x <- Right(3) | if x % 2 == 1 | y = x + 1 | } yield y <console>:13: error: value withFilter is not a member of scala.util.Right[Nothing,Int] x <- Right(3) ^
ほら、if使えないでしょ。これは困りますね。そこでEitherに限った方法ではないんですが、ちょっとヘルパーを定義します。
def when[E](p: Boolean)(e: => E): Either[E, Unit] = if (p) Right(()) else Left(e)
このwhenはpの条件を満たせばRightを返すのでそれ以下のfor式も実行されます。満たさなければLeftを返してエラーにします。
scala> val result = for { | x <- Right(3) | _ <- when(x % 2 == 1)("error") | y = x + 1 | } yield y result: scala.util.Either[String,Int] = Right(4) scala> val result = for { | x <- Right(3) | _ <- when(x % 2 == 0)("error") | y = x + 1 | } yield y result: scala.util.Either[String,Int] = Left(error)
これでEitherでもifっぽいことをできるようになり、めでたしです。
追記
@gakuzzzzさんにEither.condの存在を教えられた。
def when[E](p: Boolean)(e: => E): Either[E, Unit] =
Either.cond(p, (), e)