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 Or
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 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.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 String
Or
ErrorMessage
result
type like this:
TheparseName
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 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 Or
s, plus a function taking the same number of corresponding
Good
values. In this example, if both name
and age
are
Good
s, 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 Or
s, 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
Or
s, 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 Or
s 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.scalactic.Good[Int,Nothing] = Good(3) scala> Bad("oops") res1: org.scalactic.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.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 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.
Value members
Abstract methods
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.
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]]
.
- Returns:
this
Good
, if thisOr
is aGood
; or thisBad
value wrapped in aOne
if thisOr
is aBad
.- Source:
- Or.scala
Maps the given function to this Or
's value if it is a Bad
or returns this
if it is a Good
.
Maps the given function to this Or
's value if it is a Bad
or returns this
if it is a Good
.
- Value parameters:
- f
the function to apply
- Returns:
if this is a
Bad
, the result of applying the given function to the contained value wrapped in aBad
, else thisGood
is returned- Source:
- Or.scala
Returns true
if this Or
is a Good
and the predicate p
returns true when applied to this Good
's value.
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
.
- Value parameters:
- p
the predicate to apply to the
Good
value, if this is aGood
- Returns:
the result of applying the passed predicate
p
to theGood
value, if this is aGood
, elsefalse
- Source:
- Or.scala
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.
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
.
- Value parameters:
- f
the validation function to apply
- Returns:
a
Good
if thisOr
is aGood
that passes the validation function, else aBad
.- Source:
- Or.scala
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
.
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
.
- Value parameters:
- f
the function to apply
- Returns:
if this is a
Good
, the result of applying the given function to the contained value wrapped in aGood
, else thisBad
is returned- Source:
- Or.scala
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
.
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
.
- Value parameters:
- bf
the function to apply to this
Or
'sBad
value, if it is aBad
- gf
the function to apply to this
Or
'sGood
value, if it is aGood
- Returns:
the result of applying the appropriate one of the two passed functions,
gf
or bf, to thisOr
's value- Source:
- Or.scala
Returns true
if either this Or
is a Bad
or if the predicate p
returns true
when applied
to this Good
's value.
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
.
- Value parameters:
- p
the predicate to apply to the
Good
value, if this is aGood
- Returns:
the result of applying the passed predicate
p
to theGood
value, if this is aGood
, elsetrue
- Source:
- Or.scala
Applies the given function f to the contained value if this Or
is a Good
; does nothing if this Or
is a Bad
.
Applies the given function f to the contained value if this Or
is a Good
; does nothing if this Or
is a Bad
.
- Value parameters:
- f
the function to apply
- Source:
- Or.scala
Returns the Or
's value if it is a Good
or throws NoSuchElementException
if it is a Bad
.
Returns the Or
's value if it is a Good
or throws NoSuchElementException
if it is a Bad
.
- Returns:
the contained value if this is a
Good
- Throws:
- NoSuchElementException
if this is a
Bad
- Source:
- Or.scala
Returns, if this Or
is Good
, this Good
's value; otherwise returns the result of evaluating default
.
Returns, if this Or
is Good
, this Good
's value; otherwise returns the result of evaluating default
.
- Value parameters:
- default
the default expression to evaluate if this
Or
is aBad
- Returns:
the contained value, if this
Or
is aGood
, else the result of evaluating the givendefault
- Source:
- Or.scala
Maps the given function to this Or
's value if it is a Good
or returns this
if it is a Bad
.
Maps the given function to this Or
's value if it is a Good
or returns this
if it is a Bad
.
- Value parameters:
- f
the function to apply
- Returns:
if this is a
Good
, the result of applying the given function to the contained value wrapped in aGood
, else thisBad
is returned- Source:
- Or.scala
Returns this Or
if it is a Good
, otherwise returns the result of evaluating the passed alternative
.
Returns this Or
if it is a Good
, otherwise returns the result of evaluating the passed alternative
.
- Value parameters:
- alternative
the alternative by-name to evaluate if this
Or
is aBad
- Returns:
this
Or
, if it is aGood
, else the result of evaluatingalternative
- Source:
- Or.scala
Maps the given function to this Or
's value if it is a Bad
, transforming it into a Good
, or returns
this
if it is already a Good
.
Maps the given function to this Or
's value if it is a Bad
, transforming it into a Good
, or returns
this
if it is already a Good
.
- Value parameters:
- f
the function to apply
- Returns:
if this is a
Bad
, the result of applying the given function to the contained value wrapped in aGood
, else thisGood
is returned- Source:
- Or.scala
Maps the given function to this Or
's value if it is a Bad
, returning the result, or returns
this
if it is already a Good
.
Maps the given function to this Or
's value if it is a Bad
, returning the result, or returns
this
if it is already a Good
.
- Value parameters:
- f
the function to apply
- Returns:
if this is a
Bad
, the result of applying the given function to the contained value, else thisGood
is returned- Source:
- Or.scala
Returns an Or
with the Good
and Bad
types swapped: Bad
becomes Good
and Good
becomes Bad
.
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.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 swap
).
- Returns:
if this
Or
is aGood
, itsGood
value wrapped in aBad
; if thisOr
is aBad
, itsBad
value wrapped in aGood
.- Source:
- Or.scala
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
.
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.
- Returns:
this
Good
value, wrapped in aRight
, or thisBad
value, wrapped in aLeft
.- Source:
- Or.scala
Returns a Some
containing the Good
value, if this Or
is a Good
, else None
.
Returns a Some
containing the Good
value, if this Or
is a Good
, else None
.
- Returns:
the contained “good” value wrapped in a
Some
, if thisOr
is aGood
;None
if thisOr
is aBad
.- Source:
- Or.scala
Returns an immutable IndexedSeq
containing the Good
value, if this Or
is a Good
, else an empty
immutable IndexedSeq
.
Returns an immutable IndexedSeq
containing the Good
value, if this Or
is a Good
, else an empty
immutable IndexedSeq
.
- Returns:
the contained “good” value in a lone-element
Seq
if thisOr
is aGood
; an emptySeq
if thisOr
is aBad
.- Source:
- Or.scala
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
.
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.
- Returns:
this
Good
value, wrapped in aRight
, or thisBad
value, wrapped in aLeft
.- Source:
- Or.scala
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
.
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
.
- Value parameters:
- bf
the function to apply to this
Or
'sBad
value, if it is aBad
- gf
the function to apply to this
Or
'sGood
value, if it is aGood
- Returns:
the result of applying the appropriate one of the two passed functions,
gf
or bf, to thisOr
's value- Source:
- Or.scala
Concrete methods
Currently just forwards to filter, and therefore, returns the same result.
Currently just forwards to filter, and therefore, returns the same result.
- Source:
- Or.scala
Deprecated methods
The asOr
method has been deprecated and will be removed in a future version of Scalactic.
Please remove invocations of asOr
in expressions of type Good(value).orBad[Type]
and
Good[Type].orBad(value)
(which now return a type already widened to Or
), otherwise please
use a type annotation to widen the type, such as: (Good(3): Int Or ErrorMessage)
.
The asOr
method has been deprecated and will be removed in a future version of Scalactic.
Please remove invocations of asOr
in expressions of type Good(value).orBad[Type]
and
Good[Type].orBad(value)
(which now return a type already widened to Or
), otherwise please
use a type annotation to widen the type, such as: (Good(3): Int Or ErrorMessage)
.
- Deprecated
- Source:
- Or.scala
Inherited methods
Concrete fields
Indicates whether this Or
is a Bad
Indicates whether this Or
is a Bad
- Returns:
true if this
Or
is aBad
,false
if it is aGood
.- Source:
- Or.scala