summaryrefslogtreecommitdiff
path: root/src/library/scala/Either.scala
diff options
context:
space:
mode:
authorHeather Miller <heather.miller@epfl.ch>2011-10-16 23:13:00 +0000
committerHeather Miller <heather.miller@epfl.ch>2011-10-16 23:13:00 +0000
commit240fb16547001e37130a7e2f0b20f15863efddda (patch)
treecf98c24caff2995f233f88c264a44f888ed1940f /src/library/scala/Either.scala
parent3778505276e442ab7001b639f66a15efd5a30566 (diff)
downloadscala-240fb16547001e37130a7e2f0b20f15863efddda.tar.gz
scala-240fb16547001e37130a7e2f0b20f15863efddda.tar.bz2
scala-240fb16547001e37130a7e2f0b20f15863efddda.zip
Big improvements to scala.Either.
Diffstat (limited to 'src/library/scala/Either.scala')
-rw-r--r--src/library/scala/Either.scala301
1 files changed, 287 insertions, 14 deletions
diff --git a/src/library/scala/Either.scala b/src/library/scala/Either.scala
index 21d4d5c855..bc75f0f088 100644
--- a/src/library/scala/Either.scala
+++ b/src/library/scala/Either.scala
@@ -10,11 +10,56 @@
package scala
-/** Represents a value of one of two possible types (a disjoint union).
- * The data constructors [[scala.Left]] and [[scala.Right]] represent
- * the two possible values.
- * The `Either` type is often used as an alternative to [[scala.Option]]
- * where `Left` represents failure (by convention) and `Right` is akin to `Some`.
+/** Represents a value of one of two possible types (a disjoint union.)
+ * Instances of Either are either an instance of [[scala.Left]] or [[scala.Right]].
+ *
+ * A common use of Either is as an alternative to [[scala.Option]] for dealing
+ * with possible missing values. In this usage, [[scala.None]] is replaced
+ * with a [[scala.Left]] which can contain useful information.
+ * [[scala.Right]] takes the place of [[scala.Some]]. Convention dictates
+ * that Left is used for failure and Right is used for success.
+ *
+ * For example, you could use ``Either[String, Int]`` to detect whether a
+ * received input is a String or an Int.
+ *
+ * {{{
+ * val in = Console.readLine("Type Either a string or an Int: ")
+ * val result: Either[String,Int] = try {
+ * Right(in.toInt)
+ * } catch {
+ * case e: Exception =>
+ * Left(in)
+ * }
+ *
+ * println( result match {
+ * case Right(x) => "You passed me the Int: " + x + ", which I will increment. " + x + " + 1 = " + (x+1)
+ * case Left(x) => "You passed me the String: " + x
+ * })
+ * }}}
+ *
+ * A ''projection'' can be used to selectively operate on a value of type Either,
+ * depending on whether it is of type Left or Right. For example, to transform an
+ * Either using a function, in the case where it's a Left, one can first apply
+ * the `left` projection and invoke `map` on that projected Either. If a `right`
+ * projection is applied to that Left, the original Left is returned, unmodified.
+ *
+ * {{{
+ * val l: Either[String, Int] = Left("flower")
+ * val r: Either[String, Int] = Right(12)
+ * l.left.map(_.size): Either[Int, Int] // Left(6)
+ * r.left.map(_.size): Either[Int, Int] // Right(12)
+ * l.right.map(_.toDouble): Either[String, Double] // Left("flower")
+ * r.right.map(_.toDouble): Either[String, Double] // Right(12.0)
+ * }}}
+ *
+ * Like with other types which define a `map` method, the same can be achieved
+ * using a for-comprehension:
+ * {{{
+ * for (s <- l.left) yield s.size // Left(6)
+ * }}}
+ *
+ * To support multiple projections as generators in for-comprehensions, the Either
+ * type also defines a `flatMap` method.
*
* @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse
* @version 1.0, 11/10/2008
@@ -34,6 +79,14 @@ sealed abstract class Either[+A, +B] {
/**
* Applies `fa` if this is a `Left` or `fb` if this is a `Right`.
*
+ * @example {{{
+ * val result: Either[Exception, Value] = possiblyFailingOperation()
+ * log(result.fold(
+ * ex => "Operation failed with " + ex,
+ * v => "Operation produced value: " + v
+ * ))
+ * }}}
+ *
* @param fa the function to apply if this is a `Left`
* @param fb the function to apply if this is a `Right`
* @return the results of applying the function
@@ -45,6 +98,11 @@ sealed abstract class Either[+A, +B] {
/**
* If this is a `Left`, then return the left value in `Right` or vice versa.
+ *
+ * @example {{{
+ * val l: Either[String, Int] = Left("left")
+ * val r: Either[Int, String] = l.swap // Result: Right("left")
+ * }}}
*/
def swap = this match {
case Left(a) => Right(a)
@@ -53,6 +111,22 @@ sealed abstract class Either[+A, +B] {
/**
* Joins an `Either` through `Right`.
+ *
+ * This method requires that the right side of this Either is itself an
+ * Either type. That is, this must be some type like: {{{
+ * Either[A, Either[A, C]]
+ * }}} (which respects the type parameter bounds, shown below.)
+ *
+ * If this instance is a Right[Either[A, C]] then the contained Either[A, C]
+ * will be returned, otherwise this value will be returned unmodified.
+ *
+ * @example {{{
+ * Right[String, Either[String, Int]](Right(12)).joinRight // Result: Right(12)
+ * Right[String, Either[String, Int]](Left("flower")).joinRight // Result: Left("flower")
+ * Left[String, Either[String, Int]]("flower").joinRight // Result: Left("flower")
+ * }}}
+ *
+ * This method, and `joinLeft`, are analogous to `Option#flatten`
*/
def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
case Left(a) => Left(a)
@@ -61,6 +135,22 @@ sealed abstract class Either[+A, +B] {
/**
* Joins an `Either` through `Left`.
+ *
+ * This method requires that the left side of this Either is itself an
+ * Either type. That is, this must be some type like: {{{
+ * Either[Either[C, B], B]
+ * }}} (which respects the type parameter bounds, shown below.)
+ *
+ * If this instance is a Left[Either[C, B]] then the contained Either[C, B]
+ * will be returned, otherwise this value will be returned unmodified.
+ *
+ * {{{
+ * Left[Either[Int, String], String](Right("flower")).joinLeft // Result: Right("flower")
+ * Left[Either[Int, String], String](Left(12)).joinLeft // Result: Left(12)
+ * Right[Either[Int, String], String]("daisy").joinLeft // Result: Right("daisy")
+ * }}}
+ *
+ * This method, and `joinRight`, are analogous to `Option#flatten`
*/
def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
case Left(a) => a
@@ -69,17 +159,27 @@ sealed abstract class Either[+A, +B] {
/**
* Returns `true` if this is a `Left`, `false` otherwise.
+ *
+ * {{{
+ * Left("tulip").isLeft // true
+ * Right("venus fly-trap").isLeft // false
+ * }}}
*/
def isLeft: Boolean
/**
* Returns `true` if this is a `Right`, `false` otherwise.
+ *
+ * {{{
+ * Left("tulip").isRight // false
+ * Right("venus fly-trap").isRight // true
+ * }}}
*/
def isRight: Boolean
}
/**
- * The left side of the disjoint union, as opposed to the `Right` side.
+ * The left side of the disjoint union, as opposed to the [[scala.Right]] side.
*
* @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse
* @version 1.0, 11/10/2008
@@ -90,7 +190,7 @@ final case class Left[+A, +B](a: A) extends Either[A, B] {
}
/**
- * The right side of the disjoint union, as opposed to the `Left` side.
+ * The right side of the disjoint union, as opposed to the [[scala.Left]] side.
*
* @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse
* @version 1.0, 11/10/2008
@@ -108,11 +208,65 @@ object Either {
}
}
+ /**
+ * Allows use of a ``merge`` method to extract values from Either instances
+ * regardless of whether they are Left or Right.
+ *
+ * {{{
+ * val l = Left(List(1)): Either[List[Int], Vector[Int]]
+ * val r = Right(Vector(1)): Either[List[Int], Vector[Int]]
+ * l.merge: Seq[Int] // List(1)
+ * r.merge: Seq[Int] // Vector(1)
+ * }}}
+ */
implicit def either2mergeable[A](x: Either[A, A]): MergeableEither[A] = new MergeableEither(x)
/**
* Projects an `Either` into a `Left`.
*
+ * This allows for-comprehensions over Either instances - for example {{{
+ * for (s <- Left("flower").left) yield s.length // Left(6)
+ * }}}
+ *
+ * Continuing the analogy with [[scala.Option]], a `LeftProjection` declares
+ * that `Left` should be analogous to `Some` in some code.
+ *
+ * {{{
+ * // using Option:
+ * def interactWithDB(x: Query): Option[Result] =
+ * try {
+ * Some(getResultFromDatabase(x))
+ * } catch {
+ * case ex => None
+ * }
+ *
+ * // this will only be executed if interactWithDB returns a Some
+ * val report =
+ * for (r <- interactWithDB(someQuery)) yield generateReport(r)
+ * if (report.isDefined)
+ * send(report)
+ * else
+ * log("report not generated, not sure why...")
+ * }}}
+ *
+ * {{{
+ * // using Either
+ * def interactWithDB(x: Query): Either[Exception, Result] =
+ * try {
+ * Right(getResultFromDatabase(x))
+ * } catch {
+ * case ex => Left(ex)
+ * }
+ *
+ * // this will only be executed if interactWithDB returns a Some
+ * val report =
+ * for (r <- interactWithDB(someQuery).right) yield generateReport(r)
+ * if (report.isRight)
+ * send(report)
+ * else
+ * log("report not generated, reason was " + report.left.get)
+ * }}}
+ *
* @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse
* @version 1.0, 11/10/2008
*/
@@ -121,7 +275,12 @@ object Either {
* Returns the value from this `Left` or throws `Predef.NoSuchElementException`
* if this is a `Right`.
*
- * @throws Predef.NoSuchElementException if the option is empty.
+ * {{{
+ * Left(12).left.get // 12
+ * Right(12).left.get // NoSuchElementException
+ * }}}
+ *
+ * @throws Predef.NoSuchElementException if the projection is [[scala.Right]]
*/
def get = e match {
case Left(a) => a
@@ -129,9 +288,13 @@ object Either {
}
/**
- * Executes the given side-effect if this is a `Left`.
+ * Executes the given side-effecting function if this is a `Left`.
*
- * @param e The side-effect to execute.
+ * {{{
+ * Left(12).left.foreach(x => println(x)) // prints "12"
+ * Right(12).left.foreach(x => println(x)) // doesn't print
+ * }}}
+ * @param e The side-effecting function to execute.
*/
def foreach[U](f: A => U) = e match {
case Left(a) => f(a)
@@ -141,6 +304,12 @@ object Either {
/**
* Returns the value from this `Left` or the given argument if this is a
* `Right`.
+ *
+ * {{{
+ * Left(12).left.getOrElse(17) // 12
+ * Right(12).left.getOrElse(17) // 17
+ * }}}
+ *
*/
def getOrElse[AA >: A](or: => AA) = e match {
case Left(a) => a
@@ -150,6 +319,13 @@ object Either {
/**
* Returns `true` if `Right` or returns the result of the application of
* the given function to the `Left` value.
+ *
+ * {{{
+ * Left(12).left.forall(_ > 10) // true
+ * Left(7).left.forall(_ > 10) // false
+ * Right(12).left.forall(_ > 10) // true
+ * }}}
+ *
*/
def forall(f: A => Boolean) = e match {
case Left(a) => f(a)
@@ -159,6 +335,13 @@ object Either {
/**
* Returns `false` if `Right` or returns the result of the application of
* the given function to the `Left` value.
+ *
+ * {{{
+ * Left(12).left.exists(_ > 10) // true
+ * Left(7).left.exists(_ > 10) // false
+ * Right(12).left.exists(_ > 10) // false
+ * }}}
+ *
*/
def exists(f: A => Boolean) = e match {
case Left(a) => f(a)
@@ -168,6 +351,10 @@ object Either {
/**
* Binds the given function across `Left`.
*
+ * {{{
+ * Left(12).left.flatMap(x => Left("scala")) // Left("scala")
+ * Right(12).left.flatMap(x => Left("scala") // Right(12)
+ * }}}
* @param The function to bind across `Left`.
*/
def flatMap[BB >: B, X](f: A => Either[X, BB]) = e match {
@@ -177,6 +364,11 @@ object Either {
/**
* Maps the function argument through `Left`.
+ *
+ * {{{
+ * Left(12).left.map(_ + 2) // Left(14)
+ * Right[Int, Int](12).left.map(_ + 2) // Right(12)
+ * }}}
*/
def map[X](f: A => X) = e match {
case Left(a) => Left(f(a))
@@ -186,6 +378,12 @@ object Either {
/**
* Returns `None` if this is a `Right` or if the given predicate
* `p` does not hold for the left value, otherwise, returns a `Left`.
+ *
+ * {{{
+ * Left(12).left.filter(_ > 10) // Some(Left(12))
+ * Left(7).left.filter(_ > 10) // None
+ * Right(12).left.filter(_ > 10) // None
+ * }}}
*/
def filter[Y](p: A => Boolean): Option[Either[A, Y]] = e match {
case Left(a) => if(p(a)) Some(Left(a)) else None
@@ -195,6 +393,11 @@ object Either {
/**
* Returns a `Seq` containing the `Left` value if it exists or an empty
* `Seq` if this is a `Right`.
+ *
+ * {{{
+ * Left(12).left.toSeq // Seq(12)
+ * Right(12).left.toSeq // Seq()
+ * }}}
*/
def toSeq = e match {
case Left(a) => Seq(a)
@@ -204,6 +407,11 @@ object Either {
/**
* Returns a `Some` containing the `Left` value if it exists or a
* `None` if this is a `Right`.
+ *
+ * {{{
+ * Left(12).left.toOption // Some(12)
+ * Right(12).left.toOption // None
+ * }}}
*/
def toOption = e match {
case Left(a) => Some(a)
@@ -214,14 +422,29 @@ object Either {
/**
* Projects an `Either` into a `Right`.
*
+ * This allows for-comprehensions over Either instances - for example {{{
+ * for (s <- Right("flower").right) yield s.length // Right(6)
+ * }}}
+ *
+ * Continuing the analogy with [[scala.Option]], a `RightProjection` declares
+ * that `Right` should be analogous to `Some` in some code.
+ *
+ * Analogous to `LeftProjection`, see example usage in its documentation above.
+ *
* @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse
* @version 1.0, 11/10/2008
*/
final case class RightProjection[+A, +B](e: Either[A, B]) {
+
/**
* Returns the value from this `Right` or throws
* `Predef.NoSuchElementException` if this is a `Left`.
*
+ * {{{
+ * Right(12).right.get // 12
+ * Left(12).right.get // NoSuchElementException
+ * }}}
+ *
* @throws Predef.NoSuchElementException if the projection is `Left`.
*/
def get = e match {
@@ -230,9 +453,13 @@ object Either {
}
/**
- * Executes the given side-effect if this is a `Right`.
+ * Executes the given side-effecting function if this is a `Right`.
*
- * @param e The side-effect to execute.
+ * {{{
+ * Right(12).right.foreach(x => println(x)) // prints "12"
+ * Left(12).right.foreach(x => println(x)) // doesn't print
+ * }}}
+ * @param e The side-effecting function to execute.
*/
def foreach[U](f: B => U) = e match {
case Left(_) => {}
@@ -242,6 +469,11 @@ object Either {
/**
* Returns the value from this `Right` or the given argument if this is a
* `Left`.
+ *
+ * {{{
+ * Right(12).right.getOrElse(17) // 12
+ * Left(12).right.getOrElse(17) // 17
+ * }}}
*/
def getOrElse[BB >: B](or: => BB) = e match {
case Left(_) => or
@@ -251,6 +483,12 @@ object Either {
/**
* Returns `true` if `Left` or returns the result of the application of
* the given function to the `Right` value.
+ *
+ * {{{
+ * Right(12).right.forall(_ > 10) // true
+ * Right(7).right.forall(_ > 10) // false
+ * Left(12).right.forall(_ > 10) // true
+ * }}}
*/
def forall(f: B => Boolean) = e match {
case Left(_) => true
@@ -260,6 +498,12 @@ object Either {
/**
* Returns `false` if `Left` or returns the result of the application of
* the given function to the `Right` value.
+ *
+ * {{{
+ * Right(12).right.exists(_ > 10) // true
+ * Right(7).right.exists(_ > 10) // false
+ * Left(12).right.exists(_ > 10) // false
+ * }}}
*/
def exists(f: B => Boolean) = e match {
case Left(_) => false
@@ -277,7 +521,12 @@ object Either {
}
/**
- * Maps the function argument through `Right`.
+ * The given function is applied if this is a `Right`.
+ *
+ * {{{
+ * Right(12).right.map(x => "flower") // Result: Right("flower")
+ * Left(12).right.map(x => "flower") // Result: Left(12)
+ * }}}
*/
def map[Y](f: B => Y) = e match {
case Left(a) => Left(a)
@@ -287,6 +536,12 @@ object Either {
/** Returns `None` if this is a `Left` or if the
* given predicate `p` does not hold for the right value,
* otherwise, returns a `Right`.
+ *
+ * {{{
+ * Right(12).right.filter(_ > 10) // Some(Right(12))
+ * Right(7).right.filter(_ > 10) // None
+ * Left(12).right.filter(_ > 10) // None
+ * }}}
*/
def filter[X](p: B => Boolean): Option[Either[X, B]] = e match {
case Left(_) => None
@@ -295,6 +550,11 @@ object Either {
/** Returns a `Seq` containing the `Right` value if
* it exists or an empty `Seq` if this is a `Left`.
+ *
+ * {{{
+ * Right(12).right.toSeq // Seq(12)
+ * Left(12).right.toSeq // Seq()
+ * }}}
*/
def toSeq = e match {
case Left(_) => Seq.empty
@@ -303,6 +563,11 @@ object Either {
/** Returns a `Some` containing the `Right` value
* if it exists or a `None` if this is a `Left`.
+ *
+ * {{{
+ * Right(12).right.toOption // Some(12)
+ * Left(12).right.toOption // None
+ * }}}
*/
def toOption = e match {
case Left(_) => None
@@ -310,8 +575,16 @@ object Either {
}
}
- /** If the condition satisfies, return the given `A` in `Left`,
+ /** If the condition is satisfied, return the given `A` in `Left`,
* otherwise, return the given `B` in `Right`.
+ *
+ * {{{
+ * val userInput: String = ...
+ * Either.cond(
+ * userInput.forall(_.isDigit) && userInput.size == 10,
+ * "The input (%s) does not look like a phone number".format(userInput),
+ * PhoneNumber(userInput)
+ * }}}
*/
def cond[A, B](test: Boolean, right: => B, left: => A): Either[A, B] =
if (test) Right(right) else Left(left)