All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalautils.Or.scala Maven / Gradle / Ivy

The newest version!
/*
 * 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.”
 *
 * 

* An Or will either be a “good” value wrapped in an instance of * Good or a “bad” value wrapped in an instance * of Bad. *

* *

The motivation for Or

* *

* Or differs from Scala's Either type in that * Either treats both its Left and Right alternatives in an identical manner, whereas * Or treats its two alternatives differently: it favors * Good over Bad. * Because of this, it is more convenient to work with Ors * 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 a Some: *

* *
 * 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 a Some: *

* *
 * 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 a Some, 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 the Either. The left will be a String error * message. Here's the new parseName function, which returns an Either[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 an Either[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 an Either[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 the for 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 * the Either to a RightProjection by invoking .right at each step. * Given this implementation, the parsePerson method will now short-circuit at the first sign * of trouble (as it did when we used an Option), but you now get the first error message returned * in a Left. 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 than Either in this kind of situation. One difference to note with * Or is that the Good alternative is on the left, Bad on the right. * The reason is that Or 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 an Or, where * ErrorMessage is a type alias for String declared in the org.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 name String or, if the input string * is not a valid name, an ErrorMessage. *
* *

* 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 a Good, * short circuiting at the first sign of a Bad. 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 and Either is that Or enables * you to accumulate errors if the Bad type is an Every. * An Every is similar to a Seq in that it contains ordered elements, but * different from Seq in that it cannot be empty. An Every is * either a One, * which contains one and only one element, or a Many, which contains two or * more elements. *

* *

* Note: an Or whose Bad type is an Every, or one of its subtypes, * is called an “accumulating Or.” *

* *

* To rewrite the previous example so that errors can be accumulated, you need first to return an Every * as the Bad type. Here's how you'd change the parseName 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 name String wrapped in a * Good, or one error message, wrapped in a Bad, you would write the * Bad type as One[ErrorMessage]. The same is true for parseAge: *

* *
 * 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 first Bad encountered, you'll * need to use a different approach to write the parsePerson method. In this example, the * withGood method from trait Accumulation * 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 overloaded withGood methods that take 1 to * 22 accumulating Ors, plus a function taking the same number of corresponding * Good values. In this example, if both name and age are * Goods, the withGood method will pass the good name String * and age Int to the Person(_, _) function, and return the resulting Person * object wrapped in a Good. If either name and age, or both, * are Bad, withGood will return the accumulated errors in a Bad. *

* *

* The result of parsePerson, if Bad, will therefore contain either one or two * error messages, i.e., the result will either be a One or a Many. * As a result, the result type of parsePerson must be Person Or * Every[ErrorMessage]. Regardless of whether a Bad result contains one * or two error messages, it will contain every error message. Here's some invocations of * this accumulating version of parsePerson: *

* *
 * 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 Ors, for example, you can combine them into one Or using combined, 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 using validatedBy, 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 are Good, you'll get a * Good tuple containin both original Good values. Otherwise, you'll get a Bad * 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 to when on the Or * to submit that Or to further scrutiny. A validation function accepts a Good type and returns a Validation[E], * where E is the type in the Every in the Bad type. For an Int Or One[ErrorMessage], for example * the validation function type would be Int => 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 call when is already Bad, you get the same (Bad) Or back, because * no Good 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 call when is Good, and also passes all the validation functions (i.e., the * all return None), you again get the same Or back, but this time, a Good 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 a for expression involving an accumulating Or, 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 * infer Nothing 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 Or hierarchy was inspired in part by the disjoint union (\/) and Validation types of * scalaz, the ProcessResult type of * Typesafe Activator, and the Result type of * ScalaKittens. *

*/ sealed abstract class Or[+G,+B] { /** * Indicates whether this Or is a Good * * @return true if this Or is a Good, false if it is a Bad. */ val isGood: Boolean = false /** * Indicates whether this Or is a Bad * * @return true if this Or is a Bad, false if it is a Good. */ val isBad: Boolean = false /** * Returns the Or's value if it is a Good or throws NoSuchElementException if it is a Bad. * * @return the contained value if this is a Good * @throws NoSuchElementException if this is a Bad */ def get: G /** * Maps the given function to this Or's value if it is a Good or returns this if it is a Bad. * * @param f the function to apply * @return if this is a Good, the result of applying the given function to the contained value wrapped in a Good, * else this Bad is returned */ def map[H](f: G => H): H Or B /** * Maps the given function to this Or's value if it is a Bad or returns this if it is a Good. * * @param f the function to apply * @return if this is a Bad, the result of applying the given function to the contained value wrapped in a Bad, * else this Good is returned */ def badMap[C](f: B => C): G Or C /** * Applies the given function f to the contained value if this Or is a Good; does nothing if this Or * is a Bad. * * @param f the function to apply */ def foreach(f: G => Unit): Unit /** * Returns the given function applied to the value contained in this Or if it is a Good, * or returns this if it is a Bad. * * @param f the function to apply * @return if this is a Good, the result of applying the given function to the contained value wrapped in a Good, * else this Bad is returned */ def flatMap[H, C >: B](f: G => H Or C): H Or C /** * Returns this Or if either 1) it is a Bad or 2) it is a Good and applying the validation function f to this * Good's value returns Pass; otherwise, * returns a new Bad containing the error value contained in the Fail resulting from applying the validation * function f to this Good's value. * *

* For examples of filter used in for expressions, see the main documentation for trait * Validation. *

* * @param f the validation function to apply * @return a Good if this Or is a Good that passes the validation function, else a Bad. */ 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 to
filter, and therefore, returns the same result. */ def withFilter[C >: B](f: G => Validation[C]): G Or C = filter(f) /** * Returns true if this Or is a Good and the predicate p returns true when applied to this Good's value. * *

* Note: The exists method will return the same result as forall if this Or is a Good, but the opposite * result if this Or is a Bad. *

* * @param p the predicate to apply to the Good value, if this is a Good * @return the result of applying the passed predicate p to the Good value, if this is a Good, else false */ def exists(p: G => Boolean): Boolean /** * Returns true if either this Or is a Bad or if the predicate p returns true when applied * to this Good's value. * *

* Note: The forall method will return the same result as exists if this Or is a Good, but the opposite * result if this Or is a Bad. *

* * @param p the predicate to apply to the Good value, if this is a Good * @return the result of applying the passed predicate p to the Good value, if this is a Good, else true */ def forall(f: G => Boolean): Boolean /** * Returns, if this Or is Good, this Good's value; otherwise returns the result of evaluating default. * * @param default the default expression to evaluate if this Or is a Bad * @return the contained value, if this Or is a Good, else the result of evaluating the given default */ def getOrElse[H >: G](default: => H): H /** * Returns this Or if it is a Good, otherwise returns the result of evaluating the passed alternative. * * @param alternative the alternative by-name to evaluate if this Or is a Bad * @return this Or, if it is a Good, else the result of evaluating alternative */ def orElse[H >: G, C >: B](alternative: => H Or C): H Or C /** * Returns a Some containing the Good value, if this Or is a Good, else None. * * @return the contained “good” value wrapped in a Some, if this Or is a Good; None * if this Or is a Bad. */ def toOption: Option[G] /** * Returns an immutable IndexedSeq containing the Good value, if this Or is a Good, else an empty * immutable IndexedSeq. * * @return the contained “good” value in a lone-element Seq if this Or is a Good; an empty Seq if * this Or is a Bad. */ def toSeq: scala.collection.immutable.IndexedSeq[G] /** * Returns an Either: a Right containing the Good value, if this is a Good; a Left * containing the Bad value, if this is a Bad. * *

* Note that values effectively “switch sides” when convering an Or to an Either. If the type of the * Or on which you invoke toEither is Or[Int, ErrorMessage] for example, the result will be an * Either[ErrorMessage, Int]. The reason is that the convention for Either is that Left is used for “bad” * values and Right is used for “good” ones. *

* * @return this Good value, wrapped in a Right, or this Bad value, wrapped in a Left. */ def toEither: Either[B, G] /** * Converts this Or to an Or with the same Good type and a Bad type consisting of * One parameterized by this Or's Bad type. * *

* For example, invoking the accumulating method on an Int Or ErrorMessage would convert it to an * Int Or One[ErrorMessage]. This result type, because the Bad type is an Every, can be used * with the mechanisms provided in trait Accumulation to accumulate errors. *

* *

* Note that if this Or is already an accumulating Or, the behavior of this accumulating method does not change. * For example, if you invoke accumulating on an Int Or One[ErrorMessage] you will be rewarded with an * Int Or One[One[ErrorMessage]]. *

* * @return this Good, if this Or is a Good; or this Bad value wrapped in a One if * this Or is a Bad. */ def accumulating: G Or One[B] /** * Returns a Try: a Success containing the * Good value, if this is a Good; a Failure * containing the Bad value, if this is a Bad. * *

* Note: This method can only be called if the Bad type of this Or is a subclass * of Throwable (or Throwable itself). *

* *

* Note that values effectively “switch sides” when converting an Or to an Either. If the type of the * Or on which you invoke toEither is Or[Int, ErrorMessage] for example, the result will be an * Either[ErrorMessage, Int]. The reason is that the convention for Either is that Left is used for “bad” * values and Right is used for “good” ones. *

* * @return this Good value, wrapped in a Right, or this Bad value, wrapped in a Left. */ def toTry(implicit ev: B <:< Throwable): Try[G] /** * Returns an Or with the Good and Bad types swapped: Bad becomes Good and Good * becomes Bad. * *

* 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 swap). *

* * @return if this Or is a Good, its Good value wrapped in a Bad; if this Or is * a Bad, its Bad value wrapped in a Good. */ def swap: B Or G /** * Transforms this Or by applying the function gf to this Or's Good value if it is a Good, * or by applying bf to this Or's Bad value if it is a Bad. * * @param gf the function to apply to this Or's Good value, if it is a Good * @param bf the function to apply to this Or's Bad value, if it is a Bad * @return the result of applying the appropriate one of the two passed functions, gf or
bf
, to this Or's value */ def transform[H, C](gf: G => H Or C, bf: B => H Or C): H Or C /** * Folds this Or into a value of type V by applying the given gf function if this is * a Good else the given bf function if this is a Bad. * * @param gf the function to apply to this Or's Good value, if it is a Good * @param bf the function to apply to this Or's Bad value, if it is a Bad * @return the result of applying the appropriate one of the two passed functions, gf or bf, to this Or's value */ def fold[V](gf: G => V, bf: B => V): V } /** * The companion object for Or providing factory methods for creating Ors from Eithers and Trys. */ object Or { /** * Constructs a new Or from the given Try. * * @param theTry the Try to convert to an Or * @return a new Or whose Good type is the Try's Success type and whose * Bad type is Throwable. */ def from[G](theTry: Try[G]): G Or Throwable = theTry match { case Success(g) => Good(g) case Failure(e) => Bad(e) } /** * Constructs a new Or from the given Either. * *

* Note that values effectively “switch sides” when converting an Either to an Or. If the type of the * Either which you pass to Or.from is Either[ErrorMessage, Int] for example, the result will be an * Or[Int, ErrorMessage]. The reason is that the convention for Either is that Left is used for “bad” * values and Right is used for “good” ones. *

* * @param either the Either to convert to an Or * @return a new Or whose Good type is the Either's Right type and whose * Bad type is Either's Left 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 Good will be commonly used * to hold valid results for processes that may fail with an error instead of producing a valid result. *

* * @param g the “good” value */ final case class Good[+G,+B](g: G) extends Or[G,B] { override val isGood: Boolean = true /** * Returns this Good with the type widened to Or. * *

* This widening method can useful when the compiler infers the more specific Good type and * you need the more general Or type. Here's an example that uses foldLeft on * a List[Int] to calculate a sum, so long as only odd numbers exist in a passed List: *

* *
   * 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 be Good[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 be Int Or ErrorMessage, but the compiler thinks you want them to be the more specific * Good[Int, ErrorMessage]. You can use the asOr method to indicate you want the type to be Or * 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 the Bad type of this Good to the given type. * *

* Because Or has two types, but the Good factory method only takes a value of the “good” type, the Scala compiler will * infer Nothing for the Bad 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 specific Bad type. * Sometimes, however, you may need to specify it. In such situations you can use this orBad 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 for Good that offers, in addition to the standard factory method * for Good that takes single “good” type, an parameterless apply * used to narrow the Good type when creating a Bad. */ object Good { /** * Supports the syntax that enables Bad instances to be created with a specific * Good type. */ class GoodType[G] { /** * Factory method for Bad instances whose Good type is specified * by the type parameter of this GoodType. * *

* This method enables this syntax: *

* *
     * Good[Int].orBad("oops")
     *           ^
     * 
* * @param b the “bad” value * @return a new Bad instance containing the passed b value */ def orBad[B](b: B): Bad[G, B] = Bad[G, B](b) override def toString: String = "GoodType" } /** * Captures a Good type to enable a Bad to be constructed with a specific * Good type. * *

* Because Or has two types, but the Bad factory method only takes a value of the “bad” type, the Scala compiler will * infer Nothing for the Good 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 specific Good 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 Bad will be commonly used * to hold descriptions of an error (or several, accumulated errors). Some examples of possible error descriptions * are String error messages, Int error codes, Throwable exceptions, * or instances of a case class hierarchy designed to describe errors. *

* * @param b the “bad” value */ final case class Bad[+G,+B](b: B) extends Or[G,B] { override val isBad: Boolean = true /** * Returns this Bad with the type widened to Or. * *

* This widening method can useful when the compiler infers the more specific Bad type and * you need the more general Or type. Here's an example that uses foldLeft on * a List[Int] to find the first even number in the List: *

* *
   * 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 be Bad[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 be Int Or String, but the compiler thinks you want them to be the more specific * Bad[Int, String]. You can use the asOr method to indicate you want the type to be Or * 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) }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy