ScalaのEither(Scala関数型デザインより)
「Scala関数型デザイン&プログラミング」の「第 4 章 例外 を 使わ ない エラー 処理」のEither編。
package example sealed trait Either[+E,+A] { def map[B](f: A => B): Either[E, B] = this match { case Right(a) => Right(f(a)) case Left(e) => Left(e) } def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match { case Right(a) => f(a) case Left(e) => Left(e) } def orElse[EE >: E, AA >: A](b: => Either[EE, AA]): Either[EE, AA] = this match { case Right(a) => Right(a) case Left(_) => b } def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] = for { a <- this; b1 <- b } yield f(a,b1) def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = es match { case Nil => Right(Nil) case Cons(h,t) => (f(h) map2 traverse(t)(f))(Cons(_,_)) } def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] = traverse(es)(x => x) } case class Left[+E](get: E) extends Either[E,Nothing] case class Right[+A](get: A) extends Either[Nothing,A] case class Person(name: Name, age: Age) sealed class Name(val value: String) sealed class Age(val value: Int) object Either { def mean(xs: IndexedSeq[Double]): Either[String, Double] = if (xs.isEmpty) Left("mean of empty list!") else Right(xs.sum / xs.length) def mkName(name: String): Either[String, Name] = if (name == "" || name == null) Left("Name is empty.") else Right( new Name( name)) def mkAge(age: Int): Either[String, Age] = if (age < 0) Left("Age is out of range.") else Right(new Age(age)) def mkPerson(name: String, age: Int): Either[ String, Person] = mkName(name).map2(mkAge(age))(Person(_, _)) def main(args: Array[String]): Unit = { val ds = IndexedSeq(1.1, 2.2, 3.3) val result: Either[String, Double] = mean(ds) println(s"result:${result}") result match { case Right(a) => println(s"a:${a}") case Left(e) => println(s"e:${e}") } val ei = Right(1.0) val res = ei.map((a) => a + 2.0) //(1.0) println(s"res:${res}") val eiErr = Left("Error!!") val errRes = eiErr.map((a: Double) => a + 2.0) //(1.0) println(s"errRes:${errRes}") println(s"errRes OrElse:${errRes.orElse(Left("the other side."))}") val misia = Right(1.0) val r2 = Right(2.0) val resultMap = misia.map2(r2)((a,b) => a + b) println(s"resultMap:${resultMap}") val p = mkPerson("Totti", 18) println(s"p:${p}") p match { case Right(pp) => { println(s"name:${pp.name.value}") println(s"age:${pp.age.value}") } case Left(e) => println("Error!") } } }
こちらもOption同様、トレイトで実装。
map flatMap orElse
def map[B](f: A => B): Either[E, B] = this match { case Right(a) => Right(f(a)) case Left(e) => Left(e) } def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match { case Right(a) => f(a) case Left(e) => Left(e) } def orElse[EE >: E, AA >: A](b: => Either[EE, AA]): Either[EE, AA] = this match { case Right(a) => Right(a) case Left(_) => b }
このパターンマッチングは簡単。
map2 traverse sequence
def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] = for { a <- this; b1 <- b } yield f(a,b1) def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = es match { case Nil => Right(Nil) case Cons(h,t) => (f(h) map2 traverse(t)(f))(Cons(_,_)) } def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] = traverse(es)(x => x)
map2は、for内包表記を使う。
「<- 」で、ほどいて、fを適用する。
「<- 」は、Haskellだと、束縛というらしい。
「<- 」は、flatMap & mapに展開される。
case class Person(name: Name, age: Age) sealed class Name(val value: String) sealed class Age(val value: Int) (略) val p = mkPerson("Totti", 18) println(s"p:${p}") p match { case Right(pp) => { println(s"name:${pp.name.value}") println(s"age:${pp.age.value}") } case Left(e) => println("Error!") }
NameとAgeをclassにしているため、valueを参照する必要があるため、使い勝手がいまいち。。 typeの方がよい?