summaryrefslogtreecommitdiff
path: root/src/library/scala/util
diff options
context:
space:
mode:
authorSimon Ochsenreither <simon@ochsenreither.de>2016-04-27 21:25:57 +0200
committerSimon Ochsenreither <simon@ochsenreither.de>2016-05-27 15:01:47 +0200
commit6e9faf5157132ad575c98e5a9a0919ac38b7beb8 (patch)
tree1230333b98544c28b26e7c9e55d7581c62f323bd /src/library/scala/util
parentf7b575de222befb2cd1dd2f676e177ece8a1d234 (diff)
downloadscala-6e9faf5157132ad575c98e5a9a0919ac38b7beb8.tar.gz
scala-6e9faf5157132ad575c98e5a9a0919ac38b7beb8.tar.bz2
scala-6e9faf5157132ad575c98e5a9a0919ac38b7beb8.zip
Right-bias Either
- Add operations like map, flatMap which assume right-bias - Deprecate {Left,Right}Projection - Deprecate left and right in favor of swap - Add contains, toOption, toTry, toSeq and filterOrElse - toSeq returns collection.immutable.Seq instead of collection.Seq - Don't add get There are no incompatible changes. The only possibility of breakage that exists is when people have added extension methods named map, flatMap etc. to Either in the past doing something different than the methods added to Either now. One detail that moved the scales in favor of deprecating LeftProjection and RightProjection was the desire to have toSeq return scala.collection.immutable.Seq instead of scala.collection.Seq like LeftProjection and RightProjection do. Therefore keeping LeftProjection and RightProjection would introduce inconsistency. filter is called filterOrElse because filtering in a for-comprehension doesn't work if the method needs an explicit argument. contains was added as safer alternative to if (either.isRight && either.right.get == $something) ... While adding filter with an implicit zero value is possible, it's dangerous as it would require that developers add a "naked" implicit value of type A to their scope and it would close the door to a future in which the Scala standard library ships with Monoid and filter could exist with an implicit Monoid parameter.
Diffstat (limited to 'src/library/scala/util')
-rw-r--r--src/library/scala/util/Either.scala312
1 files changed, 236 insertions, 76 deletions
diff --git a/src/library/scala/util/Either.scala b/src/library/scala/util/Either.scala
index 01da0c1ef2..6da39692c5 100644
--- a/src/library/scala/util/Either.scala
+++ b/src/library/scala/util/Either.scala
@@ -32,12 +32,21 @@ package util
* 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
+ * println(result match {
+ * case Right(x) => s"You passed me the Int: $x, which I will increment. $x + 1 = ${x+1}"
+ * case Left(x) => s"You passed me the String: $x"
* })
* }}}
*
+ * Either is right-biased, which means that Right is assumed to be the default case to
+ * operate on. If it is Left, operations like map, flatMap, ... return the Left value
+ * unchanged:
+ *
+ * {{{
+ * Right(12).map(_ * 2) // Right(24)
+ * Left(23).map(_ * 2) // Left(23)
+ * }}}
+ *
* 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
@@ -70,11 +79,13 @@ sealed abstract class Either[+A, +B] extends Product with Serializable {
/**
* Projects this `Either` as a `Left`.
*/
+ @deprecated("use swap instead", "2.12.0")
def left = Either.LeftProjection(this)
/**
* Projects this `Either` as a `Right`.
*/
+ @deprecated("Either is now right-biased", "2.12.0")
def right = Either.RightProjection(this)
/**
@@ -83,8 +94,8 @@ sealed abstract class Either[+A, +B] extends Product with Serializable {
* @example {{{
* val result: Either[Exception, Value] = possiblyFailingOperation()
* log(result.fold(
- * ex => "Operation failed with " + ex,
- * v => "Operation produced value: " + v
+ * ex => s"Operation failed with $ex",
+ * v => s"Operation produced value: $v"
* ))
* }}}
*
@@ -92,9 +103,9 @@ sealed abstract class Either[+A, +B] extends Product with Serializable {
* @param fb the function to apply if this is a `Right`
* @return the results of applying the function
*/
- def fold[X](fa: A => X, fb: B => X) = this match {
- case Left(a) => fa(a)
+ def fold[C](fa: A => C, fb: B => C): C = this match {
case Right(b) => fb(b)
+ case Left(a) => fa(a)
}
/**
@@ -105,8 +116,8 @@ sealed abstract class Either[+A, +B] extends Product with Serializable {
* val r: Either[Int, String] = l.swap // Result: Right("left")
* }}}
*/
- def swap = this match {
- case Left(a) => Right(a)
+ def swap: Either[B, A] = this match {
+ case Left(a) => Right(a)
case Right(b) => Left(b)
}
@@ -130,8 +141,9 @@ sealed abstract class Either[+A, +B] extends Product with Serializable {
* 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)
case Right(b) => b
+ case Left(a) => this.asInstanceOf[Either[A1, C]]
+
}
/**
@@ -155,7 +167,155 @@ sealed abstract class Either[+A, +B] extends Product with Serializable {
*/
def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
case Left(a) => a
- case Right(b) => Right(b)
+ case Right(b) => this.asInstanceOf[Either[C, B1]]
+ }
+
+ /**
+ * Executes the given side-effecting function if this is a `Right`.
+ *
+ * {{{
+ * Right(12).foreach(x => println(x)) // prints "12"
+ * Left(12).foreach(x => println(x)) // doesn't print
+ * }}}
+ * @param f The side-effecting function to execute.
+ */
+ def foreach[U](f: B => U): Unit = this match {
+ case Right(b) => f(b)
+ case Left(_) =>
+ }
+
+ /**
+ * Returns the value from this `Right` or the given argument if this is a `Left`.
+ *
+ * {{{
+ * Right(12).getOrElse(17) // 12
+ * Left(12).getOrElse(17) // 17
+ * }}}
+ */
+ def getOrElse[BB >: B](or: => BB): BB = this match {
+ case Right(b) => b
+ case Left(_) => or
+ }
+
+ /** Returns `true` if this is a `Right` and its value is equal to `elem` (as determined by `==`),
+ * returns `false` otherwise.
+ *
+ * {{{
+ * // Returns true because value of Right is "something" which equals "something".
+ * Right("something") contains "something"
+ *
+ * // Returns false because value of Right is "something" which does not equal "anything".
+ * Right("something") contains "anything"
+ *
+ * // Returns false because there is no value for Right.
+ * Left("something") contains "something"
+ * }}}
+ *
+ * @param elem the element to test.
+ * @return `true` if the option has an element that is equal (as determined by `==`) to `elem`, `false` otherwise.
+ */
+ final def contains[AA >: A](elem: AA): Boolean = this match {
+ case Right(b) => b == elem
+ case Left(_) => false
+ }
+
+ /**
+ * Returns `true` if `Left` or returns the result of the application of
+ * the given function to the `Right` value.
+ *
+ * {{{
+ * Right(12).forall(_ > 10) // true
+ * Right(7).forall(_ > 10) // false
+ * Left(12).forall(_ > 10) // true
+ * }}}
+ */
+ def forall(f: B => Boolean): Boolean = this match {
+ case Right(b) => f(b)
+ case Left(_) => true
+ }
+
+ /**
+ * Returns `false` if `Left` or returns the result of the application of
+ * the given function to the `Right` value.
+ *
+ * {{{
+ * Right(12).exists(_ > 10) // true
+ * Right(7).exists(_ > 10) // false
+ * Left(12).exists(_ > 10) // false
+ * }}}
+ */
+ def exists(p: B => Boolean): Boolean = this match {
+ case Right(b) => p(b)
+ case Left(_) => false
+ }
+
+ /**
+ * Binds the given function across `Right`.
+ *
+ * @param f The function to bind across `Right`.
+ */
+ def flatMap[AA >: A, Y](f: B => Either[AA, Y]): Either[AA, Y] = this match {
+ case Right(b) => f(b)
+ case Left(a) => this.asInstanceOf[Either[AA, Y]]
+ }
+
+ /**
+ * The given function is applied if this is a `Right`.
+ *
+ * {{{
+ * Right(12).map(x => "flower") // Result: Right("flower")
+ * Left(12).map(x => "flower") // Result: Left(12)
+ * }}}
+ */
+ def map[Y](f: B => Y): Either[A, Y] = this match {
+ case Right(b) => Right(f(b))
+ case Left(a) => this.asInstanceOf[Either[A, Y]]
+ }
+
+ /** Returns `Right` with the existing value of `Right` if this is a `Right` and the given predicate `p` holds for the right value,
+ * returns `Left(zero)` if this is a `Right` and the given predicate `p` does not hold for the right value,
+ * returns `Left` with the existing value of `Left` if this is a `Left`.
+ *
+ * {{{
+ * Right(12).filterOrElse(_ > 10, -1) // Right(12)
+ * Right(7).filterOrElse(_ > 10, -1) // Left(-1)
+ * Left(12).filterOrElse(_ > 10, -1) // Left(12)
+ * }}}
+ */
+ def filterOrElse[AA >: A](p: B => Boolean, zero: => AA): Either[AA, B] = this match {
+ case Right(b) => if (p(b)) this else Left(zero)
+ case Left(a) => this
+ }
+
+ /** Returns a `Seq` containing the `Right` value if
+ * it exists or an empty `Seq` if this is a `Left`.
+ *
+ * {{{
+ * Right(12).toSeq // Seq(12)
+ * Left(12).toSeq // Seq()
+ * }}}
+ */
+ def toSeq: collection.immutable.Seq[B] = this match {
+ case Right(b) => collection.immutable.Seq(b)
+ case Left(_) => collection.immutable.Seq.empty
+ }
+
+ /** Returns a `Some` containing the `Right` value
+ * if it exists or a `None` if this is a `Left`.
+ *
+ * {{{
+ * Right(12).toOption // Some(12)
+ * Left(12).toOption // None
+ * }}}
+ */
+ def toOption: Option[B] = this match {
+ case Right(b) => Some(b)
+ case Left(_) => None
+ }
+
+ def toTry(implicit ev: A <:< Throwable): Try[B] = this match {
+ case Right(b) => Success(b)
+ case Left(a) => Failure(a)
}
/**
@@ -186,7 +346,7 @@ sealed abstract class Either[+A, +B] extends Product with Serializable {
* @version 1.0, 11/10/2008
*/
final case class Left[+A, +B](a: A) extends Either[A, B] {
- def isLeft = true
+ def isLeft = true
def isRight = false
}
@@ -197,12 +357,26 @@ final case class Left[+A, +B](a: A) extends Either[A, B] {
* @version 1.0, 11/10/2008
*/
final case class Right[+A, +B](b: B) extends Either[A, B] {
- def isLeft = false
+ def isLeft = false
def isRight = true
}
object Either {
+ /** If the condition is satisfied, return the given `B` in `Right`,
+ * otherwise, return the given `A` in `Left`.
+ *
+ * {{{
+ * val userInput: String = ...
+ * Either.cond(
+ * userInput.forall(_.isDigit) && userInput.size == 10,
+ * PhoneNumber(userInput),
+ * "The input (%s) does not look like a phone number".format(userInput)
+ * }}}
+ */
+ def cond[X, Y](test: Boolean, right: => Y, left: => X): Either[X, Y] =
+ if (test) Right(right) else Left(left)
+
/**
* Allows use of a `merge` method to extract values from Either instances
* regardless of whether they are Left or Right.
@@ -216,8 +390,8 @@ object Either {
*/
implicit class MergeableEither[A](private val x: Either[A, A]) extends AnyVal {
def merge: A = x match {
- case Left(a) => a
case Right(a) => a
+ case Left(a) => a
}
}
@@ -250,7 +424,7 @@ object Either {
* }}}
*
* {{{
- * // using Either
+ * // using Either
* def interactWithDB(x: Query): Either[Exception, Result] =
* try {
* Right(getResultFromDatabase(x))
@@ -270,6 +444,7 @@ object Either {
* @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse
* @version 1.0, 11/10/2008
*/
+ @deprecated("use swap instead", "2.12.0")
final case class LeftProjection[+A, +B](e: Either[A, B]) {
/**
* Returns the value from this `Left` or throws `java.util.NoSuchElementException`
@@ -282,9 +457,9 @@ object Either {
*
* @throws java.util.NoSuchElementException if the projection is [[scala.util.Right]]
*/
- def get = e match {
- case Left(a) => a
- case Right(_) => throw new NoSuchElementException("Either.left.value on Right")
+ def get: A = e match {
+ case Left(a) => a
+ case Right(_) => throw new NoSuchElementException("Either.left.get on Right")
}
/**
@@ -296,14 +471,13 @@ object Either {
* }}}
* @param f The side-effecting function to execute.
*/
- def foreach[U](f: A => U) = e match {
- case Left(a) => f(a)
- case Right(_) => {}
+ def foreach[U](f: A => U): Unit = e match {
+ case Left(a) => f(a)
+ case Right(_) =>
}
/**
- * Returns the value from this `Left` or the given argument if this is a
- * `Right`.
+ * Returns the value from this `Left` or the given argument if this is a `Right`.
*
* {{{
* Left(12).left.getOrElse(17) // 12
@@ -311,8 +485,8 @@ object Either {
* }}}
*
*/
- def getOrElse[AA >: A](or: => AA) = e match {
- case Left(a) => a
+ def getOrElse[AA >: A](or: => AA): AA = e match {
+ case Left(a) => a
case Right(_) => or
}
@@ -327,8 +501,8 @@ object Either {
* }}}
*
*/
- def forall(@deprecatedName('f) p: A => Boolean) = e match {
- case Left(a) => p(a)
+ def forall(@deprecatedName('f) p: A => Boolean): Boolean = e match {
+ case Left(a) => p(a)
case Right(_) => true
}
@@ -343,8 +517,8 @@ object Either {
* }}}
*
*/
- def exists(@deprecatedName('f) p: A => Boolean) = e match {
- case Left(a) => p(a)
+ def exists(@deprecatedName('f) p: A => Boolean): Boolean = e match {
+ case Left(a) => p(a)
case Right(_) => false
}
@@ -357,9 +531,9 @@ object Either {
* }}}
* @param f The function to bind across `Left`.
*/
- def flatMap[BB >: B, X](f: A => Either[X, BB]) = e match {
- case Left(a) => f(a)
- case Right(b) => Right(b)
+ def flatMap[BB >: B, X](f: A => Either[X, BB]): Either[X, BB] = e match {
+ case Left(a) => f(a)
+ case Right(b) => e.asInstanceOf[Either[X, BB]]
}
/**
@@ -370,9 +544,9 @@ object Either {
* Right[Int, Int](12).left.map(_ + 2) // Right(12)
* }}}
*/
- def map[X](f: A => X) = e match {
- case Left(a) => Left(f(a))
- case Right(b) => Right(b)
+ def map[X](f: A => X): Either[X, B] = e match {
+ case Left(a) => Left(f(a))
+ case Right(b) => e.asInstanceOf[Either[X, B]]
}
/**
@@ -386,7 +560,7 @@ object Either {
* }}}
*/
def filter[Y](p: A => Boolean): Option[Either[A, Y]] = e match {
- case Left(a) => if(p(a)) Some(Left(a)) else None
+ case Left(a) => if(p(a)) Some(Left(a)) else None
case Right(b) => None
}
@@ -399,8 +573,8 @@ object Either {
* Right(12).left.toSeq // Seq()
* }}}
*/
- def toSeq = e match {
- case Left(a) => Seq(a)
+ def toSeq: Seq[A] = e match {
+ case Left(a) => Seq(a)
case Right(_) => Seq.empty
}
@@ -413,8 +587,8 @@ object Either {
* Right(12).left.toOption // None
* }}}
*/
- def toOption = e match {
- case Left(a) => Some(a)
+ def toOption: Option[A] = e match {
+ case Left(a) => Some(a)
case Right(_) => None
}
}
@@ -434,6 +608,7 @@ object Either {
* @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse
* @version 1.0, 11/10/2008
*/
+ @deprecated("Either is now right-biased", "2.12.0")
final case class RightProjection[+A, +B](e: Either[A, B]) {
/**
@@ -447,9 +622,9 @@ object Either {
*
* @throws java.util.NoSuchElementException if the projection is `Left`.
*/
- def get = e match {
- case Left(_) => throw new NoSuchElementException("Either.right.value on Left")
- case Right(a) => a
+ def get: B = e match {
+ case Right(b) => b
+ case Left(_) => throw new NoSuchElementException("Either.right.get on Left")
}
/**
@@ -461,23 +636,22 @@ object Either {
* }}}
* @param f The side-effecting function to execute.
*/
- def foreach[U](f: B => U) = e match {
- case Left(_) => {}
+ def foreach[U](f: B => U): Unit = e match {
case Right(b) => f(b)
+ case Left(_) =>
}
/**
- * Returns the value from this `Right` or the given argument if this is a
- * `Left`.
+ * 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
+ def getOrElse[BB >: B](or: => BB): BB = e match {
case Right(b) => b
+ case Left(_) => or
}
/**
@@ -490,9 +664,9 @@ object Either {
* Left(12).right.forall(_ > 10) // true
* }}}
*/
- def forall(f: B => Boolean) = e match {
- case Left(_) => true
+ def forall(f: B => Boolean): Boolean = e match {
case Right(b) => f(b)
+ case Left(_) => true
}
/**
@@ -505,9 +679,9 @@ object Either {
* Left(12).right.exists(_ > 10) // false
* }}}
*/
- def exists(@deprecatedName('f) p: B => Boolean) = e match {
- case Left(_) => false
+ def exists(@deprecatedName('f) p: B => Boolean): Boolean = e match {
case Right(b) => p(b)
+ case Left(_) => false
}
/**
@@ -515,9 +689,9 @@ object Either {
*
* @param f The function to bind across `Right`.
*/
- def flatMap[AA >: A, Y](f: B => Either[AA, Y]) = e match {
- case Left(a) => Left(a)
+ def flatMap[AA >: A, Y](f: B => Either[AA, Y]): Either[AA, Y] = e match {
case Right(b) => f(b)
+ case Left(a) => e.asInstanceOf[Either[AA, Y]]
}
/**
@@ -528,9 +702,9 @@ object Either {
* Left(12).right.map(x => "flower") // Result: Left(12)
* }}}
*/
- def map[Y](f: B => Y) = e match {
- case Left(a) => Left(a)
+ def map[Y](f: B => Y): Either[A, Y] = e match {
case Right(b) => Right(f(b))
+ case Left(a) => e.asInstanceOf[Either[A, Y]]
}
/** Returns `None` if this is a `Left` or if the
@@ -544,8 +718,8 @@ object Either {
* }}}
*/
def filter[X](p: B => Boolean): Option[Either[X, B]] = e match {
- case Left(_) => None
case Right(b) => if(p(b)) Some(Right(b)) else None
+ case Left(_) => None
}
/** Returns a `Seq` containing the `Right` value if
@@ -556,9 +730,9 @@ object Either {
* Left(12).right.toSeq // Seq()
* }}}
*/
- def toSeq = e match {
- case Left(_) => Seq.empty
+ def toSeq: Seq[B] = e match {
case Right(b) => Seq(b)
+ case Left(_) => Seq.empty
}
/** Returns a `Some` containing the `Right` value
@@ -569,23 +743,9 @@ object Either {
* Left(12).right.toOption // None
* }}}
*/
- def toOption = e match {
- case Left(_) => None
+ def toOption: Option[B] = e match {
case Right(b) => Some(b)
+ case Left(_) => None
}
}
-
- /** If the condition is satisfied, return the given `B` in `Right`,
- * otherwise, return the given `A` in `Left`.
- *
- * {{{
- * val userInput: String = ...
- * Either.cond(
- * userInput.forall(_.isDigit) && userInput.size == 10,
- * PhoneNumber(userInput),
- * "The input (%s) does not look like a phone number".format(userInput)
- * }}}
- */
- def cond[A, B](test: Boolean, right: => B, left: => A): Either[A, B] =
- if (test) Right(right) else Left(left)
}