- 副作用である
- 型チェックできない(Scalaは非チェック例外しかない)
- try catch構文はめんどくさい(catchしてまた例外を投げたり、DRYに書けないことも多い)
- 例外が起きてほしくない場所がある(Futureの中など)
直和型を使って解決したい
直和型を使って解決したい
Option[A]
Either[A, B]
Validation[E, A]
scala.util.Try
を使うscala.util.control.Exception
を使って例外をOptionやEitherに変換するMonadとして使おうとすると、rightメソッドでRightProjectionに変換しなければいけない
for { a <- e1.right b <- e2.right } yield a + b
使いづらい…
さらに微妙な問題がある
for { a <- Right(1).right b = a + 1 } yield b
↓
error: value map is not a member of Serializable with Product with scala.util.Either[Nothing,(Int, Int)] a <- Right(1).right
!?
for { a <- Right(1).right b <- Right(2).right if a > 0 } yield a
↓
type mismatch; found : Option[Int] required: scala.util.Either[?,?] b <- Right(2).right
!?
EitherがMonadになればこんな問題は起きないのに!
val e1 = 1.right[String] val e2 = 2.right[String] for { a <- e1 b <- e2 c = a + b if c > 0 } yield c
短く問題なく使えて嬉しい!
sealed abstract class Validation[+E, +A] final case class Success[A](a: A) extends Validation[Nothing, A] final case class Failure[E](e: E) extends Validation[E, Nothing]
def ap[EE >: E, B](x: => Validation[EE, A => B]) (implicit E: Semigroup[EE]): Validation[EE, B] = (this, x) match { case (Success(a), Success(f)) => Success(f(a)) case (e @ Failure(_), Success(_)) => e case (Success(f), e @ Failure(_)) => e case (Failure(e1), Failure(e2)) => Failure(E.append(e2, e1)) }
case class Person(name: String, age: Int) def validateName(name: String) = if (name.length > 1) name.successNel else "invalid name".failureNel def validateAge(age: Int) = if (age >= 0) age.successNel else "invalid age".failureNel
scala> (validateName("Yoshida") |@| validateAge(27))(Person) res0: scalaz.Validation[scalaz.NonEmptyList[String],Person] = Success(Person(Yoshida,27)) scala> (validateName("") |@| validateAge(-1))(Person) res1: scalaz.Validation[scalaz.NonEmptyList[String],Person] = Failure(NonEmptyList(invalid name, invalid age))
case class Person(name: String, age: Int, job: String) def validateJob(job: String, age: Int) = if (6 <= age && age <= 15 && job != "Student") "invalid job".failureNel else job.successNel
じゃあ、こう書きたい!
for { n <- validateName(name) a <- validateAge(age) j <- validateJob(job, a) } yield Person(n, a, j)
でも書けない(◞‸◟)
ap
メソッドでエラーを集約してるところが問題になる
case (Failure(e1), Failure(e2)) => Failure(E.append(e2, e1))
flatMap(bind)
を使ってap
を実装することができる(逆はできない)def ap[A, B](fa: => F[A])(f: => F[A => B]): F[B] = bind(f)(map(fa)) = f flatMap (fa map _)
ValidationがMonadだと仮定する
def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = self match { case Success(a) => f(a) case e @ Failure(_) => e }
OptionやEitherと同じようにflatMapを定義すると以上のようになる
FailureのapメソッドにFailureを適用してみる
Failure(e1) ap Failure(e2) === Failure(e1 |+| e2)
flatMapで作られたapメソッドにFailureを適用してみる
Failure(e1) ap Failure(e2) === Failure(e2) flatMap (Failure(e1) map _) === Failure(e2) // エラーが集約されていない!
違う結果になる
→ flatMapでapを作ることができない
→ ValidationはMonadになることはできない
ちなみにこのようなflatMapも定義されていて
import scalaz.Validation.FlatMap._
とすれば使うことができるがエラーが集約されないので注意
Eitherとほぼ同じなのでEitherを使いましょう
Important contact information goes here.