I am looking at following snippet. When map-getOrElse and nested patten matching increases in the code it doesn't look so elegant. What better options do you suggest?
case MyMessage =>
val image = (request \ "image").asOpt[String]
image.map { im =>
val conf = (request \ "confirmation").asOpt[String]
conf.map { cf =>
//code to retrieve ride
ride match {
case Some(r) =>
if (booleanCondition) sender ! SuccessCommand(JsBoolean(true), command)
else sender ! FailureCommand("Problem updating", command)
case None => sender ! FailureCommand("Ride empty", command)
}
} getOrElse (sender ! FailureCommand("Missing number", command))
} getOrElse (sender ! FailureCommand("Missing image", command))
Whenever you are mapping over an Option with a function that produces an Option, you should consider whether you should be using flatMap:
def f(x: Int): Option[Int] = Some(x + 1)
f(1).flatMap(x => f(x)).flatMap(y => f(y)) // Some(4)
f(1).flatMap(x => f(x)).flatMap(y => f(y)).getOrElse(0) // 4
You can also use for-comprehensions for this, which is really nice for having clean code when you have long chains of these:
(for(x <- f(1); y <- f(x); z <- f(y)) yield z).getOrElse(0)
Another way to tackle this is to return Either[Command,String] from various helper functions, rather than Option. This would then allow you to use a for comprehension, something like the following:
val result = for {
i <- getImage().right
c <- getConf().right
r <- getRide().right
z <- check(r).right
} yield z
// extract either left or right, whichever is occupied
sender ! result.fold(identity, _ => success())
This has the desired property that we stop as soon as we encounter an error, and capture that specific error - or proceed to a successful conclusion.
I think you should be able to collapse a lot of this into Option.fold(), roughly as follows:
case MyMessage =>
sender !
getImage().fold(fail("Missing image")) { im =>
getConf().fold(fail("Missing number")) { conf => // conf isn't used
getRide().fold(fail("Ride empty")) { r =>
if (booleanCondition) succeed(true)
else fail("Problem updating")
}
}
}
This turns out a bit more concise than flatMap and orElse in this situation (see below)
Option.fold(ifEmpty){f} returns ifEmpty (evaluated lazily) if the option was empty, or evaluates the function f if the option was full.
The above code assumes you create helper functions for getting the various Options (or you could inline the relevant code). It also assumes you pull out the creation of commands into a helper function or two, to avoid all the duplicate references to command.
For comparison, a solution using flatMap looks something like:
case MyMessage =>
sender !
getImage().flatMap { im =>
getConf().flatMap { conf =>
getRide().flatMap { r =>
if (booleanCondition) Some(succeed(true))
else Some(fail("Problem updating"))
}.orElse(Some(fail("Ride Empty")))
}.orElse(Some(fail("Missing number")))
}.getOrElse(fail("Missing image"))
which you could shorten very slightly by having variants of your helper methods (fail and succeed) that return Some[Command] rather than Command
Related
I have recently read Manuel Bernhardt's new book Reactive Web Applications. In his book, he states that Scala developers should never use .get to retrieve an optional value.
I want to pick up his suggestions but I am struggling to avoid .get when using for comprehensions for Futures.
Let's say I have the following code:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
userId <- user.id
_ <- accountTokenService.save(AccountToken.create(userId, email))
} yield {
Logger.info("Foo bar")
}
Normally, I would have used AccountToken.create(user.id.get, email) instead of AccountToken.create(userId, email). However, when trying to avoid this bad practice, I get the following exception:
[error] found : Option[Nothing]
[error] required: scala.concurrent.Future[?]
[error] userId <- user.id
[error] ^
How can I solve this?
First option
If you really want to use for comprehension you'll have to separate it to several fors, where each works with the same monad type:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
} yield for {
userId <- user.id
} yield for {
_ <- accountTokenService.save(AccountToken.create(userId, email))
}
Second option
Another option is to avoid Future[Option[T]] altogether and use Future[T] which can materialize into Failure(e) where e is a NoSuchElementException whenever you expect a None (in your case, the accountService.save() method):
def saveWithoutOption(account: Account): Future[User] = {
this.save(account) map { userOpt =>
userOpt.getOrElse(throw new NoSuchElementException)
}
}
Then you'll have:
(for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.saveWithoutOption(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
_ <- accountTokenService.save(AccountToken.create(user.id, email))
} yield {
Logger.info("Foo bar")
}) recover {
case t: NoSuchElementException => Logger.error("boo")
}
Third option
Fall back to map/flatMap and introduce intermediate results.
Let's take a step back and explore the meaning of our expression:
A Future is "eventually a value (but might fail)"
An Option is "maybe a value"
What are the semantics of Future[Option]? Let's explore the values to gain some intuition:
Future[Option]
Success(Some(x)) => Good. Let's do stuff with x
Success(None) => Finished but got nothing => This is probably an application-level error
Failure(_) => Something went wrong, so we don't have a value
We can flatten Success(None) into a Failure(SomeApplicationException) and eliminate the need of handling the Option separately.
For that, we can define a generic function to turn an Option into a Future and use the for-comprehension to apply the flattening.
def optionToFuture[T](opt:Option[T], ex: ()=>Exception):Future[T] = opt match {
case Some(v) => Future.successful(v)
case None => Future.failed(ex())
}
We can now express our computation fluently with a for-comprehension:
for {
avatarUrl <- avatarService.retrieve(email)
user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
userId <- optionToFuture(user.id, () => new UserNotFoundException(email))
_ <- accountTokenService.save(AccountToken.create(userId, email))
} yield {
Logger.info("Foo bar")
}
Stop Option propogation by failing the Future when option is None
Fail the future when id is none and abort
for {
....
accountOpt <-
user.id.map { id =>
Account.create(id, ...)
}.getOrElse {
Future.failed(new Exception("could not create account."))
}
...
} yield result
Better to have a custom exception like
case class NoIdException(msg: String) extends Exception(msg)
invoking .get on Option should be done only if you are sure that option is Some(x) otherwise .get will throw an exception.
Thats by using .get is not good practise because it may cause an exception in the code.
Instead of .get its good practice to use getOrElse.
You can map or flatMap the option to get access to the inner value.
Good practice
val x: Option[Int] = giveMeOption()
x.getOrElse(defaultValue)
Get can be used here
val x: Option[Int] = giveMeOption()
x.OrElse(Some(1)).get
Say I have three database access functions foo, bar, and baz that can each return Option[A] where A is some model class, and the calls depend on each other.
I would like to call the functions sequentially and in each case, return an appropriate error message if the value is not found (None).
My current code looks like this:
Input is a URL: /x/:xID/y/:yID/z/:zID
foo(xID) match {
case None => Left(s"$xID is not a valid id")
case Some(x) =>
bar(yID) match {
case None => Left(s"$yID is not a valid id")
case Some(y) =>
baz(zID) match {
case None => Left(s"$zID is not a valid id")
case Some(z) => Right(process(x, y, z))
}
}
}
As can be seen, the code is badly nested.
If instead, I use a for comprehension, I cannot give specific error messages, because I do not know which step failed:
(for {
x <- foo(xID)
y <- bar(yID)
z <- baz(zID)
} yield {
Right(process(x, y, z))
}).getOrElse(Left("One of the IDs was invalid, but we do not know which one"))
If I use map and getOrElse, I end up with code almost as nested as the first example.
Is these some better way to structure this to avoid the nesting while allowing specific error messages?
You can get your for loop working by using right projections.
def ckErr[A](id: String, f: String => Option[A]) = (f(id) match {
case None => Left(s"$id is not a valid id")
case Some(a) => Right(a)
}).right
for {
x <- ckErr(xID, foo)
y <- ckErr(yID, bar)
z <- ckErr(zID, baz)
} yield process(x,y,z)
This is still a little clumsy, but it has the advantage of being part of the standard library.
Exceptions are another way to go, but they slow things down a lot if the failure cases are common. I'd only use that if failure was truly exceptional.
It's also possible to use non-local returns, but it's kind of awkward for this particular setup. I think right projections of Either are the way to go. If you really like working this way but dislike putting .right all over the place, there are various places you can find a "right-biased Either" which will act like the right projection by default (e.g. ScalaUtils, Scalaz, etc.).
Instead of using an Option I would instead use a Try. That way you have the Monadic composition that you'd like mixed with the ability to retain the error.
def myDBAccess(..args..) =
thingThatDoesStuff(args) match{
case Some(x) => Success(x)
case None => Failure(new IdError(args))
}
I'm assuming in the above that you don't actually control the functions and can't refactor them to give you a non-Option. If you did, then simply substitute Try.
I know this question was answered some time back, but I wanted to give an alternative to the accepted answer.
Given that, in your example, the three Options are independent, you can treat them as Applicative Functors and use ValidatedNel from Cats to simplify and aggregate the handling of the unhappy path.
Given the code:
import cats.data.Validated.{invalidNel, valid}
def checkOption[B, T](t : Option[T])(ifNone : => B) : ValidatedNel[B, T] = t match {
case None => invalidNel(ifNone)
case Some(x) => valid(x)
def processUnwrappedData(a : Int, b : String, c : Boolean) : String = ???
val o1 : Option[Int] = ???
val o2 : Option[String] = ???
val o3 : Option[Boolean] = ???
You can then replicate obtain what you want with:
//import cats.syntax.cartesian._
(
checkOption(o1)(s"First option is not None") |#|
checkOption(o2)(s"Second option is not None") |#|
checkOption(o3)(s"Third option is not None")
) map (processUnwrappedData)
This approach will allow you to aggregate failures, which was not possible in your solution (as using for-comprehensions enforces sequential evaluation). More examples and documentation can be found here and here.
Finally this solution uses Cats Validated but could easily be translated to Scalaz Validation
I came up with this solution (based on #Rex's solution and his comments):
def ifTrue[A](boolean: Boolean)(isFalse: => A): RightProjection[A, Unit.type] =
Either.cond(boolean, Unit, isFalse).right
def none[A](option: Option[_])(isSome: => A): RightProjection[A, Unit.type] =
Either.cond(option.isEmpty, Unit, isSome).right
def some[A, B](option: Option[A])(ifNone: => B): RightProjection[B, A] =
option.toRight(ifNone).right
They do the following:
ifTrue is used when a function returns a Boolean, with true being the "success" case (e.g.: isAllowed(userId)). It actually returns Unit so should be used as _ <- ifTrue(...) { error } in a for comprehension.
none is used when a function returns an Option with None being the "success" case (e.g.: findUser(email) for creating accounts with unique email addresses). It actually returns Unit so should be used as _ <- none(...) { error } in a for comprehension.
some is used when a function returns an Option with Some() being the "success" case (e.g.: findUser(userId) for a GET /users/userId). It returns the contents of the Some: user <- some(findUser(userId)) { s"user $userId not found" }.
They are used in a for comprehension:
for {
x <- some(foo(xID)) { s"$xID is not a valid id" }
y <- some(bar(yID)) { s"$yID is not a valid id" }
z <- some(baz(zID)) { s"$zID is not a valid id" }
} yield {
process(x, y, z)
}
This returns an Either[String, X] where the String is an error message and the X is the result of calling process.
I have some code for validating ip addresses that looks like the following:
sealed abstract class Result
case object Valid extends Result
case class Malformatted(val invalid: Iterable[IpConfig]) extends Result
case class Duplicates(val dups: Iterable[Inet4Address]) extends Result
case class Unavailable(val taken: Iterable[Inet4Address]) extends Result
def result(ipConfigs: Iterable[IpConfig]): Result = {
val invalidIpConfigs: Iterable[IpConfig] =
ipConfigs.filterNot(ipConfig => {
(isValidIpv4(ipConfig.address)
&& isValidIpv4(ipConfig.gateway))
})
if (!invalidIpConfigs.isEmpty) {
Malformatted(invalidIpConfigs)
} else {
val ipv4it: Iterable[Inet4Address] = ipConfigs.map { ipConfig =>
InetAddress.getByName(ipConfig.address).asInstanceOf[Inet4Address]
}
val dups = ipv4it.groupBy(identity).filter(_._2.size != 1).keys
if (!dups.isEmpty) {
Duplicates(dups)
} else {
val ipAvailability: Map[Inet4Address, Boolean] =
ipv4it.map(ip => (ip, isIpAvailable(ip)))
val taken: Iterable[Inet4Address] = ipAvailability.filter(!_._2).keys
if (!taken.isEmpty) {
Unavailable(taken)
} else {
Valid
}
}
}
}
I don't like the nested ifs because it makes the code less readable. Is there a nice way to linearize this code? In java, I might use return statements, but this is discouraged in scala.
I personally advocate using a match everywhere you can, as it in my opinion usually makes code very readable
def result(ipConfigs: Iterable[IpConfig]): Result =
ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
case Nil =>
val ipv4it = ipConfigs.map { ipc =>
InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address]
}
ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
case Nil =>
val taken = ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys
if (taken.nonEmpty) Unavailable(taken) else Valid
case dups => Duplicates(dups)
}
case invalid => Malformatted(invalid)
}
Note that I've chosen to match on the else part first, since you generally go from specific to generic in matches, since Nil is a subclass of Iterable I put that as the first case, eliminating the need for an i if i.nonEmpty in the other case, since it would be a given if it didn't match Nil
Also a thing to note here, all your vals don't need the type explicitly defined, it significantly declutters the code if you write something like
val ipAvailability: Map[Inet4Address, Boolean] =
ipv4it.map(ip => (ip, isIpAvailable(ip)))
as simply
val ipAvailability = ipv4it.map(ip => (ip, isIpAvailable(ip)))
I've also taken the liberty of removing many one-off variables I didn't find remotely necessary, as all they did was add more lines to the code
A thing to note here about using match over nested ifs, is that is that it's easier to add a new case than it is to add a new else if 99% of the time, thereby making it more modular, and modularity is always a good thing.
Alternatively, as suggested by Nathaniel Ford, you can break it up into several smaller methods, in which case the above code would look like so:
def result(ipConfigs: Iterable[IpConfig]): Result =
ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
case Nil => wellFormatted(ipConfigs)
case i => Malformatted(i)
}
def wellFormatted(ipConfigs: Iterable[IpConfig]): Result = {
val ipv4it = ipConfigs.map(ipc => InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address])
ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
case Nil => noDuplicates(ipv4it)
case dups => Duplicates(dups)
}
}
def noDuplicates(ipv4it: Iterable[IpConfig]): Result =
ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys match {
case Nil => Valid
case taken => Unavailable(taken)
}
This has the benefit of splitting it up into smaller more manageable chunks, while keeping to the FP ideal of having functions that only do one thing, but do that one thing well, rather than having god-methods that do everything.
Which style you prefer, of course is up to you.
This has some time now but I will add my 2 cents. The proper way to handle this is with Either. You can create a method like:
def checkErrors[T](errorList: Iterable[T], onError: Result) : Either[Result, Unit] = if(errorList.isEmpty) Right() else Left(onError)
so you can use for comprehension syntax
val invalidIpConfigs = getFormatErrors(ipConfigs)
val result = for {
_ <- checkErrors(invalidIpConfigs, Malformatted(invalidIpConfigs))
dups = getDuplicates(ipConfigs)
_ <- checkErrors(dups, Duplicates(dups))
taken = getAvailability(ipConfigs)
_ <- checkErrors(taken, Unavailable(taken))
} yield Valid
If you don't want to return an Either use
result.fold(l => l, r => r)
In case of the check methods uses Futures (could be the case for getAvailability, for example), you can use cats library to be able of use it in a clean way: https://typelevel.org/cats/datatypes/eithert.html
I think it's pretty readable and I wouldn't try to improve it from there, except that !isEmpty equals to nonEmpty.
Suppose I have two Options and, if both are Some, execute one code path, and if note, execute another. I'd like to do something like
for (x <- xMaybe; y <- yMaybe) {
// do something
}
else {
// either x or y were None, handle this
}
Outside of if statements or pattern matching (which might not scale if I had more than two options), is there a better way of handling this?
Very close to your syntax proposal by using yield to wrap the for output in an Option:
val result = {
for (x <- xMaybe; y <- yMaybe) yield {
// do something
}
} getOrElse {
// either x or y were None, handle this
}
The getOrElse block is executed only if one or both options are None.
You could pattern match both Options at the same time:
(xMaybe, yMaybe) match {
case (Some(x), Some(y)) => "x and y are there"
case _ => "x and/or y were None"
}
The traverse function in Scalaz generalises your problem here. It takes two arguments:
T[F[A]]
A => F[B]
and returns F[T[B]]. The T is any traversable data structure such as List and the F is any applicative functor such as Option. Therefore, to specialise, your desired function has this type:
List[Option[A]] => (A => Option[B]) => Option[List[B]]
So put all your Option values in a List
val z = List(xMaybe, yMaybe)
Construct the function got however you want to collection the results:
val f: X => Option[Y] = ...
and call traverse
val r = z traverse f
This programming patterns occurs very often. It has a paper that talks all about it, The Essence of the Iterator Pattern.
note: I just wanted to fix the URL but the CLEVER edit help tells me I need to change at least 6 characters so I include this useful link too (scala examples):
http://etorreborre.blogspot.com/2011/06/essence-of-iterator-pattern.html
Why would something like this not work?
val opts = List[Option[Int]](Some(1), None, Some(2))
if (opts contains None) {
// Has a None
} else {
// Launch the missiles
val values = opts.map(_.get) // We know that there is no None in the list so get will not throw
}
If you don't know the number of values you are dealing with, then Tony's answer is the best. If you do know the number of values you are dealing with then I would suggest using an applicative functor.
((xMaybe |#| yMaybe) { (x, y) => /* do something */ }).getOrElse(/* something else */)
You said you want the solution to be scalable:
val optional = List(Some(4), Some(3), None)
if(optional forall {_.isDefined}) {
//All defined
} else {
//At least one not defined
}
EDIT: Just saw that Emil Ivanov's solution is a bit more elegant.
Starting Scala 2.13, we can alternatively use Option#zip which concatenates two options to Some tuple of their values if both options are defined or else None:
opt1 zip opt2 match {
case Some((x, y)) => "x and y are there"
case None => "x and/or y were None"
}
Or with Option#fold:
(opt1 zip opt2).fold("x and/or y were None"){ case (x, y) => "x and y are there" }
For scaling to many options, try something along these lines:
def runIfAllSome[A](func:(A)=>Unit, opts:Option[A]*) = {
if(opts.find((o)=>o==None) == None) for(opt<-opts) func(opt.get)
}
With this, you can do:
scala> def fun(i:Int) = println(i)
fun: (i: Int)Unit
scala> runIfAllSome(fun, Some(1), Some(2))
1
2
scala> runIfAllSome(fun, None, Some(1))
scala>
I think the key point here is to think in term of types as what you want to do. As I understand it you want to iterate over a list of Option pairs and then do something based on a certain condition. So the interesting bit of your question would be , what would the return type look like you would except? I think it would look something like this: Either[List[Option], List [Option,Option]] . on the error side (left) you would accumulate the option which was paired with a None (and was left alone so to speak) . On the right side you sum the non empty options which represent your successful values. So we would just need a function which does exactly that. Validate each pair and accumulate it according to it's result( success - failure) . I hope this helps , if not please explain in more detail your usecase. Some links to implement what I described : http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/pdf/index.pdf and : http://blog.tmorris.net/automated-validation-with-applicatives-and-semigroups-for-sanjiv/
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