I am learning Scala and today came across the Fail Slow mechanism using Scalaz ValidationNel however it is really difficult to understand how to use it. I am reading these blogs: Blog1 , I am reading this StackOverflow post too: StackOverflow but it is really difficult to understand for non functional programmer. Can somebody provide a simple example on how to accumulate errors in ValidationNel in Scala? It will be really helpful to have a description about the example too.
Using the example from the blog you've linked
val sumV: ValidationNEL[String, Int] = for {
a <- 42.successNel[String]
b <- "Boo".failNel[Int]
c <- "Wah wah".failNel[Int] // by defn of flatMap, can't get here
} yield a + b + c
What this is doing is using a flatMap to chain together various operations. 42.successNel[String], for example, creates a Success, and "Boo".failNel[Int] creates a failure. The way flatMap works here is to continue on to the next operations only on a success. So this is a "fail fast" operation - it gathers the first failure into your error case and stops.
If you want to "fail slow" - ie. gather all possible failures, you need to use a different method. This is where Applicative comes in.
val yes = 3.14.successNel[String]
val doh = "Error".failNel[Double]
def addTwo(x: Double, y: Double) = x + y
(yes |#| yes)(addTwo) // Success(6.28)
(doh |#| doh)(addTwo) // Failure(NonEmptyList(Error, Error))
(a |#| b)(someFunctionOfTwoArgsHere) - What this is saying is "Perform the 'a' operation, and perform the 'b' operation, and if both are successful, perform someFunctionOfTwoArgsHere(a,b). Otherwise, take any failures and combine them. So if a fails, but b succeeds, you get a Validation failure with the result of a failing. If a AND b fails, you get a Validation failure with the results of both a and b failing.
Previous answer is good but I understand you come from OOP paradigm so let me put another example comparing both paradigms.
Common code:
val a = "1"
val b = "aaa"
val c = "bbb"
def isAllDigits(x: String) = x forall Character.isDigit
def appendError(x: String, errors: mutable.Buffer[String]) = errors += s"$x is not number"
type Errors = NonEmptyList[String]
// disjunction \/ for fail fast
def toDigitFailFast(x: String): Errors \/ Int = {
if (isAllDigits(x)) {
x.toInt.right
} else {
s"$x is not number".wrapNel.left
}
}
// validation nel (non empty list) for fail slow
def toDigitFS(x: String): ValidationNel[String, Int] = {
if (x forall Character.isDigit) {
x.toInt.successNel
} else {
s"$x is not number".failureNel
}
}
Code for fail fast imperative:
// fail fast imperative programming
println("---\nFail Fast imperative")
val failFastErrors = mutable.Buffer.empty[String]
if(isAllDigits(a)) {
if(isAllDigits(b)) {
if(isAllDigits(c)) {
val total = a.toInt + b.toInt + c.toInt
println(s"Total = ${total}!!")
} else {
appendError(c, failFastErrors)
}
} else {
appendError(b, failFastErrors)
}
} else {
appendError(a, failFastErrors)
}
if(failFastErrors.nonEmpty) {
println("Errors:")
for(error <- failFastErrors) {
println(error)
}
}
Code for fail fast functional (with disjunction /):
val resultFunc = for {
x <- toDigitFailFast(a)
y <- toDigitFailFast(b)
z <- toDigitFailFast(c)
} yield (x + y + z)
resultFunc match {
case \/-(total) => println(s"Total = $total")
case -\/(errors) =>
println("Errors:")
errors.foreach(println)
}
Output on fail fast (only tells you first error):
Fail Fast imperative
Errors:
aaa is not number
Fail Fast functional
Errors:
aaa is not number
Now the fail slow code for imperative:
// fail slow imperative programming
println("---\nFail Slow imperative")
val failSlowErrors = mutable.Buffer.empty[String]
if(!isAllDigits(a)) {
appendError(a, failSlowErrors)
}
if(!isAllDigits(b)) {
appendError(b, failSlowErrors)
}
if(!isAllDigits(c)) {
appendError(c, failSlowErrors)
}
if(failSlowErrors.isEmpty) {
val total = a.toInt + b.toInt + c.toInt
println(s"Total = ${total}!!")
} else {
println("Errors:")
for(error <- failSlowErrors) {
println(error)
}
}
And the functional version (fail slow):
// fail slow functional programming
println("---\nFail Slow functional")
val resultFuncSlow =
(toDigitFS(a) |#| toDigitFS(b) |#| toDigitFS(c)) { _ + _ + _ }
resultFuncSlow match {
case Success(result) => println(result)
case Failure(errors) =>
println("Errors:")
errors.foreach(println)
}
And the output with both errors:
Fail Slow imperative
Errors:
aaa is not number
bbb is not number
---
Fail Slow functional
Errors:
aaa is not number
bbb is not number
Related
I am trying to do some handson with scala basic operations and got stuck here in the following sample code
def insuranceRateQuote(a: Int, tickets:Int) : Either[Exception, Double] = {
// ... something
Right(Double)
}
def parseInsuranceQuoteFromWebForm(age: String, numOfTickets: String) : Either[Exception, Double]= {
try{
val a = Try(age.toInt)
val tickets = Try(numOfTickets.toInt)
for{
aa <- a
t <- tickets
} yield insuranceRateQuote(aa,t) // ERROR HERE
} catch {
case _ => Left(new Exception)}
}
The Error I am getting is that it says found Try[Either[Exception,Double]]
I am not getting why it is wrapper under Try of Either
PS - This must not be the perfect way to do in scala so feel free to post your sample code :)
The key to understand is that for-comprehensions might transform what is inside the wrapper but will not change the wrapper itself. The reason is because for-comprehension de-sugar to map/flatMap calls on the wrapper determined in the first step of the chain. For example consider the following snippet
val result: Try[Int] = Try(41).map(v => v + 1)
// result: scala.util.Try[Int] = Success(42)
Note how we transformed the value inside the Try wrapper from 41 to 42 however the wrapper remained unchanged. Alternatively we could express the same thing using a for-comprehension
val result: Try[Int] = for { v <- Try(41) } yield v + 1
// result: scala.util.Try[Int] = Success(42)
Note how the effect is exactly the same. Now consider the following for comprehension which chains multiple steps
val result: Try[Int] =
for {
a <- Try(41) // first step determines the wrapper for all the other steps
b <- Try(1)
} yield a + b
// result: scala.util.Try[Int] = Success(42)
This expands to
val result: Try[Int] =
Try(41).flatMap { (a: Int) =>
Try(1).map { (b: Int) => a + b }
}
// result: scala.util.Try[Int] = Success(42)
where again we see the result is the same, namely, a value transformed inside the wrapper but wrapper remained untransformed.
Finally consider
val result: Try[Either[Exception, Int]] =
for {
a <- Try(41) // first step still determines the top-level wrapper
b <- Try(1)
} yield Right(a + b) // here we wrap inside `Either`
// result: scala.util.Try[Either[Exception,Int]] = Success(Right(42))
The principle remains the same - we did wrap a + b inside Either however this does not affect the top-level outer wrapper which is still Try.
Mario Galic's answer already explains the problem with your code, but I'd fix it differently.
Two points:
Either[Exception, A] (or rather, Either[Throwable, A]) is kind of equivalent to Try[A], with Left taking the role of Failure and Right the role of Success.
The outer try/catch is not useful because the exceptions should be captured by working in Try.
So you probably want something like
def insuranceRateQuote(a: Int, tickets:Int) : Try[Double] = {
// ... something
Success(someDouble)
}
def parseInsuranceQuoteFromWebForm(age: String, numOfTickets: String): Try[Double] = {
val a = Try(age.toInt)
val tickets = Try(numOfTickets.toInt)
for{
aa <- a
t <- tickets
q <- insuranceRateQuote(aa,t)
} yield q
}
A bit unfortunately, this does a useless map(q => q) if you figure out what the comprehension does, so you can write it more directly as
a.flatMap(aa => tickets.flatMap(t => insuranceRateQuote(aa,t)))
I'm writing code in scala/play with anorm/postgres for match generation based on users profiles. The following code works, but I've commented out the section that is causing problems, the while loop. I noticed while running it that the first 3 Futures seem to work synchronously but the problem comes when I'm retrieving the count of rows in the table in the fourth step.
The fourth step returns the count before the above insert's actually happened. As far as I can tell, steps 1-3 are being queued up for postgres synchronously, but the call to retrieve the count seems to return BEFORE the first 3 steps complete, which makes no sense to me. If the first 3 steps get queued up in the correct order, why wouldn't the fourth step wait to return the count until after the inserts happen?
When I uncomment the while loop, the match generation and insert functions are called until memory runs out, as the count returned is continually below the desired threshold.
I know the format itself is subpar, but my question is not about how to write the most elegant scala code, but merely how to get it to work for now.
def matchGeneration(email:String,itNum:Int) = {
var currentIterationNumber = itNum
var numberOfMatches = MatchData.numberOfCurrentMatches(email)
while(numberOfMatches < 150){
Thread.sleep(25000)//delay while loop execution time
generateUsers(email) onComplete {
case(s) => {
print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 1")
Thread.sleep(5000)//Time for initial user generation to take place
genDemoMatches(email, currentIterationNumber) onComplete {
case (s) => {
print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 2")
genIntMatches(email,currentIterationNumber) onComplete {
case(s) => {
print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 3")
genSchoolWorkMatches(email,currentIterationNumber) onComplete {
case(s) => {
Thread.sleep(10000)
print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 4")
incrementNumberOfMatches(email) onComplete {
case(s) => {
currentIterationNumber+=1
println(s"current number of matches: $numberOfMatches")
println(s"current Iteration: $currentIterationNumber")
}
}
}
}
}
}
}
}
}
}
//}
}
The match functions are defined as futures, such as :
def genSchoolWorkMatches(email:String,currentIterationNumber:Int):Future[Unit]=
Future(genUsersFromSchoolWorkData(email, currentIterationNumber))
genUsersFromSchoolWorkData(email:String) follows the same form as the other two. It is a function that initially gets all the school/work fields that a user has filled out in their profile ( SELECT major FROM school_work where email='$email') and it generates a dummyUser that contains one of those fields in common with this user of email:String. It would take about 30-40 lines of code to print this function so I can explain it further if need be.
I have edited my code, the only way I found so far to get this to work was by hacking it with Thread.sleep(). I think the problem may lie with anorm
as my Future logic constructs did work as I expected, but the problem lies in the inconsistency of when writes occur versus what the read returns. The numberOfCurrentMatches(email:String) function returns the number of matches as it is a simple SELECT count(email) from table where email='$email'. The problem is that sometimes after inserting 23 matches the count returns as 0, then after a second iteration it will return 46. I assumed that the onComplete() would bind to the underlying anorm function defined with DB.withConnection() but apparently it may be too far removed to accomplish this. I am not really sure at this point what to research or look up further to try to get around this problem, rather than writing a separate sort of supervisor function to return at a value closer to 150.
UPDATE
Thanks to the advice of user's here, and trying to understand Scala's documentation at this link: Scala Futures and Promises
I have updated my code to be a bit more readable and scala-esque:
def genMatchOfTypes(email:String,iterationNumber:Int) = {
genDemoMatches(email,iterationNumber)
genIntMatches(email,iterationNumber)
genSchoolWorkMatches(email,iterationNumber)
}
def matchGeneration(email:String) = {
var currentIterationNumber = 0
var numberOfMatches = MatchData.numberOfCurrentMatches(email)
while (numberOfMatches < 150) {
println(s"current number of matches: $numberOfMatches")
Thread.sleep(30000)
generateUsers(email)
.flatMap(users => genMatchOfTypes(email,currentIterationNumber))
.flatMap(matches => incrementNumberOfMatches(email))
.map{
result =>
currentIterationNumber += 1
println(s"current Iteration2: $currentIterationNumber")
numberOfMatches = MatchData.numberOfCurrentMatches(email)
println(s"current number of matches2: $numberOfMatches")
}
}
}
I still am heavily dependent upon the Thread.sleep(30000) to provide enough time to run through the while loop before it tries to loop back again. It's still an unwieldy hack. When I uncomment the Thread.sleep()
my output in bash looks like this:
users for match generation createdcurrent number of matches: 0
[error] c.MatchDataController - here is the list: jnkj
[error] c.MatchDataController - here is the list: hbhjbjjnkjn
current number of matches: 0
current number of matches: 0
current number of matches: 0
current number of matches: 0
current number of matches: 0
This of course is a truncated output. It runs like this over and over until I get errors about too many open files and the JVM/play server crashes entirely.
One solution is to use Future.traverse for known iteration count
Implying
object MatchData {
def numberOfCurrentMatches(email: String) = ???
}
def generateUsers(email: String): Future[Unit] = ???
def incrementNumberOfMatches(email: String): Future[Int] = ???
def genDemoMatches(email: String, it: Int): Future[Unit] = ???
def genIntMatches(email: String, it: Int): Future[Unit] = ???
def genSchoolWorkMatches(email: String, it: Int): Future[Unit] = ???
You can write code like
def matchGeneration(email: String, itNum: Int) = {
val numberOfMatches = MatchData.numberOfCurrentMatches(email)
Future.traverse(Stream.range(itNum, 150 - numberOfMatches + itNum)) { currentIterationNumber => for {
_ <- generateUsers(email)
_ = print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 1")
_ <- genDemoMatches(email, currentIterationNumber)
_ = print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 2")
_ <- genIntMatches(email, currentIterationNumber)
_ = print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 3")
_ <- genSchoolWorkMatches(email, currentIterationNumber)
_ = Thread.sleep(15000)
_ = print(s">>>>>>>>>>>>>>>>>>>>>>>>>>>STEP 4")
numberOfMatches <- incrementNumberOfMatches(email)
_ = println(s"current number of matches: $numberOfMatches")
_ = println(s"current Iteration: $currentIterationNumber")
} yield ()
}
Update
If you urged to check some condition each time, one way is to use cool monadic things from scalaz library. It have definition of monad for scala.Future so we can replace word monadic with asynchronous when we want to
For example StreamT.unfoldM can create conditional monadic(asynchronous) loop, even if we don need elements of resulting collection we still can use it just for iteration.
Lets define your
def generateAll(email: String, iterationNumber: Int): Future[Unit] = for {
_ <- generateUsers(email)
_ <- genDemoMatches(email, iterationNumber)
_ <- genIntMatches(email, iterationNumber)
_ <- genSchoolWorkMatches(email, iterationNumber)
} yield ()
Then iteration step
def generateStep(email: String, limit: Int)(iterationNumber: Int): Future[Option[(Unit, Int)]] =
if (MatchData.numberOfCurrentMatches(email) >= limit) Future(None)
else for {
_ <- generateAll(email, iterationNumber)
_ <- incrementNumberOfMatches(email)
next = iterationNumber + 1
} yield Some((), next)
Now our resulting function simplifies to
import scalaz._
import scalaz.std.scalaFuture._
def matchGeneration(email: String, itNum: Int): Future[Unit] =
StreamT.unfoldM(0)(generateStep(email, 150) _).toStream.map(_.force: Unit)
It looks like synchronous method MatchData.numberOfCurrentMatches is reacting on your asynchronous modification inside the incrementNumberOfMatches. Note that generally it could lead to disastrous results and you probably need to move that state inside some actor or something like that
While dealing with error-handling in Scala, I came to the point where I asked myself, whether Trys in a for-comprehension make sense.
Please regard the unit test given below. This test shows two approaches:
The first approach (with call) embeds the methods fooA and fooB, which return regular Strings, into a Try construct.
The second approach (tryCall) uses a for-comprehension that uses methods tryFooA and tryFooB, which return Try[String] each.
For what reason should one prefer the for-comprehension variant with tryCall over the call-variant?
test("Stackoverflow post: Try and for-comprehensions.") {
val iae = new IllegalArgumentException("IAE")
val rt = new RuntimeException("RT")
import scala.util.{Try, Success, Failure}
def fooA(x1: Int) : String = {
println("fooA")
if (x1 == 1) "x1 is 1" else throw iae
}
def fooB(x2: Int) : String = {
println("fooB")
if (x2 == 1) "x2 is 1" else throw rt
}
def tryFooA(x1: Int) : Try[String] = {
Try {
println("tryFooA")
if (x1 == 1) "x1 is 1" else throw iae
}
}
def tryFooB(x2: Int) : Try[String] = {
Try {
println("tryFooB")
if (x2 == 1) "x2 is 1" else throw rt
}
}
def call( x1: Int, x2: Int ) : Try[String] = {
val res: Try[String] = Try{
val a = fooA(x1)
val b = fooB(x2)
a + " " + b
}
res
}
def tryCall( x1: Int, x2: Int ): Try[String] = {
for {
a <- tryFooA(x1)
b <- tryFooB(x2)
} yield (a + " " + b)
}
assert( call(0,0) === tryCall(0,0))
assert( call(0,1) === tryCall(0,1))
assert( call(1,0) === tryCall(1,0))
assert( call(1,1) === tryCall(1,1))
}
The purpose of Try is to let the compiler help you (and anyone else who uses your code) handle and reason about errors more responsibly.
In your examples, the behavior of call and tryCall are more or less identical, and if the fooA, etc. methods were not part of any public API, there'd be little reason to prefer one over the other.
The advantage of Try is that it lets you compose operations that can fail in a clear, concise way. The signatures of tryFooA and tryFooB are upfront about the fact that they may result in (recoverable) failure, and the compiler makes sure that anyone who calls those methods must deal with that possibility. The signatures of fooA and fooB aren't self-documenting in this way, and the compiler can't provide any assurances that they'll be called responsibly.
These are good reasons to prefer the tryFoo signatures. The downside is that callers have to deal with the extra overhead of using map, flatMap, etc., instead of working with the results (if they exist) directly. The for-comprehension syntax is an attempt to minimize this overhead—you get the safety provided by Try with only a little extra syntactic overhead.
Suppose I would like to code the following logic in Scala
val xdir = System.getProperty("XDir")
if (xdir == null)
error("No XDir") // log the error and exit
val ydir = System.getProperty("YDir")
if (ydir == null)
error("No YDir")
if (!new File(xdir).isDirectory)
error("XDir is not a directory")
if (!new File(ydir).isDirectory)
error("YDir is not a directory")
if (!new File(xdir).exists)
error("XDir does not exis")
if (!new File(ydir).exists)
error("YDir does not exist")
...
(and so on)
What is the best way to code this chain of validations in Scala?
Here's some useful things:
def sysValue(prop: String) = Option(System.getProperty(prop)) //returns Option[String]
def trySysValue(prop: String) = //returns Either[String, String]
sysValue(prop) map Right getOrElse Left("Absent property: " + prop)
Then you can use monadic composition of Either through its right-projection
val batch = //batch is Either[String, (File, File)]
for {
x <- trySysValue("XDir")).right
xf <- dir(x).right
y <- trySysValue("YDir").right
yf <- dir(y).right
}
yield (xf, yf)
Where:
def dir(s: String) = { //returns Either[String, File]
val f = new File(s)
if (!f.exists()) Left("Does not exist: " + f)
else if (!f.isDir()) Left("Is not a directory: " + f)
else Right(f)
}
The left-hand-side of the Either will be an error message. This monadic composition is fail fast. You can achieve composition which will accumulate all failures (for example, if neither XDir nor YDir exist, you would see both messages) using scalaz Validation. In that case, the code would look like this:
def trySysValue(prop: String) = //returns Validation[String, String]
sysValue(prop) map Success getOrElse ("Absent property: " + prop).fail
def dir(s: String) = {
val f = new File(s)
if (!f.exists())("Does not exist: " + f).fail
else if (!f.isDir()) ("Is not a directory: " + f).fail
else f.success
}
val batch = //batch is ValidationNEL[String, (File, File)]
(trySysValue("XDir")) flatMap dir).liftFailNel <|*|> (trySysValue("YDir")) flatMap dir).liftFailNel
something like:
val batch = for{
a <- safe(doA, "A failed") either
b <- safe(doB, "B failed") either
c <- safe(doC, "C failed") either
} yield(a,b,c)
batch fold( error(_), doSuccess(_) )
Where safe performs a, you guessed it, safe (try/catch) operation that takes a failure (Left outcome) message and returns an Either RightProjection (which allows you to do above batch operation while threading through the point-of-failure error message)
class Catching[T](f: => T) {
def either(msg: String) = {
try { Right(f).right } catch { Left(msg).right }
}
}
def safe[T](f: => T) = new Catching(f)
Can add an option method to Catching class as well, along with logging if you want to log particular error types.
See Jason Zaugg's solution for right biasing Either and this thread from scala-debate on the subject as well. No consensus as yet, but most scala "heavies" seem to be in favor.
One limitation of this approach is that if you attempt to add conditionals (if a = b) to the for{} block, it won't compile (since default Either filter method returns Option). The workaround is to implement filter and withFilter, returning Either, something I have yet to figure out/do (if someone has already done so, please post)
Yes you can use validation without scalaz, see here for a self containt implementation :
http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-xhtml/apa.html
HTH
Option monad is a great expressive way to deal with something-or-nothing things in Scala. But what if one needs to log a message when "nothing" occurs? According to the Scala API documentation,
The Either type is often used as an
alternative to scala.Option where Left
represents failure (by convention) and
Right is akin to Some.
However, I had no luck to find best practices using Either or good real-world examples involving Either for processing failures. Finally I've come up with the following code for my own project:
def logs: Array[String] = {
def props: Option[Map[String, Any]] = configAdmin.map{ ca =>
val config = ca.getConfiguration(PID, null)
config.properties getOrElse immutable.Map.empty
}
def checkType(any: Any): Option[Array[String]] = any match {
case a: Array[String] => Some(a)
case _ => None
}
def lookup: Either[(Symbol, String), Array[String]] =
for {val properties <- props.toRight('warning -> "ConfigurationAdmin service not bound").right
val logsParam <- properties.get("logs").toRight('debug -> "'logs' not defined in the configuration").right
val array <- checkType(logsParam).toRight('warning -> "unknown type of 'logs' confguration parameter").right}
yield array
lookup.fold(failure => { failure match {
case ('warning, msg) => log(LogService.WARNING, msg)
case ('debug, msg) => log(LogService.DEBUG, msg)
case _ =>
}; new Array[String](0) }, success => success)
}
(Please note this is a snippet from a real project, so it will not compile on its own)
I'd be grateful to know how you are using Either in your code and/or better ideas on refactoring the above code.
Either is used to return one of possible two meaningful results, unlike Option which is used to return a single meaningful result or nothing.
An easy to understand example is given below (circulated on the Scala mailing list a while back):
def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] =
try {
Right(block)
} catch {
case ex => Left(ex)
}
As the function name implies, if the execution of "block" is successful, it will return "Right(<result>)". Otherwise, if a Throwable is thrown, it will return "Left(<throwable>)". Use pattern matching to process the result:
var s = "hello"
throwableToLeft { s.toUpperCase } match {
case Right(s) => println(s)
case Left(e) => e.printStackTrace
}
// prints "HELLO"
s = null
throwableToLeft { s.toUpperCase } match {
case Right(s) => println(s)
case Left(e) => e.printStackTrace
}
// prints NullPointerException stack trace
Hope that helps.
Scalaz library has something alike Either named Validation. It is more idiomatic than Either for use as "get either a valid result or a failure".
Validation also allows to accumulate errors.
Edit: "alike" Either is complettly false, because Validation is an applicative functor, and scalaz Either, named \/ (pronounced "disjonction" or "either"), is a monad.
The fact that Validation can accumalate errors is because of that nature. On the other hand, / has a "stop early" nature, stopping at the first -\/ (read it "left", or "error") it encounters. There is a perfect explanation here: http://typelevel.org/blog/2014/02/21/error-handling.html
See: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html
As requested by the comment, copy/paste of the above link (some lines removed):
// Extracting success or failure values
val s: Validation[String, Int] = 1.success
val f: Validation[String, Int] = "error".fail
// It is recommended to use fold rather than pattern matching:
val result: String = s.fold(e => "got error: " + e, s => "got success: " + s.toString)
s match {
case Success(a) => "success"
case Failure(e) => "fail"
}
// Validation is a Monad, and can be used in for comprehensions.
val k1 = for {
i <- s
j <- s
} yield i + j
k1.toOption assert_≟ Some(2)
// The first failing sub-computation fails the entire computation.
val k2 = for {
i <- f
j <- f
} yield i + j
k2.fail.toOption assert_≟ Some("error")
// Validation is also an Applicative Functor, if the type of the error side of the validation is a Semigroup.
// A number of computations are tried. If the all success, a function can combine them into a Success. If any
// of them fails, the individual errors are accumulated.
// Use the NonEmptyList semigroup to accumulate errors using the Validation Applicative Functor.
val k4 = (fNel <**> fNel){ _ + _ }
k4.fail.toOption assert_≟ some(nel1("error", "error"))
The snippet you posted seems very contrived. You use Either in a situation where:
It's not enough to just know the data isn't available.
You need to return one of two distinct types.
Turning an exception into a Left is, indeed, a common use case. Over try/catch, it has the advantage of keeping the code together, which makes sense if the exception is an expected result. The most common way of handling Either is pattern matching:
result match {
case Right(res) => ...
case Left(res) => ...
}
Another interesting way of handling Either is when it appears in a collection. When doing a map over a collection, throwing an exception might not be viable, and you may want to return some information other than "not possible". Using an Either enables you to do that without overburdening the algorithm:
val list = (
library
\\ "books"
map (book =>
if (book \ "author" isEmpty)
Left(book)
else
Right((book \ "author" toList) map (_ text))
)
)
Here we get a list of all authors in the library, plus a list of books without an author. So we can then further process it accordingly:
val authorCount = (
(Map[String,Int]() /: (list filter (_ isRight) map (_.right.get)))
((map, author) => map + (author -> (map.getOrElse(author, 0) + 1)))
toList
)
val problemBooks = list flatMap (_.left.toSeq) // thanks to Azarov for this variation
So, basic Either usage goes like that. It's not a particularly useful class, but if it were you'd have seen it before. On the other hand, it's not useless either.
Cats has a nice way to create an Either from exception-throwing code:
val either: Either[NumberFormatException, Int] =
Either.catchOnly[NumberFormatException]("abc".toInt)
// either: Either[NumberFormatException,Int] = Left(java.lang.NumberFormatException: For input string: "abc")
in https://typelevel.org/cats/datatypes/either.html#working-with-exception-y-code