I often find myself writing Scala of the form:
def foo = {
f1() match {
case Some(x1) => x1
case _ =>
f2() match {
case Some(x2) => x2
case _ =>
f3() match {
case Some(x3) => x3
case _ =>
f4()
}
}
}
}
This is the moral equivalent of Java's
Object foo() {
Object result = f1();
if (result != null) {
return result;
} else {
result = f2();
if (result != null) {
return result;
} else {
result = f3();
if (result != null) {
return result;
} else {
return f4();
}
}
}
}
and it seems ugly and unnecessarily verbose. I feel like there should be a readable way to do this in one line of Scala, but it's not clear to me what it is.
Note: I looked at Idiomatic Scala for Nested Options but it's a somewhat different case.
The idiomatic way to write nested pattern matching with options in Scala is by using the methods map, flatMap, orElse, and getOrElse.
You use map when you want to process the content of the option further and keep the optional behaviour:
So instead of this:
val opt: Option[Int] = ???
opt match {
case Some(x) => Some(x + 1)
case None => None
}
You would do this:
val opt: Option[Int] = ???
opt.map(_ + 1)
This chains much more naturally:
opt.map(_ + 1).map(_ * 3).map(_ - 2)
flatMap is verlly similar, but is used when your further operations return an option type as well.
In your particular example, orElse seems to be the most adapted solution. You can use orElse to return the option itself if not empty or else return the argument. Note that the argument is lazily evaluated so it is really equivalent to nested pattern matching/if-then-else.
def foo = {
f1().orElse(f2())
.orElse(f3())
.orElse(f4())
}
You can also combine these with getOrElse if you want to get rid of an Option. In your example you seem to return the final f4 as if it did not return an Option, so you would do the following:
def foo = {
f1().orElse(f2())
.orElse(f3())
.getOrElse(f4())
}
I know I am way late to the party, but feel that the orElse solution here is a bit clumsy. For me, the general functional idiom (not just scalaic) would be sth. along these lines (forgive me, I am not scala proficient):
def f1 = () => { println("f1 here"); null }
def f2 = () => { println("f2 here"); null }
def f3 = () => { println("f3 here"); 2 }
def f4 = () => { println("f4 here"); 3 }
def f5 = () => { println("f5 here"); 43 }
Stream(f1, f2, f3, f4, f5)
.map(f => f())
.dropWhile(_ == null)
.head
You use Stream as a lazy list, and basically, you say: Start invoking the functions and give me the first that does not evaluate to zero. Combining declarative approach and laziness gives you this generic piece of code, where the only thing you need to change when number of functions change, is the input list (stream) by adding just one more function element. That way, functions f1...fn become data, so you do not have to modify any existing code.
You could try:
f1() orElse f2() orElse Option(f3()) getOrElse f4()
Assuming that f1 and f2 returns options of the same type and f3 and f4 return a non-options of that same type
EDIT
It's not completely clear from your example if f3() returns an Option or not, so if it does then the code would be simplified to:
f1() orElse f2() orElse f3() getOrElse f4()
A slight tweak on cmbaxter's response that saves a few characters but is otherwise the same.
def foo = f1 orElse f2 orElse f3 getOrElse f4
Related
Say I have a set of rules that have a validation function that returns IO[Boolean] at runtime.
case class Rule1() {
def validate(): IO[Boolean] = IO.pure(false)
}
case class Rule2() {
def validate(): IO[Boolean] = IO.pure(false)
}
case class Rule3() {
def validate(): IO[Boolean] = IO.pure(true)
}
val rules = List(Rule1(), Rule2(), Rule3())
Now I have to iterate through these rules and see "if any of these rules" hold valid and if not then throw exception!
for {
i <- rules.map(_.validate()).sequence
_ <- if (i.contains(true)) IO.unit else IO.raiseError(new RuntimeException("Failed"))
} yield ()
The problem with the code snippet above is that it is trying to evaluate all the rules! What I really want is to exit at the encounter of the first true validation.
Not sure how to achieve this using cats effects in Scala.
I claim that existsM is the most direct way to achieve what you want. It behaves pretty much the same as exists, but for monadic predicates:
for {
t <- rules.existsM(_.validate())
_ <- IO.raiseUnless(t)(new RuntimeException("Failed"))
} yield ()
It also stops the search as soon as it finds the first true.
The raiseUnless is just some syntactic sugar that's equivalent to the if-else from your question.
If you take a look at list of available extension methods in your IDE, you can find findM:
for {
opt <- rules.findM(_.validate())
_ <- opt match {
case Some(_) => IO.unit
case None => IO.raiseError(new RuntimeException("Failed")
}
} yield ()
Doing it manually could be done with foldLeft and flatMap:
rules.foldLeft(IO.pure(false)) { (valueSoFar, nextValue) =>
valueSoFar.flatMap {
case true => IO.pure(true) // can skip evaluating nextValue
case false => nextValue.validate() // need to find the first true IO yet
}
}.flatMap {
case true => IO.unit
case false => IO.raiseError(new RuntimeException("Failed")
}
The former should have the additional advantage that it doesn't have to iterate over whole collection when it finds the first match, while the latter will still go through all items, even if will start discarding them at some point. findM solves that by using tailRecM internally to terminate the iteration on first met condition.
You can try recursive
def firstTrue(rules: List[{def validate(): IO[Boolean]}]): IO[Unit] = rules match {
case r :: rs => for {
b <- r.validate()
res <- if (b) IO.unit else firstTrue(rs)
} yield res
case _ => IO.raiseError(new RuntimeException("Failed"))
}
Another approach is not using booleans at all, but the monad capabilities of IO
def validateRules(rules: List[Rule]): IO[Unit] =
rules.traverse_ { rule =>
rule.validate().flatMap { flag =>
IO.raiseUnless(flag)(new RuntimeException("Failed"))
}
}
The trivial approach (with if,else), is known.
I'm thinking about how Scala can help me to do it in a more elegant way:
def prepareData(baseObj: BaseObj): Option[NextObj] = {
val maybeDataOne = Option(baseObj.getDataOne)
val maybeDataTwo = Option(baseObj.getDataTwo)
// return None if no DataOne or DataTwo defined
// return Some(NextObj) if at least one of the Datas defined.
// trivial solution:
if(maybeDataOne.isDefined || maybeDataTwo.isDefined) {
Some(NextObj(
dataOne = baseObj.dataOne,
dataTwo = baseObj.dataTwo
))
} else None
}
//DataOne and DataTwo will be mapped to NextObj, if, at least one, is defined
case class NextObj(d1: Option[DataOne], d2: Option[DataTwo])
maybeDataOne orElse maybeDataTwo map { _ => nextObj }
One way to make this look even prettier is to equip your NextObject class with a .toOption method:
def toOption = d1 orElse d2 map { _ => this }
Then you can just write NextObject(maybeDataOe, maybeDataTwo).toOption at the call site.
Or maybe this:
object NextObject {
def opt(d1: Option[DataOne], d2: Option[DataTwo]) =
d1 orElse d2 map { _ => apply(d1, d2) }
}
and then just NextObject.opt(maybeDataOne, maybeDataTwo)
I need to get an entire case class, built with 3 other case classes, back in one call. However, depending on a users credentials they may or may not have access to certain parts of that case class.
Below is how I'm currently doing it.(and it works) It's making the call but clearing out the unauthorized case classes afterwards. I would ideally like to stop the call (due to low bandwidth) if they don't have the proper credentials and return None.
The persistent entity service call is expecting an Option[PersonOne] and Option[PersonTwo] so the .ask must return that or it says it's returning an object and won't compile.
override def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean]) = ServerServiceCall { _ =>
for {
g <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
h <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne) //if (credOne) **this 'if' does not work**
s <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo)
} yield {
val x: Option[PersonOne] = if (credOne == Some(true)) h else None
val y: Option[PersonTwo] = if (credTwo == Some(true)) s else None
CompletePerson(g.get, x, y)
//TODO catch if no employee is returned
//throw NotFound (s"Person not Found with $id");
}
}
Here is a version of your function that will only call ask if the appropriate credential value is Some(true):
override def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean]) = ServerServiceCall { _ =>
CompletePerson(
registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral).get,
credOne.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne)),
credTwo.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo))
)
}
The key part is this:
credX.filter(_ == true).flatMap(...)
If credX is None this will return None
If credX contains false it will return None
If credX is Some(true) then flatMap will call the ask function
If ask returns None then the expression will return None, otherwise it will return Some[PersonX]
I am a bit unclear on some of the data types, but I think this should give you some idea of how to approach this code. (For example, that bare .get look dangerous as it could throw an exception)
Edit after comments
I fixed an issue with the filter(_), it should be filter(_ == true).
It looks like ask actually returns Option[Option[T]], in which case this might be closer to what is needed.
def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean]) = ServerServiceCall { _ =>
for {
g <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
gen <- g
} yield {
val p1 = credOne.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne))).flatten
val p2 = credTwo.filter(_ == true).flatMap(_ => registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo))).flatten
CompletePerson(gen, p1, p2)
}
}
I'm not quite sure what you're asking, but are you concerned that if
registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
returns None that you'll keep making expensive .ask calls? Cos that won't happen, as we can see from this
def giveMeSome = {println("giving you Some"); Some(1)}
def giveMeNone = {println("giving you None"); None}
def giveMeSomeMore = {println("giving you Some more"); Some(2)}
val ans = for {
a <- giveMeSome
f <- giveMeNone
q <- giveMeSomeMore
} yield (a, f, q)
println(ans)
which prints
giving you Some
giving you None
None
We can also see that in this case, you'll get None back. I can't tell if you want a None or an error thrown. If you want an error, you can just wrap what's above
I didn't get your question completely, but based on my understanding you want to first check if credOne == Some(true) && credTwo == Some(true) then proceed further else return None
override def getPerson(id: UUID, credOne: Option[Boolean], credTwo: Option[Boolean])
=
for {
cone <- credOne
ctwo <- credTwo
if cone
if ctwo
} yield {
ServerServiceCall { _ =>
for {
g <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonGeneral)
h <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonOne)
s <- registry.refFor[PersonEntity](id.toString()).ask(GetPersonTwo)
if g.isDefined
} yield {
CompletePerson(g.get, h, s)
}
}
I want to replace the below match with an if statement, preferably less verbose than this. I personally find if's to be easier to parse in the mind.
val obj = referencedCollection match{
case None => $set(nextColumn -> MongoDBObject("name" -> name))
case Some( collection) =>....}
Is there an equivalent if statement or some other method that produces the equivalent result?
You can replace the pattern match by a combination of map and getOrElse:
ox match {
case None => a
case Some(x) => f(x)
}
can be replaced by
ox.map(f).getOrElse(a)
You probably ought not indulge yourself in continuing to use if in conditions like this. It's not idiomatic, rarely is faster, is more verbose, requires intermediate variables so is more error-prone, etc..
Oh, and it's a bad idea to use anything with $ signs!
Here are some other patterns that you might use in addition to match:
val obj = referenceCollection.fold( $set(nextColumn -> MongoDBObject("name" -> name) ){
collection => ...
}
val obj = (for (collection <- referenceCollection) yield ...).getOrElse{
$set(nextColumn -> MongoDBObject("name" -> name)
}
val obj = referenceCollection.map{ collection => ... }.getOrElse{
$set(nextColumn -> MongoDBObject("name" -> name)
}
You can basically think of map as the if (x.isDefined) x.get... branch of the if and the getOrElse branch as the else $set... branch. It's not exactly the same, of course, as leaving off the else gives you an expression that doesn't return a value, while leaving off the getOrElse leaves you with an unpacked Option. But the thought-flow is very similar.
Anyway, fold is the most compact. Note that both of these have a bit of runtime overhead above a match or if statement.
There is huge number of options (pun intended):
if (referencedCollection != None) { ... } else { ... }
if (referencedCollection.isDefined) { ... } else { ... } // #Kigyo variant
if (referencedCollection.isEmpty) { /* None processing */ } else { ... }
You could do it like this:
val obj = if(referencedCollection.isDefined) { (work with referencedCollection.get) } else ...
getFirstNotNullResult executes a list of functions, until one of them returns a not null value.
How to implement getNotNullFirstResult more elegantly/concise?
object A {
def main(args: Array[String]) {
println(test());
}
def test(): String = {
getFirstNotNullResult(f1 _ :: f2 _ :: f3 _ :: Nil);
}
def getFirstNotNullResult(fs: List[() => String]): String = {
fs match {
case head::tail =>
val v = head();
if (v != null) return v;
return getFirstNotNullResult(tail);
case Nil => null
}
}
// these would be some complex and slow functions; we only want to execute them if necessary; that is, if f1() returns not null, we don't want to execute f2 nor f3.
def f1(): String = { null }
def f2(): String = { "hello" }
def f3(): String = { null }
}
I like Rex's answer, but your question brings up so many things, I'd like to expand on it, to add:
Using Scala's Option/Some/None classes to clarify what should be returned when no match is found. Your example returned null, Rex's threw an exception. Using Option makes it immediately clear that we will return a match or "None".
Use type parameters so you don't have to operate just on functions that return a String.
Here's the code:
object A extends App {
def getFirstNNWithOption[T](fs: List[() => Option[T]]): Option[T] = fs
.view //allows us to evaluate your functions lazily: only evaluate as many as it takes to find a match
.flatMap(_()) //invoke the function, discarding results that return None
.headOption // take the first element from the view - returns None if empty
def f1 = { println("f1"); None }
def f2 = Some("yay!")
def f3 = { println("f2"); None }
println(getFirstNNWithOption(List(f1 _, f2 _, f3 _)))
}
Note that when this code runs, f2 never prints, demonstrating that, thanks to the .view call, we evaluate the minimum number of functions before returning a match.
Note that callers of this method now must consider the fact that a match might not be found: instead of returning T, we return Option[T]. In our case above, it would return Some("yay"). When all functions return None, the return value would be None. No more NullPointerExceptions when you mistake a null for an actual match!
def getFirstNN(fs: List[() => String]): String = fs.iterator.map(_()).find(_ ne null).get
You'll probably want the type passed into getFirstNotNullResult to be a Stream[String] instead of List[() => String] and construct it something like:
Stream.cons(f1, Stream.cons(f2, Stream.cons(f3, Stream.empty)))
Then getFirstNotNullResult changes to be:
fs.filter(_ != null).headOption
Which will also mean that it should really return Option[String] as well, as you can't guarantee that something will be non-null.
As suggested, the reason why I suggest a Stream is that it only evaluates the "tail" of the Stream on demand. So if getFirstNotNullResult finds that the first element is not null then the second parameter to the first Stream.cons call is never actually executed.