org.scalautils.Or.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.11.0-RC2 Show documentation
/* * Copyright 2001-2013 Artima, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.scalautils import scala.util.Try import scala.util.Success import scala.util.Failure import scala.util.control.NonFatal import scala.collection.GenTraversableOnce import scala.collection.generic.CanBuildFrom import scala.collection.mutable.Builder /** * Represents a value that is one of two possible types, with one type being “good” and * the other “bad.” * *
filter, and therefore, returns the same result. */ def withFilter[C >: B](f: G => Validation[C]): G Or C = filter(f) /** * Returns* An
* *Or
will either be a “good” value wrapped in an instance of *Good
or a “bad” value wrapped in an instance * ofBad
. *The motivation for
* *Or
*
* *Or
differs from Scala'sEither
type in that *Either
treats both itsLeft
andRight
alternatives in an identical manner, whereas *Or
treats its two alternatives differently: it favors *Good
overBad
. * Because of this, it is more convenient to work withOr
s * when you prefer one alternative over the other; for example, if one alternative represents a valid result * and another represents an error. ** To illustrate, imagine you want to create instances this
* *Person
class from user input strings: ** case class Person(name: String, age: Int) ** ** You might write a method that parses the name from user input string and returns an *
* *Option[String]
:None
if the string is empty or blank, else the * trimmed string wrapped in aSome
: ** def parseName(input: String): Option[String] = { * val trimmed = input.trim * if (!trimmed.isEmpty) Some(trimmed) else None * } ** ** You might also write a method that parses the age from user input string and returns an *
* *Option[Int]
:None
if either the string is not a valid integer or * it is a negative integer, else the string converted to an integer wrapped in aSome
: ** def parseAge(input: String): Option[Int] = { * try { * val age = input.trim.toInt * if (age >= 0) Some(age) else None * } * catch { * case _: NumberFormatException => None * } * } ** ** With these building blocks you could write a method that parses name and age input * strings and returns either a
* *Person
, wrapped in aSome
, or *None
if either the name or age, or both, was invalid: ** def parsePerson(inputName: String, inputAge: String): Option[Person] = * for { * name <- parseName(inputName) * age <- parseAge(inputAge) * } yield Person(name, age) ** ** Here are some examples of invoking
* *parsePerson
: ** parsePerson("Bridget Jones", "29") * // Result: Some(Person(Bridget Jones,29)) * * parsePerson("Bridget Jones", "") * // Result: None * * parsePerson("Bridget Jones", "-29") * // Result: None * * parsePerson("", "") * // Result: None ** ** Now imagine you want to give an error message back if the user's input is invalid. * You might rewrite the parsing methods to return an
* *Either
instead. In this * case, the desired result is a valid name or age, which by convention should be placed * on the right of theEither
. The left will be aString
error * message. Here's the newparseName
function, which returns anEither[String, String]
: ** def parseName(input: String): Either[String, String] = { * val trimmed = input.trim * if (!trimmed.isEmpty) Right(trimmed) else Left(s""""${input}" is not a valid name""") * } ** ** And here's the new
* *parseAge
function, which returns anEither[String, Int]
: ** def parseAge(input: String): Either[String, Int] = { * try { * val age = input.trim.toInt * if (age >= 0) Right(age) else Left(s""""${age}" is not a valid age""") * } * catch { * case _: NumberFormatException => Left(s""""${input}" is not a valid integer""") * } * } ** ** The new
* *parsePerson
method will return anEither[String, Person]
: ** def parsePerson(inputName: String, inputAge: String): Either[String, Person] = * for { * name <- parseName(inputName).right * age <- parseAge(inputAge).right * } yield Person(name, age) ** ** Note that
* *Either
requires you to add.right
* at the end of each generator in thefor
expression. Although the convention is to place the * valid result on the right, you must explicitly (and repetitively) indicate that you've done so by transforming * theEither
to aRightProjection
by invoking.right
at each step. * Given this implementation, theparsePerson
method will now short-circuit at the first sign * of trouble (as it did when we used anOption
), but you now get the first error message returned * in aLeft
. Here are some examples: ** parsePerson("Bridget Jones", "29") * // Result: Right(Person(Bridget Jones,29)) * * parsePerson("Bridget Jones", "") * // Result: Left("" is not a valid integer) * * parsePerson("Bridget Jones", "-29") * // Result: Left("-29" is not a valid age) * * parsePerson("", "") * // Result: Left("" is not a valid name) ** *An
* *Either
with “attitude”* Because
* *Or
declares one alternative to be “good” and the other “bad,” * it is more convenient thanEither
in this kind of situation. One difference to note with *Or
is that theGood
alternative is on the left,Bad
on the right. * The reason is thatOr
is designed to be written using infix notation, and placing the * “happy path” first is more readable. For example, instead of writing: ** Or[Int, ErrorMessage] ** ** You can write: *
* ** Int Or ErrorMessage ** ** Here's how the
* *parseName
method might be written using anOr
, where *ErrorMessage
is a type alias forString
declared in theorg.scalautils
* package object: ** import org.scalautils._ * * def parseName(input: String): String Or ErrorMessage = { * val trimmed = input.trim * if (!trimmed.isEmpty) Good(trimmed) else Bad(s""""${input}" is not a valid name""") * } ** ** You can think of the
* *String
Or
ErrorMessage
result * type like this: ** The* *parseName
method will return a nameString
or, if the input string * is not a valid name, anErrorMessage
. ** Here's how the
* *parseAge
method might be written: ** def parseAge(input: String): Int Or ErrorMessage = { * try { * val age = input.trim.toInt * if (age >= 0) Good(age) else Bad(s""""${age}" is not a valid age""") * } * catch { * case _: NumberFormatException => Bad(s""""${input}" is not a valid integer""") * } * } ** ** Given these implementations, here's how you'd write the
* *parsePerson
method: ** def parsePerson(inputName: String, inputAge: String): Person Or ErrorMessage = * for { * name <- parseName(inputName) * age <- parseAge(inputAge) * } yield Person(name, age) ** ** Because of
* *Or
's attitude, you need not write.good
at the end of * each generator.Or
will keep going so long as each step produces aGood
, * short circuiting at the first sign of aBad
. Here are a few invocations of this *parsePerson
method: ** parsePerson("Bridget Jones", "29") * // Result: Good(Person(Bridget Jones,29)) * * parsePerson("Bridget Jones", "") * // Result: Bad("" is not a valid integer) * * parsePerson("Bridget Jones", "-29") * // Result: Bad("-29" is not a valid age) * * parsePerson("", "") * // Result: Bad("" is not a valid name) ** * *Accumulating errors with
* *Or
* Another difference between
* *Or
andEither
is thatOr
enables * you to accumulate errors if theBad
type is anEvery
. * AnEvery
is similar to aSeq
in that it contains ordered elements, but * different fromSeq
in that it cannot be empty. AnEvery
is * either aOne
, * which contains one and only one element, or aMany
, which contains two or * more elements. ** Note: an
* *Or
whoseBad
type is anEvery
, or one of its subtypes, * is called an “accumulatingOr
.” ** To rewrite the previous example so that errors can be accumulated, you need first to return an
* *Every
* as theBad
type. Here's how you'd change theparseName
method: ** def parseName(input: String): String Or One[ErrorMessage] = { * val trimmed = input.trim * if (!trimmed.isEmpty) Good(trimmed) else Bad(One(s""""${input}" is not a valid name""")) * } ** ** Because
* *parseName
will either return a valid nameString
wrapped in a *Good
, or one error message, wrapped in aBad
, you would write the *Bad
type asOne[ErrorMessage]
. The same is true forparseAge
: ** def parseAge(input: String): Int Or One[ErrorMessage] = { * try { * val age = input.trim.toInt * if (age >= 0) Good(age) else Bad(One(s""""${age}" is not a valid age""")) * } * catch { * case _: NumberFormatException => Bad(One(s""""${input}" is not a valid integer""")) * } * } ** ** Because a
* *for
expression short-circuits on the firstBad
encountered, you'll * need to use a different approach to write theparsePerson
method. In this example, the *withGood
method from traitAccumulation
* will do the trick: ** import Accumulation._ * * def parsePerson(inputName: String, inputAge: String): Person Or Every[ErrorMessage] = { * val name = parseName(inputName) * val age = parseAge(inputAge) * withGood(name, age) { Person(_, _) } * } ** ** Trait
* *Accumulation
offers overloadedwithGood
methods that take 1 to * 22 accumulatingOr
s, plus a function taking the same number of corresponding *Good
values. In this example, if bothname
andage
are *Good
s, thewithGood
method will pass the good nameString
* and ageInt
to thePerson(_, _)
function, and return the resultingPerson
* object wrapped in aGood
. If eithername
andage
, or both, * areBad
,withGood
will return the accumulated errors in aBad
. ** The result of
* *parsePerson
, ifBad
, will therefore contain either one or two * error messages, i.e., the result will either be aOne
or aMany
. * As a result, the result type ofparsePerson
must bePerson
Or
*Every[ErrorMessage]
. Regardless of whether aBad
result contains one * or two error messages, it will contain every error message. Here's some invocations of * this accumulating version ofparsePerson
: ** parsePerson("Bridget Jones", "29") * // Result: Good(Person(Bridget Jones,29)) * * parsePerson("Bridget Jones", "") * // Result: Bad(One("" is not a valid integer)) * * parsePerson("Bridget Jones", "-29") * // Result: Bad(One("-29" is not a valid age)) * * parsePerson("", "") * // Result: Bad(Many("" is not a valid name, "" is not a valid integer)) ** ** Note that in the last example, the
* *Bad
contains an error message for both name and age. *Other ways to accumulate errors
** The
* * *Accumlation
trait also enables other ways of accumulating errors. *Using
* *combined
* If you have a collection of * accumulating
* *Or
s, for example, you can combine them into oneOr
usingcombined
, like this: ** List(parseAge("29"), parseAge("30"), parseAge("31")).combined * // Result: Good(List(29, 30, 31)) * * List(parseAge("29"), parseAge("-30"), parseAge("31")).combined * // Result: Bad(One("-30" is not a valid age)) * * List(parseAge("29"), parseAge("-30"), parseAge("-31")).combined * // Result: Bad(Many("-30" is not a valid age, "-31" is not a valid age)) ** * *Using
* *validatedBy
* Or if you have a collection of values and a function that transforms that type of value into an accumulating *
* *Or
s, you can validate the values using the function usingvalidatedBy
, like this: ** List("29", "30", "31").validatedBy(parseAge) * // Result: Good(List(29, 30, 31)) * * List("29", "-30", "31").validatedBy(parseAge) * // Result: Bad(One("-30" is not a valid age)) * * List("29", "-30", "-31").validatedBy(parseAge) * // Result: Bad(Many("-30" is not a valid age, "-31" is not a valid age)) ** * *Using
* *zip
* You can also zip two accumulating
* *Or
s together. If both areGood
, you'll get a *Good
tuple containin both originalGood
values. Otherwise, you'll get aBad
* containing every error message. Here are some examples: ** parseName("Dude") zip parseAge("21") * // Result: Good((Dude,21)) * * parseName("Dude") zip parseAge("-21") * // Result: Bad(One("-21" is not a valid age)) * * parseName("") zip parseAge("-21") * // Result: Bad(Many("" is not a valid name, "-21" is not a valid age)) ** * *Using
* *when
* In addition, given an accumlating
* *Or
, you can pass one or more validation functions towhen
on theOr
* to submit thatOr
to further scrutiny. A validation function accepts aGood
type and returns aValidation[E]
, * whereE
is the type in theEvery
in theBad
type. For anInt
Or
One[ErrorMessage]
, for example * the validation function type would beInt
=>
Validation[ErrorMessage]
. Here are a few examples: ** def isRound(i: Int): Validation[ErrorMessage] = * if (i % 10 == 0) Pass else Fail(i + " was not a round number") * * def isDivBy3(i: Int): Validation[ErrorMessage] = * if (i % 3 == 0) Pass else Fail(i + " was not divisible by 3") ** ** If the
* *Or
on which you callwhen
is alreadyBad
, you get the same (Bad
)Or
back, because * noGood
value exists to pass to the valiation functions: ** parseAge("-30").when(isRound, isDivBy3) * // Result: Bad(One("-30" is not a valid age)) ** ** If the
* *Or
on which you callwhen
isGood
, and also passes all the validation functions (i.e., the * all returnNone
), you again get the sameOr
back, but this time, aGood
one: ** parseAge("30").when(isRound, isDivBy3) * // Result: Good(30) ** ** If one or more of the validation functions fails, however, you'll get a
* *Bad
back contining every error. Here are some examples: ** parseAge("33").when(isRound, isDivBy3) * // Result: Bad(One(33 was not a round number)) * * parseAge("20").when(isRound, isDivBy3) * // Result: Bad(One(20 was not divisible by 3)) * * parseAge("31").when(isRound, isDivBy3) * // Result: Bad(Many(31 was not a round number, 31 was not divisible by 3)) ** ** Note that you can use
* *when
to accumulate errors in afor
expression involving an accumulatingOr
, like this: ** for (age <- parseAge("-30") when (isRound, isDivBy3)) yield age * // Result: Bad(One("-30" is not a valid age)) * * for (age <- parseAge("30") when (isRound, isDivBy3)) yield age * // Result: Good(30) * * for (age <- parseAge("33") when (isRound, isDivBy3)) yield age * // Result: Bad(One(33 was not a round number)) * * for (age <- parseAge("20") when (isRound, isDivBy3)) yield age * // Result: Bad(One(20 was not divisible by 3)) * * for (age <- parseAge("31") when (isRound, isDivBy3)) yield age * // Result: Bad(Many(31 was not a round number, 31 was not divisible by 3)) ** *Much ado about
* *Nothing
* Because
* *Or
has two types, but each of its two subtypes only takes a value of one or the other type, the Scala compiler will * inferNothing
for the unspecified type: ** scala> Good(3) * res0: org.scalautils.Good[Int,Nothing] = Good(3) * * scala> Bad("oops") * res1: org.scalautils.Bad[Nothing,String] = Bad(oops) ** ** Often
* *Nothing
will work fine, as it will be widened as soon as the compiler encounters a more specific type. * Sometimes, however, you may need to specify it. In such situations you can use this syntax: ** scala> Good(3).orBad[String] * res2: org.scalautils.Good[Int,String] = Good(3) * * scala> Good[Int].orBad("oops") * res3: org.scalautils.Bad[Int,String] = Bad(oops) ** ** If you want to specify both types, because you don't like the inferred type, you can do so like this: *
* ** scala> Good[AnyVal, String](3) * res4: org.scalautils.Good[AnyVal,String] = Good(3) * * scala> Bad[Int, ErrorMessage]("oops") * res5: org.scalautils.Bad[Int,org.scalautils.ErrorMessage] = Bad(oops) ** ** But you may find the code is clearer if you instead use a type ascription, like this: *
* ** scala> Good(3): AnyVal Or String * res6: org.scalautils.Or[AnyVal,String] = Good(3) * * scala> Bad("oops"): Int Or ErrorMessage * res7: org.scalautils.Or[Int,org.scalautils.ErrorMessage] = Bad(oops) ** ** Note: The
*/ sealed abstract class Or[+G,+B] { /** * Indicates whether thisOr
hierarchy was inspired in part by the disjoint union (\/
) andValidation
types of *scalaz
, theProcessResult
type of * Typesafe Activator, and theResult
type of * ScalaKittens. *Or
is aGood
* * @return true if thisOr
is aGood
,false
if it is aBad
. */ val isGood: Boolean = false /** * Indicates whether thisOr
is aBad
* * @return true if thisOr
is aBad
,false
if it is aGood
. */ val isBad: Boolean = false /** * Returns theOr
's value if it is aGood
or throwsNoSuchElementException
if it is aBad
. * * @return the contained value if this is aGood
* @throws NoSuchElementException if this is aBad
*/ def get: G /** * Maps the given function to thisOr
's value if it is aGood
or returnsthis
if it is aBad
. * * @param f the function to apply * @return if this is aGood
, the result of applying the given function to the contained value wrapped in aGood
, * else thisBad
is returned */ def map[H](f: G => H): H Or B /** * Maps the given function to thisOr
's value if it is aBad
or returnsthis
if it is aGood
. * * @param f the function to apply * @return if this is aBad
, the result of applying the given function to the contained value wrapped in aBad
, * else thisGood
is returned */ def badMap[C](f: B => C): G Or C /** * Applies the given function f to the contained value if thisOr
is aGood
; does nothing if thisOr
* is aBad
. * * @param f the function to apply */ def foreach(f: G => Unit): Unit /** * Returns the given function applied to the value contained in thisOr
if it is aGood
, * or returnsthis
if it is aBad
. * * @param f the function to apply * @return if this is aGood
, the result of applying the given function to the contained value wrapped in aGood
, * else thisBad
is returned */ def flatMap[H, C >: B](f: G => H Or C): H Or C /** * Returns thisOr
if either 1) it is aBad
or 2) it is aGood
and applying the validation functionf
to this *Good
's value returnsPass
; otherwise, * returns a newBad
containing the error value contained in theFail
resulting from applying the validation * functionf
to thisGood
's value. * ** For examples of
* * @param f the validation function to apply * @return afilter
used infor
expressions, see the main documentation for trait *Validation
. *Good
if thisOr
is aGood
that passes the validation function, else aBad
. */ def filter[C >: B](f: G => Validation[C]): G Or C // TODO: What should we do about withFilter. Good question for the hackathon. /** * Currently just forwards totrue
if thisOr
is aGood
and the predicatep
returns true when applied to thisGood
's value. * ** Note: The
* * @param p the predicate to apply to theexists
method will return the same result asforall
if thisOr
is aGood
, but the opposite * result if thisOr
is aBad
. *Good
value, if this is aGood
* @return the result of applying the passed predicatep
to theGood
value, if this is aGood
, elsefalse
*/ def exists(p: G => Boolean): Boolean /** * Returnstrue
if either thisOr
is aBad
or if the predicatep
returnstrue
when applied * to thisGood
's value. * ** Note: The
* * @param p the predicate to apply to theforall
method will return the same result asexists
if thisOr
is aGood
, but the opposite * result if thisOr
is aBad
. *Good
value, if this is aGood
* @return the result of applying the passed predicatep
to theGood
value, if this is aGood
, elsetrue
*/ def forall(f: G => Boolean): Boolean /** * Returns, if thisOr
isGood
, thisGood
's value; otherwise returns the result of evaluatingdefault
. * * @param default the default expression to evaluate if thisOr
is aBad
* @return the contained value, if thisOr
is aGood
, else the result of evaluating the givendefault
*/ def getOrElse[H >: G](default: => H): H /** * Returns thisOr
if it is aGood
, otherwise returns the result of evaluating the passedalternative
. * * @param alternative the alternative by-name to evaluate if thisOr
is aBad
* @return thisOr
, to this, if it is a
bfGood
, else the result of evaluatingalternative
*/ def orElse[H >: G, C >: B](alternative: => H Or C): H Or C /** * Returns aSome
containing theGood
value, if thisOr
is aGood
, elseNone
. * * @return the contained “good” value wrapped in aSome
, if thisOr
is aGood
;None
* if thisOr
is aBad
. */ def toOption: Option[G] /** * Returns an immutableIndexedSeq
containing theGood
value, if thisOr
is aGood
, else an empty * immutableIndexedSeq
. * * @return the contained “good” value in a lone-elementSeq
if thisOr
is aGood
; an emptySeq
if * thisOr
is aBad
. */ def toSeq: scala.collection.immutable.IndexedSeq[G] /** * Returns anEither
: aRight
containing theGood
value, if this is aGood
; aLeft
* containing theBad
value, if this is aBad
. * ** Note that values effectively “switch sides” when convering an
* * @return thisOr
to anEither
. If the type of the *Or
on which you invoketoEither
isOr[Int, ErrorMessage]
for example, the result will be an *Either[ErrorMessage, Int]
. The reason is that the convention forEither
is thatLeft
is used for “bad” * values andRight
is used for “good” ones. *Good
value, wrapped in aRight
, or thisBad
value, wrapped in aLeft
. */ def toEither: Either[B, G] /** * Converts thisOr
to anOr
with the sameGood
type and aBad
type consisting of *One
parameterized by thisOr
'sBad
type. * ** For example, invoking the
accumulating
method on anInt Or ErrorMessage
would convert it to an *Int Or One[ErrorMessage]
. This result type, because theBad
type is anEvery
, can be used * with the mechanisms provided in traitAccumulation
to accumulate errors. ** *
* Note that if this
* * @return thisOr
is already an accumulatingOr
, the behavior of thisaccumulating
method does not change. * For example, if you invokeaccumulating
on anInt Or One[ErrorMessage]
you will be rewarded with an *Int Or One[One[ErrorMessage]]
. *Good
, if thisOr
is aGood
; or thisBad
value wrapped in aOne
if * thisOr
is aBad
. */ def accumulating: G Or One[B] /** * Returns aTry
: aSuccess
containing the *Good
value, if this is aGood
; aFailure
* containing theBad
value, if this is aBad
. * ** Note: This method can only be called if the
* *Bad
type of thisOr
is a subclass * ofThrowable
(orThrowable
itself). ** Note that values effectively “switch sides” when converting an
* * @return thisOr
to anEither
. If the type of the *Or
on which you invoketoEither
isOr[Int, ErrorMessage]
for example, the result will be an *Either[ErrorMessage, Int]
. The reason is that the convention forEither
is thatLeft
is used for “bad” * values andRight
is used for “good” ones. *Good
value, wrapped in aRight
, or thisBad
value, wrapped in aLeft
. */ def toTry(implicit ev: B <:< Throwable): Try[G] /** * Returns anOr
with theGood
andBad
types swapped:Bad
becomesGood
andGood
* becomesBad
. * ** Here's an example: *
* ** scala> val lyrics = Bad("Hey Jude, don't make it bad. Take a sad song and make it better.") * lyrics: org.scalautils.Bad[Nothing,String] = * Bad(Hey Jude, don't make it bad. Take a sad song and make it better.) * * scala> lyrics.swap * res12: org.scalautils.Or[String,Nothing] = * Good(Hey Jude, don't make it bad. Take a sad song and make it better.) ** ** Now that song will be rolling around in your head all afternoon. But at least it is a good song (thanks to
* * @return if thisswap
). *Or
is aGood
, itsGood
value wrapped in aBad
; if thisOr
is * aBad
, itsBad
value wrapped in aGood
. */ def swap: B Or G /** * Transforms thisOr
by applying the functiongf
to thisOr
'sGood
value if it is aGood
, * or by applyingbf
to thisOr
'sBad
value if it is aBad
. * * @param gf the function to apply to thisOr
'sGood
value, if it is aGood
* @param bf the function to apply to thisOr
'sBad
value, if it is aBad
* @return the result of applying the appropriate one of the two passed functions,gf
orOr
's value */ def transform[H, C](gf: G => H Or C, bf: B => H Or C): H Or C /** * Folds thisOr
into a value of typeV
by applying the givengf
function if this is * aGood
else the givenbf
function if this is aBad
. * * @param gf the function to apply to thisOr
'sGood
value, if it is aGood
* @param bf the function to apply to thisOr
'sBad
value, if it is aBad
* @return the result of applying the appropriate one of the two passed functions,gf
or bf, to thisOr
's value */ def fold[V](gf: G => V, bf: B => V): V } /** * The companion object forOr
providing factory methods for creatingOr
s fromEither
s andTry
s. */ object Or { /** * Constructs a newOr
from the givenTry
. * * @param theTry theTry
to convert to anOr
* @return a newOr
whoseGood
type is theTry
'sSuccess
type and whose *Bad
type isThrowable
. */ def from[G](theTry: Try[G]): G Or Throwable = theTry match { case Success(g) => Good(g) case Failure(e) => Bad(e) } /** * Constructs a newOr
from the givenEither
. * ** Note that values effectively “switch sides” when converting an
* * @param either theEither
to anOr
. If the type of the *Either
which you pass toOr.from
isEither[ErrorMessage, Int]
for example, the result will be an *Or[Int, ErrorMessage]
. The reason is that the convention forEither
is thatLeft
is used for “bad” * values andRight
is used for “good” ones. *Either
to convert to anOr
* @return a newOr
whoseGood
type is theEither
'sRight
type and whose *Bad
type isEither
'sLeft
type. */ def from[B, G](either: Either[B, G]): G Or B = either match { case Right(g) => Good(g) case Left(e) => Bad(e) } } /** * Contains a “good” value. * ** You can decide what “good” means, but it is expected
* * @param g the “good” value */ final case class Good[+G,+B](g: G) extends Or[G,B] { override val isGood: Boolean = true /** * Returns thisGood
will be commonly used * to hold valid results for processes that may fail with an error instead of producing a valid result. *Good
with the type widened toOr
. * ** This widening method can useful when the compiler infers the more specific
* *Good
type and * you need the more generalOr
type. Here's an example that usesfoldLeft
on * aList[Int]
to calculate a sum, so long as only odd numbers exist in a passedList
: ** scala> import org.scalautils._ * import org.scalautils._ * * scala> def oddSum(xs: List[Int]): Int Or ErrorMessage = * | xs.foldLeft(Good(0).orBad[ErrorMessage]) { (acc, x) => * | acc match { * | case Bad(_) => acc * | case Good(_) if (x % 2 == 0) => Bad(x + " is not odd") * | case Good(sum) => Good(sum + x) * | } * | } * <console>:13: error: constructor cannot be instantiated to expected type; * found : org.scalautils.Bad[G,B] * required: org.scalautils.Good[Int,org.scalautils.ErrorMessage] * case Bad(_) => acc * ^ * <console>:14: error: type mismatch; * found : org.scalautils.Bad[Nothing,String] * required: org.scalautils.Good[Int,org.scalautils.ErrorMessage] * case Good(_) if (x % 2 == 0) => Bad(x + " is not odd") * ^ ** ** Because the compiler infers the type of the first parameter to
* *foldLeft
to beGood[Int, ErrorMessage]
, * it expects the same type to appear as the result type of function passed as the second, curried parameter. What you really want is * that both types beInt Or ErrorMessage
, but the compiler thinks you want them to be the more specific *Good[Int, ErrorMessage]
. You can use theasOr
method to indicate you want the type to beOr
* with minimal boilerplate: ** scala> def oddSum(xs: List[Int]): Int Or ErrorMessage = * | xs.foldLeft(Good(0).orBad[ErrorMessage].asOr) { (acc, x) => * | acc match { * | case Bad(_) => acc * | case Good(_) if (x % 2 == 0) => Bad(x + " is not odd") * | case Good(sum) => Good(sum + x) * | } * | } * oddSum: (xs: List[Int])org.scalautils.Or[Int,org.scalautils.ErrorMessage] ** ** Now you can use the method to sum a
* *List
of odd numbers: ** scala> oddSum(List(1, 2, 3)) * res2: org.scalautils.Or[Int,org.scalautils.ErrorMessage] = Bad(2 is not odd) * * scala> oddSum(List(1, 3, 5)) * res3: org.scalautils.Or[Int,org.scalautils.ErrorMessage] = Good(9) **/ def asOr: G Or B = this /** * Narrows theBad
type of thisGood
to the given type. * ** Because
* *Or
has two types, but theGood
factory method only takes a value of the “good” type, the Scala compiler will * inferNothing
for theBad
type: ** scala> Good(3) * res0: org.scalautils.Good[Int,Nothing] = Good(3) ** ** Often
* *Nothing
will work fine, as it will be widened as soon as the compiler encounters a more specificBad
type. * Sometimes, however, you may need to specify it. In such situations you can use thisorBad
method, like this: ** scala> Good(3).orBad[String] * res1: org.scalautils.Good[Int,String] = Good(3) **/ def orBad[C](implicit ev: B <:< C): Good[G, C] = this.asInstanceOf[Good[G, C]] def get: G = g def map[H](f: G => H): H Or B = Good(f(g)) def badMap[C](f: B => C): G Or C = this.asInstanceOf[G Or C] def foreach(f: G => Unit): Unit = f(g) def flatMap[H, C >: B](f: G => H Or C): H Or C = f(g) def filter[C >: B](f: G => Validation[C]): G Or C = f(g) match { case Fail(error) => Bad(error) case Pass => this } def exists(p: G => Boolean): Boolean = p(g) def forall(p: G => Boolean): Boolean = p(g) def getOrElse[H >: G](default: => H): G = g def orElse[H >: G, C >: B](alternative: => H Or C): G Or B = this def toOption: Some[G] = Some(g) def toSeq: scala.collection.immutable.IndexedSeq[G] = Vector(g) def toEither: Either[B, G] = Right(g) def accumulating: G Or One[B] = Good(g) def toTry(implicit ev: B <:< Throwable): Success[G] = Success(g) def swap: B Or G = Bad(g) def transform[H, C](gf: G => H Or C, bf: B => H Or C): H Or C = gf(g) def fold[V](gf: G => V, bf: B => V): V = gf(g) } /** * Companion object forGood
that offers, in addition to the standard factory method * forGood
that takes single “good” type, an parameterless apply * used to narrow theGood
type when creating aBad
. */ object Good { /** * Supports the syntax that enablesBad
instances to be created with a specific *Good
type. */ class GoodType[G] { /** * Factory method forBad
instances whoseGood
type is specified * by the type parameter of thisGoodType
. * ** This method enables this syntax: *
* ** Good[Int].orBad("oops") * ^ ** * @param b the “bad” value * @return a newBad
instance containing the passedb
value */ def orBad[B](b: B): Bad[G, B] = Bad[G, B](b) override def toString: String = "GoodType" } /** * Captures aGood
type to enable aBad
to be constructed with a specific *Good
type. * ** Because
* *Or
has two types, but theBad
factory method only takes a value of the “bad” type, the Scala compiler will * inferNothing
for theGood
type: ** scala> Bad("oops") * res1: org.scalautils.Bad[Nothing,String] = Bad(oops) ** ** Often
* *Nothing
will work fine, as it will be widened as soon as the compiler encounters a more specificGood
type. * Sometimes, however, you may need to specify it. In such situations you can use this factory method, like this: ** scala> Good[Int].orBad("oops") * res3: org.scalautils.Bad[Int,String] = Bad(oops) **/ def apply[G]: GoodType[G] = new GoodType[G] } /** * Contains a “bad” value. * ** You can decide what “bad” means, but it is expected
* * @param b the “bad” value */ final case class Bad[+G,+B](b: B) extends Or[G,B] { override val isBad: Boolean = true /** * Returns thisBad
will be commonly used * to hold descriptions of an error (or several, accumulated errors). Some examples of possible error descriptions * areString
error messages,Int
error codes,Throwable
exceptions, * or instances of a case class hierarchy designed to describe errors. *Bad
with the type widened toOr
. * ** This widening method can useful when the compiler infers the more specific
* *Bad
type and * you need the more generalOr
type. Here's an example that usesfoldLeft
on * aList[Int]
to find the first even number in theList
: ** scala> import org.scalautils._ * import org.scalautils._ * * scala> def findFirstEven(xs: List[Int]): Int Or ErrorMessage = * | xs.foldLeft(Good[Int].orBad("No even nums")) { (acc, x) => * | acc orElse (if (x % 2 == 0) Good(x) else acc) * | } * <console>:13: error: type mismatch; * found : org.scalautils.Or[Int,String] * required: org.scalautils.Bad[Int,String] * acc orElse (if (x % 2 == 0) Good(x) else acc) * ^ ** ** Because the compiler infers the type of the first parameter to
* *foldLeft
to beBad[Int, String]
, * it expects the same type to appear as the result type of function passed as the second, curried parameter. What you really want is * that both types beInt Or String
, but the compiler thinks you want them to be the more specific *Bad[Int, String]
. You can use theasOr
method to indicate you want the type to beOr
* with minimal boilerplate: ** scala> def findFirstEven(xs: List[Int]): Int Or ErrorMessage = * | xs.foldLeft(Good[Int].orBad("No even nums").asOr) { (acc, x) => * | acc orElse (if (x % 2 == 0) Good(x) else acc) * | } * findFirstEven: (xs: List[Int])org.scalautils.Or[Int,String] ** ** Now you can use the method to find the first even number in a
* *List
: ** scala> findFirstEven(List(1, 2, 3)) * res4: org.scalautils.Or[Int,ErrorMessage] = Good(2) * * scala> findFirstEven(List(1, 3, 5)) * res5: org.scalautils.Or[Int,ErrorMessage] = Bad(No even nums) **/ def asOr: G Or B = this def get: G = throw new NoSuchElementException("Bad(" + b + ").get") def map[H](f: G => H): H Or B = this.asInstanceOf[H Or B] def badMap[C](f: B => C): G Or C = Bad(f(b)) def foreach(f: G => Unit): Unit = () def flatMap[H, C >: B](f: G => H Or C): H Or C = this.asInstanceOf[H Or C] def filter[C >: B](f: G => Validation[C]): G Or C = this def exists(p: G => Boolean): Boolean = false def forall(p: G => Boolean): Boolean = true def getOrElse[H >: G](default: => H): H = default def orElse[H >: G, C >: B](alternative: => H Or C): H Or C = alternative def toOption: None.type = None def toSeq: scala.collection.immutable.IndexedSeq[G] = Vector.empty def toEither: Either[B, G] = Left(b) def accumulating: G Or One[B] = Bad(One(b)) def toTry(implicit ev: B <:< Throwable): Failure[G] = Failure(b) def swap: B Or G = Good(b) def transform[H, C](gf: G => H Or C, bf: B => H Or C): H Or C = bf(b) def fold[V](gf: G => V, bf: B => V): V = bf(b) }