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の方がよい?