org.scalactic.Or.scala Maven / Gradle / Ivy
/* * 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.scalactic 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
* *Orwill either be a “good” value wrapped in an instance of *Goodor a “bad” value wrapped in an instance * ofBad. *The motivation for
* *Or*
* *Ordiffers from Scala'sEithertype in that *Eithertreats both itsLeftandRightalternatives in an identical manner, whereas *Ortreats its two alternatives differently: it favors *GoodoverBad. * Because of this, it is more convenient to work withOrs * 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
* *Personclass 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]:Noneif 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]:Noneif 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 *Noneif 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
* *Eitherinstead. 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 aStringerror * message. Here's the newparseNamefunction, 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
* *parseAgefunction, 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
* *parsePersonmethod 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
* *Eitherrequires you to add.right* at the end of each generator in theforexpression. 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 * theEitherto aRightProjectionby invoking.rightat each step. * Given this implementation, theparsePersonmethod 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
* *Eitherwith “attitude”* Because
* *Ordeclares one alternative to be “good” and the other “bad,” * it is more convenient thanEitherin this kind of situation. One difference to note with *Oris that theGoodalternative is on the left,Badon the right. * The reason is thatOris 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
* *parseNamemethod might be written using anOr, where *ErrorMessageis a type alias forStringdeclared in theorg.scalactic* package object: ** import org.scalactic._ * * 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
* *StringOrErrorMessageresult * type like this: ** The* *parseNamemethod will return a nameStringor, if the input string * is not a valid name, anErrorMessage. ** Here's how the
* *parseAgemethod 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
* *parsePersonmethod: ** 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.goodat the end of * each generator.Orwill keep going so long as each step produces aGood, * short circuiting at the first sign of aBad. Here are a few invocations of this *parsePersonmethod: ** 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
* *OrandEitheris thatOrenables * you to accumulate errors if theBadtype is anEvery. * AnEveryis similar to aSeqin that it contains ordered elements, but * different fromSeqin that it cannot be empty. AnEveryis * either aOne, * which contains one and only one element, or aMany, which contains two or * more elements. ** Note: an
* *OrwhoseBadtype 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 theBadtype. Here's how you'd change theparseNamemethod: ** 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
* *parseNamewill either return a valid nameStringwrapped in a *Good, or one error message, wrapped in aBad, you would write the *Badtype 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
* *forexpression short-circuits on the firstBadencountered, you'll * need to use a different approach to write theparsePersonmethod. In this example, the *withGoodmethod 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
* *Accumulationoffers overloadedwithGoodmethods that take 1 to * 22 accumulatingOrs, plus a function taking the same number of corresponding *Goodvalues. In this example, if bothnameandageare *Goods, thewithGoodmethod will pass the good nameString* and ageIntto thePerson(_, _)function, and return the resultingPerson* object wrapped in aGood. If eithernameandage, or both, * areBad,withGoodwill 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 aOneor aMany. * As a result, the result type ofparsePersonmust bePersonOr*Every[ErrorMessage]. Regardless of whether aBadresult 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
* *Badcontains an error message for both name and age. *Other ways to accumulate errors
** The
* * *Accumlationtrait also enables other ways of accumulating errors. *Using
* *combined* If you have a collection of * accumulating
* *Ors, for example, you can combine them into oneOrusingcombined, 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 *
* *Ors, 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
* *Ors together. If both areGood, you'll get a *Goodtuple containin both originalGoodvalues. 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 towhenon theOr* to submit thatOrto further scrutiny. A validation function accepts aGoodtype and returns aValidation[E], * whereEis the type in theEveryin theBadtype. For anIntOrOne[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
* *Oron which you callwhenis alreadyBad, you get the same (Bad)Orback, because * noGoodvalue exists to pass to the valiation functions: ** parseAge("-30").when(isRound, isDivBy3) * // Result: Bad(One("-30" is not a valid age)) ** ** If the
* *Oron which you callwhenisGood, and also passes all the validation functions (i.e., the * all returnNone), you again get the sameOrback, but this time, aGoodone: ** parseAge("30").when(isRound, isDivBy3) * // Result: Good(30) ** ** If one or more of the validation functions fails, however, you'll get a
* *Badback 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
* *whento accumulate errors in aforexpression 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
* *Orhas two types, but each of its two subtypes only takes a value of one or the other type, the Scala compiler will * inferNothingfor the unspecified type: ** scala> Good(3) * res0: org.scalactic.Good[Int,Nothing] = Good(3) * * scala> Bad("oops") * res1: org.scalactic.Bad[Nothing,String] = Bad(oops) ** ** Often
* *Nothingwill 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.scalactic.Good[Int,String] = Good(3) * * scala> Good[Int].orBad("oops") * res3: org.scalactic.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.scalactic.Good[AnyVal,String] = Good(3) * * scala> Bad[Int, ErrorMessage]("oops") * res5: org.scalactic.Bad[Int,org.scalactic.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.scalactic.Or[AnyVal,String] = Good(3) * * scala> Bad("oops"): Int Or ErrorMessage * res7: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Bad(oops) ** ** Note: The
*/ sealed abstract class Or[+G,+B] { /** * Indicates whether thisOrhierarchy was inspired in part by the disjoint union (\/) andValidationtypes of *scalaz, theProcessResulttype of * Typesafe Activator, and theResulttype of * ScalaKittens. *Oris aGood* * @return true if thisOris aGood,falseif it is aBad. */ val isGood: Boolean = false /** * Indicates whether thisOris aBad* * @return true if thisOris aBad,falseif it is aGood. */ val isBad: Boolean = false /** * Returns theOr's value if it is aGoodor throwsNoSuchElementExceptionif 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 aGoodor returnsthisif 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 thisBadis returned */ def map[H](f: G => H): H Or B /** * Maps the given function to thisOr's value if it is aBador returnsthisif 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 thisGoodis returned */ def badMap[C](f: B => C): G Or C /** * Maps the given function to thisOr's value if it is aBad, transforming it into aGood, or returns *thisif it is already 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 aGood, * else thisGoodis returned */ def recover[H >: G](f: B => H): H Or B /** * Maps the given function to thisOr's value if it is aBad, returning the result, or returns *thisif it is already aGood. * * @param f the function to apply * @return if this is aBad, the result of applying the given function to the contained value, * else thisGoodis returned */ def recoverWith[H >: G, C](f: B => H Or C): H Or C /** * Applies the given function f to the contained value if thisOris 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 thisOrif it is aGood, * or returnsthisif 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 thisBadis returned */ def flatMap[H, C >: B](f: G => H Or C): H Or C /** * Returns thisOrif either 1) it is aBador 2) it is aGoodand applying the validation functionfto this *Good's value returnsPass; otherwise, * returns a newBadcontaining the error value contained in theFailresulting from applying the validation * functionfto thisGood's value. * ** For examples of
* * @param f the validation function to apply * @return afilterused inforexpressions, see the main documentation for trait *Validation. *Goodif thisOris aGoodthat 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 totrueif thisOris aGoodand the predicatepreturns true when applied to thisGood's value. * ** Note: The
* * @param p the predicate to apply to theexistsmethod will return the same result asforallif thisOris aGood, but the opposite * result if thisOris aBad. *Goodvalue, if this is aGood* @return the result of applying the passed predicatepto theGoodvalue, if this is aGood, elsefalse*/ def exists(p: G => Boolean): Boolean /** * Returnstrueif either thisOris aBador if the predicatepreturnstruewhen applied * to thisGood's value. * ** Note: The
* * @param p the predicate to apply to theforallmethod will return the same result asexistsif thisOris aGood, but the opposite * result if thisOris aBad. *Goodvalue, if this is aGood* @return the result of applying the passed predicatepto theGoodvalue, if this is aGood, elsetrue*/ def forall(f: G => Boolean): Boolean /** * Returns, if thisOrisGood, thisGood's value; otherwise returns the result of evaluatingdefault. * * @param default the default expression to evaluate if thisOris aBad* @return the contained value, if thisOris aGood, else the result of evaluating the givendefault*/ def getOrElse[H >: G](default: => H): H /** * Returns thisOrif it is aGood, otherwise returns the result of evaluating the passedalternative. * * @param alternative the alternative by-name to evaluate if thisOris aBad* @return thisOr, if it is aGood, else the result of evaluatingalternative*/ def orElse[H >: G, C >: B](alternative: => H Or C): H Or C /** * Returns aSomecontaining theGoodvalue, if thisOris aGood, elseNone. * * @return the contained “good” value wrapped in aSome, if thisOris aGood;None* if thisOris aBad. */ def toOption: Option[G] /** * Returns an immutableIndexedSeqcontaining theGoodvalue, if thisOris aGood, else an empty * immutableIndexedSeq. * * @return the contained “good” value in a lone-elementSeqif thisOris aGood; an emptySeqif * thisOris aBad. */ def toSeq: scala.collection.immutable.IndexedSeq[G] /** * Returns anEither: aRightcontaining theGoodvalue, if this is aGood; aLeft* containing theBadvalue, if this is aBad. * ** Note that values effectively “switch sides” when convering an
* * @return thisOrto anEither. If the type of the *Oron which you invoketoEitherisOr[Int, ErrorMessage]for example, the result will be an *Either[ErrorMessage, Int]. The reason is that the convention forEitheris thatLeftis used for “bad” * values andRightis used for “good” ones. *Goodvalue, wrapped in aRight, or thisBadvalue, wrapped in aLeft. */ def toEither: Either[B, G] /** * Converts thisOrto anOrwith the sameGoodtype and aBadtype consisting of *Oneparameterized by thisOr'sBadtype. * ** For example, invoking the
accumulatingmethod on anInt Or ErrorMessagewould convert it to an *Int Or One[ErrorMessage]. This result type, because theBadtype is anEvery, can be used * with the mechanisms provided in traitAccumulationto accumulate errors. ** *
* Note that if this
* * @return thisOris already an accumulatingOr, the behavior of thisaccumulatingmethod does not change. * For example, if you invokeaccumulatingon anInt Or One[ErrorMessage]you will be rewarded with an *Int Or One[One[ErrorMessage]]. *Good, if thisOris aGood; or thisBadvalue wrapped in aOneif * thisOris aBad. */ def accumulating: G Or One[B] /** * Returns aTry: aSuccesscontaining the *Goodvalue, if this is aGood; aFailure* containing theBadvalue, if this is aBad. * ** Note: This method can only be called if the
* *Badtype of thisOris a subclass * ofThrowable(orThrowableitself). ** Note that values effectively “switch sides” when converting an
* * @return thisOrto anEither. If the type of the *Oron which you invoketoEitherisOr[Int, ErrorMessage]for example, the result will be an *Either[ErrorMessage, Int]. The reason is that the convention forEitheris thatLeftis used for “bad” * values andRightis used for “good” ones. *Goodvalue, wrapped in aRight, or thisBadvalue, wrapped in aLeft. */ def toTry(implicit ev: B <:< Throwable): Try[G] /** * Returns anOrwith theGoodandBadtypes swapped:BadbecomesGoodandGood* 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.scalactic.Bad[Nothing,String] = * Bad(Hey Jude, don't make it bad. Take a sad song and make it better.) * * scala> lyrics.swap * res12: org.scalactic.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). *Oris aGood, itsGoodvalue wrapped in aBad; if thisOris * aBad, itsBadvalue wrapped in aGood. */ def swap: B Or G /** * Transforms thisOrby applying the functiongfto thisOr'sGoodvalue if it is aGood, * or by applyingbfto thisOr'sBadvalue if it is aBad. * * @param gf the function to apply to thisOr'sGoodvalue, if it is aGood* @param bf the function to apply to thisOr'sBadvalue, if it is aBad* @return the result of applying the appropriate one of the two passed functions,gfor bf, to thisOr's value */ def transform[H, C](gf: G => H Or C, bf: B => H Or C): H Or C /** * Folds thisOrinto a value of typeVby applying the givengffunction if this is * aGoodelse the givenbffunction if this is aBad. * * @param gf the function to apply to thisOr'sGoodvalue, if it is aGood* @param bf the function to apply to thisOr'sBadvalue, if it is aBad* @return the result of applying the appropriate one of the two passed functions,gfor bf, to thisOr's value */ def fold[V](gf: G => V, bf: B => V): V /** * TheasOrmethod has been deprecated and will be removed in a future version of Scalactic. * Please remove invocations ofasOrin expressions of typeGood(value).orBad[Type]and *Good[Type].orBad(value)(which now return a type already widened toOr), otherwise please * use a type annotation to widen the type, such as:(Good(3): Int Or ErrorMessage). */ @deprecated("The asOr is no longer needed because Good(value).orBad[Type] and Good[Type].orBad(value) now return Or. You can delete invocations of asOr in those cases, otherwise, please use a type annotation to widen the type, like (Good(3): Int Or ErrorMessage).") def asOr: G Or B = this } /** * The companion object forOrproviding factory methods for creatingOrs fromEithers andTrys. */ object Or { /** * Trait providing a concise type lambda syntax forOrtypes partially applied on their "bad" type. * ** This trait is used to curry the type parameters of
* *Or, which takes two type parameters, * into a type (this trait) which takes one parameter, and another (its type member) which * takes the other. For example, typeOr[GOOD, BAD](which can be written in infix form * asGOOD Or BAD) can be expressed in curried form asOr.B[BAD]#G[GOOD]. * Leaving off the finalGOODtype parameter yields a "type lambda," such asOr.B[ErrorMessage]#G. ** For example, consider this method that takes two type parameters, a type constructor named
* *Contextand a * type namedA: ** scala> def example[Context[_], A](ca: Context[A]) = ca * example: [Context[_], A](ca: Context[A])Context[A] ** ** Because
* *Listtakes a single type parameter, it fits the shape ofContext, * it can be simply passed toexample--i.e., the compiler will inferContextasList: ** scala> example(List(1, 2, 3)) * res0: List[Int] = List(1, 2, 3) ** ** But because
* *Ortakes two type parameters,Gfor the "good" type andBfor the "bad" type, it * cannot simply be passed, because the compiler doesn't know which ofGor B you'd want to abstract over: ** scala> val or: Int Or ErrorMessage = Good(3) * or: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Good(3) * * scala> example(or) * <console>:16: error: no type parameters for method example: (ca: Context[A])Context[A] exist so that it can be applied to arguments (org.scalactic.Or[Int,org.scalactic.ErrorMessage]) * --- because --- * argument expression's type is not compatible with formal parameter type; * found : org.scalactic.Or[Int,org.scalactic.ErrorMessage] * (which expands to) org.scalactic.Or[Int,String] * required: ?Context[?A] * example(or) * ^ * <console>:16: error: type mismatch; * found : org.scalactic.Or[Int,org.scalactic.ErrorMessage] * (which expands to) org.scalactic.Or[Int,String] * required: Context[A] * example(or) * ^ ** ** You must therefore tell the compiler which one you want with a "type lambda." Here's an example: *
* ** scala> example[({type L[G] = G Or ErrorMessage})#L, Int](or) * res1: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Good(3) ** ** The alternate type lambda syntax provided by this trait is more concise and hopefully easier to remember and read: *
* ** scala> example[Or.B[ErrorMessage]#G, Int](or) * res2: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Good(3) ** ** You can read
*/ private[scalactic] trait B[BAD] { /** * Type member that provides a curried alias toOr.B[ErrorMessage]#Gas: anOrwith its "bad" type,B, * fixed toErrorMessageand its "good" type,G, left unspecified. *GOrB. * ** See the main documentation for trait
*/ type G[GOOD] = GOOD Or BAD } /** * Trait providing a concise type lambda syntax forBfor more detail. *Ortypes partially applied on their "good" type. * ** This trait is used to curry the type parameters of
* *Or, which takes two type parameters, * into a type (this trait) which takes one parameter, and another (its type member) which * takes the other. For example, typeOr[GOOD, BAD](which can be written in infix form * asGOOD Or BAD) can be expressed in curried form asOr.G[GOOD]#B[BAD]. * Leaving off the finalBtype parameter yields a "type lambda," such asOr.G[Int]#B. ** For example, consider this method that takes two type parameters, a type constructor named
* *Contextand a * type namedA: ** scala> def example[Context[_], A](ca: Context[A]) = ca * example: [Context[_], A](ca: Context[A])Context[A] ** ** Because
* *Listtakes a single type parameter, it fits the shape ofContext, * it can be simply passed toexample--i.e., the compiler will inferContextasList: ** scala> example(List(1, 2, 3)) * res0: List[Int] = List(1, 2, 3) ** ** But because
* *Ortakes two type parameters,Gfor the "good" type andBfor the "bad" type, it * cannot simply be passed, because the compiler doesn't know which ofGor B you'd want to abstract over: ** scala> val or: Int Or ErrorMessage = Good(3) * or: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Good(3) * * scala> example(or) * <console>:16: error: no type parameters for method example: (ca: Context[A])Context[A] exist so that it can be applied to arguments (org.scalactic.Or[Int,org.scalactic.ErrorMessage]) * --- because --- * argument expression's type is not compatible with formal parameter type; * found : org.scalactic.Or[Int,org.scalactic.ErrorMessage] * (which expands to) org.scalactic.Or[Int,String] * required: ?Context[?A] * example(or) * ^ * <console>:16: error: type mismatch; * found : org.scalactic.Or[Int,org.scalactic.ErrorMessage] * (which expands to) org.scalactic.Or[Int,String] * required: Context[A] * example(or) * ^ ** ** You must therefore tell the compiler which one you want with a "type lambda." Here's an example: *
* ** scala> example[({type L[B] = Int Or B})#L, ErrorMessage](or) * res1: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Good(3) ** ** The alternate type lambda syntax provided by this trait is more concise and hopefully easier to remember and read: *
* ** scala> example[Or.G[Int]#B, ErrorMessage](or) * res15: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Good(3) ** ** You can read
*/ private[scalactic] trait G[GOOD] { /** * Type member that provides a curried alias toOr.G[Int]#Bas: anOrwith its "good" type,G, * fixed toIntand its "bad" type,B, left unspecified. *GOrB. * ** See the main documentation for trait
*/ type B[BAD] = GOOD Or BAD } /** * Constructs a newGfor more detail. *Orfrom the givenTry. * * @param theTry theTryto convert to anOr* @return a newOrwhoseGoodtype is theTry'sSuccesstype and whose *Badtype isThrowable. */ def from[G](theTry: Try[G]): G Or Throwable = theTry match { case Success(g) => Good(g) case Failure(e) => Bad(e) } /** * Constructs a newOrfrom the givenEither. * ** Note that values effectively “switch sides” when converting an
* * @param either theEitherto anOr. If the type of the *Eitherwhich you pass toOr.fromisEither[ErrorMessage, Int]for example, the result will be an *Or[Int, ErrorMessage]. The reason is that the convention forEitheris thatLeftis used for “bad” * values andRightis used for “good” ones. If you with to keep the types on the same side, invokeswapon the *Eitherbefore passing it tofrom. *Eitherto convert to anOr* @return a newOrwhoseGoodtype is theEither'sRighttype and whose *Badtype isEither'sLefttype. */ def from[B, G](either: Either[B, G]): G Or B = either match { case Right(g) => Good(g) case Left(b) => Bad(b) } /** * Constructs a newOrfrom the givenOption. * * @param option theOptionto convert to anOr* @param orElse theBadvalue to use if theOptionpassed asoptionisNone. * @return a newOrwhoseGoodtype is theOption's type and whose *Badtype is the type of the passedorElseparameter. */ def from[G, B](option: Option[G], orElse: => B): G Or B = option match { case Some(g) => Good(g) case None => Bad(orElse) } } /** * Contains a “good” value. * ** You can decide what “good” means, but it is expected
* * @param g the “good” value */ final case class Good[+G](g: G) extends Or[G,Nothing] { override val isGood: Boolean = true /* * Returns thisGoodwill be commonly used * to hold valid results for processes that may fail with an error instead of producing a valid result. *Goodwith the type widened toOr. * ** This widening method can useful when the compiler infers the more specific
* *Goodtype and * you need the more generalOrtype. Here's an example that usesfoldLefton * aList[Int]to calculate a sum, so long as only odd numbers exist in a passedList: ** scala> import org.scalactic._ * import org.scalactic._ * * 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.scalactic.Bad[G,B] * required: org.scalactic.Good[Int,org.scalactic.ErrorMessage] * case Bad(_) => acc * ^ * <console>:14: error: type mismatch; * found : org.scalactic.Bad[Nothing,String] * required: org.scalactic.Good[Int,org.scalactic.ErrorMessage] * case Good(_) if (x % 2 == 0) => Bad(x + " is not odd") * ^ ** ** Because the compiler infers the type of the first parameter to
* *foldLeftto 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 theasOrmethod 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.scalactic.Or[Int,org.scalactic.ErrorMessage] ** ** Now you can use the method to sum a
* *Listof odd numbers: ** scala> oddSum(List(1, 2, 3)) * res2: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Bad(2 is not odd) * * scala> oddSum(List(1, 3, 5)) * res3: org.scalactic.Or[Int,org.scalactic.ErrorMessage] = Good(9) **/ /** * TheasOrmethod has been deprecated and will be removed in a future version of Scalactic. * Please remove invocations ofasOrin expressions of typeGood(value).orBad[Type]and *Good[Type].orBad(value)(which now return a type already widened toOr), otherwise please * use a type annotation to widen the type, such as:(Good(3): Int Or ErrorMessage). */ @deprecated("The asOr is no longer needed because Good(value).orBad[Type] and Good[Type].orBad(value) now return Or. You can delete invocations of asOr in those cases, otherwise, please use a type annotation to widen the type, like (Good(3): Int Or ErrorMessage).") override def asOr: G Or Nothing = this /** * Narrows theBadtype of thisGoodto the given type. * ** Because
* *Orhas two types, but theGoodfactory method only takes a value of the “good” type, the Scala compiler will * inferNothingfor theBadtype: ** scala> Good(3) * res0: org.scalactic.Good[Int,Nothing] = Good(3) ** ** Often
* *Nothingwill work fine, as it will be widened as soon as the compiler encounters a more specificBadtype. * Sometimes, however, you may need to specify it. In such situations you can use thisorBadmethod, like this: ** scala> Good(3).orBad[String] * res1: org.scalactic.Good[Int,String] = Good(3) **/ def orBad[C]: G Or C = this def get: G = g def map[H](f: G => H): H Or Nothing = Good(f(g)) def badMap[C](f: Nothing => C): G Or C = this.asInstanceOf[G Or C] def recover[H >: G](f: Nothing => H): H Or Nothing = this.asInstanceOf[H Or Nothing] def recoverWith[H >: G, C](f: Nothing => H Or C): H Or C = this.asInstanceOf[H Or C] def foreach(f: G => Unit): Unit = f(g) def flatMap[H, C >: Nothing](f: G => H Or C): H Or C = f(g) def filter[C >: Nothing](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 >: Nothing](alternative: => H Or C): G Or Nothing = this def toOption: Some[G] = Some(g) def toSeq: scala.collection.immutable.IndexedSeq[G] = Vector(g) def toEither: Either[Nothing, G] = Right(g) def accumulating: G Or One[Nothing] = Good(g) def toTry(implicit ev: Nothing <:< Throwable): Success[G] = Success(g) def swap: Nothing Or G = Bad(g) def transform[H, C](gf: G => H Or C, bf: Nothing => H Or C): H Or C = gf(g) def fold[V](gf: G => V, bf: Nothing => V): V = gf(g) } /** * Companion object forGoodthat offers, in addition to the standard factory method * forGoodthat takes single “good” type, an parameterless apply * used to narrow theGoodtype when creating aBad. */ object Good { /** * Supports the syntax that enablesBadinstances to be created with a specific *Goodtype. */ class GoodType[G] { /** * Factory method forBadinstances whoseGoodtype is specified * by the type parameter of thisGoodType. * ** This method enables this syntax: *
* ** Good[Int].orBad("oops") * ^ ** * @param b the “bad” value * @return a newBadinstance containing the passedbvalue */ def orBad[B](b: B): G Or B = Bad[B](b) override def toString: String = "GoodType" } /** * Captures aGoodtype to enable aBadto be constructed with a specific *Goodtype. * ** Because
* *Orhas two types, but theBadfactory method only takes a value of the “bad” type, the Scala compiler will * inferNothingfor theGoodtype: ** scala> Bad("oops") * res1: org.scalactic.Bad[Nothing,String] = Bad(oops) ** ** Often
* *Nothingwill work fine, as it will be widened as soon as the compiler encounters a more specificGoodtype. * 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.scalactic.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[+B](b: B) extends Or[Nothing,B] { override val isBad: Boolean = true /* * Returns thisBadwill be commonly used * to hold descriptions of an error (or several, accumulated errors). Some examples of possible error descriptions * areStringerror messages,Interror codes,Throwableexceptions, * or instances of a case class hierarchy designed to describe errors. *Badwith the type widened toOr. * ** This widening method can useful when the compiler infers the more specific
* *Badtype and * you need the more generalOrtype. Here's an example that usesfoldLefton * aList[Int]to find the first even number in theList: ** scala> import org.scalactic._ * import org.scalactic._ * * 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.scalactic.Or[Int,String] * required: org.scalactic.Bad[Int,String] * acc orElse (if (x % 2 == 0) Good(x) else acc) * ^ ** ** Because the compiler infers the type of the first parameter to
* *foldLeftto 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 theasOrmethod 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.scalactic.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.scalactic.Or[Int,ErrorMessage] = Good(2) * * scala> findFirstEven(List(1, 3, 5)) * res5: org.scalactic.Or[Int,ErrorMessage] = Bad(No even nums) **/ /** * TheasOrmethod has been deprecated and will be removed in a future version of Scalactic. * Please remove invocations ofasOrin expressions of typeGood(value).orBad[Type]and *Good[Type].orBad(value)(which now return a type already widened toOr), otherwise please * use a type annotation to widen the type, such as:(Good(3): Int Or ErrorMessage). */ @deprecated("The asOr is no longer needed because Good(value).orBad[Type] and Good[Type].orBad(value) now return Or. You can delete invocations of asOr in those cases, otherwise, please use a type annotation to widen the type, like (Good(3): Int Or ErrorMessage).") override def asOr: Nothing Or B = this def get: Nothing = throw new NoSuchElementException("Bad(" + b + ").get") def map[H](f: Nothing => H): H Or B = this.asInstanceOf[H Or B] def badMap[C](f: B => C): Nothing Or C = Bad(f(b)) def recover[H >: Nothing](f: B => H): H Or B = Good(f(b)) def recoverWith[H >: Nothing, C](f: B => H Or C): H Or C = f(b) def foreach(f: Nothing => Unit): Unit = () def flatMap[H, C >: B](f: Nothing => H Or C): H Or C = this.asInstanceOf[H Or C] def filter[C >: B](f: Nothing => Validation[C]): Nothing Or C = this def exists(p: Nothing => Boolean): Boolean = false def forall(p: Nothing => Boolean): Boolean = true def getOrElse[H >: Nothing](default: => H): H = default def orElse[H >: Nothing, C >: B](alternative: => H Or C): H Or C = alternative def toOption: None.type = None def toSeq: scala.collection.immutable.IndexedSeq[Nothing] = Vector.empty def toEither: Either[B, Nothing] = Left(b) def accumulating: Nothing Or One[B] = Bad(One(b)) def toTry(implicit ev: B <:< Throwable): Failure[Nothing] = Failure(b) def swap: B Or Nothing = Good(b) def transform[H, C](gf: Nothing => H Or C, bf: B => H Or C): H Or C = bf(b) def fold[V](gf: Nothing => V, bf: B => V): V = bf(b) }