How can I do 'if..else' inside a for-comprehension? - scala

I am asking a very basic question which confused me recently.
I want to write a Scala For expression to do something like the following:
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
The problem is that, in the multiple generators For expression, I don't know where can I put each for expression body.
for {i <- expr1
if(i.method) // where can I write the else logic ?
j <- i
if (j.method)
} doSomething()
How can I rewrite the code in Scala Style?

The first code you wrote is perfectly valid, so there's no need to rewrite it. Elsewhere you said you wanted to know how to do it Scala-style. There isn't really a "Scala-style", but I'll assume a more functional style and tack that.
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
The first concern is that this returns no value. All it does is side effects, which are to be avoided as well. So the first change would be like this:
val result = for (i <- expr1) yield {
if (i.method) {
for (j <- i) yield {
if (j.method) {
returnSomething()
// etc
Now, there's a big difference between
for (i <- expr1; j <- i) yield ...
and
for (i <- expr1) yield for (j <- i) yield ...
They return different things, and there are times you want the later, not the former. I'll assume you want the former, though. Now, before we proceed, let's fix the code. It is ugly, difficult to follow and uninformative. Let's refactor it by extracting methods.
def resultOrB(j) = if (j.method) returnSomething else returnSomethingElseB
def nonCResults(i) = for (j <- i) yield resultOrB(j)
def resultOrC(i) = if (i.method) nonCResults(i) else returnSomethingC
val result = for (i <- expr1) yield resultOrC(i)
It is already much cleaner, but it isn't returning quite what we expect. Let's look at the difference:
trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"
def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else Unrecognized
val result = for (i <- expr1) yield classifyElements(i)
The type of result there is Array[AnyRef], while using multiple generators would yield Array[Element]. The easy part of the fix is this:
val result = for {
i <- expr1
element <- classifyElements(i)
} yield element
But that alone won't work, because classifyElements itself returns AnyRef, and we want it returning a collection. Now, validElements return a collection, so that is not a problem. We only need to fix the else part. Since validElements is returning an IndexedSeq, let's return that on the else part as well. The final result is:
trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"
def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else IndexedSeq(Unrecognized)
val result = for {
i <- expr1
element <- classifyElements(i)
} yield element
That does exactly the same combination of loops and conditions as you presented, but it is much more readable and easy to change.
About Yield
I think it is important to note one thing about the problem presented. Let's simplify it:
for (i <- expr1) {
for (j <- i) {
doSomething
}
}
Now, that is implemented with foreach (see here, or other similar questions and answer). That means the code above does exactly the same thing as this code:
for {
i <- expr1
j <- i
} doSomething
Exactly the same thing. That is not true at all when one is using yield. The following expressions do not yield the same result:
for (i <- expr1) yield for (j <- i) yield j
for (i <- expr1; j <- i) yield j
The first snippet will be implemented through two map calls, while the second snippet will use one flatMap and one map.
So, it is only in the context of yield that it even makes any sense to worry about nesting for loops or using multiple generators. And, in fact, generators stands for the fact that something is being generated, which is only true of true for-comprehensions (the ones yielding something).

The part
for (j <- i) {
if (j.method) {
doSomething(j)
} else {
doSomethingElse(j)
}
}
can be rewritten as
for(j <- i; e = Either.cond(j.method, j, j)) {
e.fold(doSomething _, doSomethingElse _)
}
(of course you can use a yield instead if your do.. methods return something)
Here it is not so terrible useful, but if you have a deeper nested structure, it could...

import scalaz._; import Scalaz._
val lhs = (_ : List[X]) collect { case j if j.methodJ => doSomething(j) }
val rhs = (_ : List[X]) map doSomethingElse
lhs <-: (expr1 partition methodI) :-> rhs

You can not. The for(expr; if) construct just filter the element that must be handled in the loop.

If the order isn't important for the calls to doSomething() and doSomethingElse() then you can rearrange the code like this.
val (tmp, no1) = expr1.partition(_.method)
val (yes, no2) = tmp.partition(_.method)
yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())
To answer your original question, I think that for comprehensions can be quite nice for specific use cases, and your example doesn't fit nicely.

The conditions specified in a Scala for operation act to filter the elements from the generators. Elements not satisfying the conditions are discarded and are not presented to the yield / code block.
What this means is that if you want to perform alternate operations based on a conditional expression, the test needs to be deferred to the yield / code block.
Also be aware that the for operation is relatively expensive to compute (currently) so perhaps a simpler iterative approach might be more appropriate, perhaps something like:
expr1 foreach {i =>
if (i.method) {
i foreach {j =>
if (j.method)
doSomething()
else
doSomethingElseA()
}
}
else
doSomethingElseB()
}
Update:
If you must use a for comprehension and you can live with some restrictions, this might work:
for (i <- expr1; j <- i) {
if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}

Related

Is there a way to make a for loop with two parallel iterators in Scala?

I am trying to do a for loop on a HashMap in Scala, but I also need a count of my iteration, like an index of sorts. Here is what I want the functionality to be:
val map = HashMap()
val i = 0;
for ((k, v) <- map) {
// do something
i += 1
}
But I want it to look something like this, where i is updated inside the for loop syntax. But, this seems to act as a nested loop rather than a parallel iterator.
for ((k, v) <- map; i <- 0 until map.size) {
// do something
}
The zipWithIndex method is designed specifically for this purpose:
for {((k, v), i) <- map.zipWithIndex} yield {???}
or
map.zipWithIndex.collect { case ((k, v), i) => ??? }
Personally I would use the second version in this case and only use the for syntax when iterating over multiple collections.

Yield after if within nested for loop

I'm having trouble with a nested for loop and using yield properly.
The problem is if I have two lists of the form List[(Int,Int)]
val ls = (1,5)::(3,2)::(5,3)::Nil
val ls_two = (1,9)::(5,9)::(6,7)::Nil
and now I want to create a third list only combining the key and both second int of all the lists so the result would be
val result = (1,5,9)::(5,3,9)::Nil
I've tried a few variations of something like this which none seem to work
val result = for(i <- ls) {
for(j <- ls_two) {
if(i._1 == j._1) yield (i._1,i._2,j._2)
}
}
I've tried placing the yield at the end of the for loop, it seems to work if i replace yield with println but i'm not sure how to do it with yield.
Also if you have a more functional approach to how to solve this it would be greatly appreciated, thanks!
The recommended approach here is not to "nest" the "loops" at all - but to create a single for-comprehension which uses a "guard":
val result = for {
i <- ls
j <- ls_two if i._1 == j._1
} yield (i._1,i._2,j._2)

How to improve the code of a method which uses 'Free monad'?

I'm trying some code which inspects this slides about Free Monad in Scala, and made a small project with some slightly changed code.
The project is here: https://github.com/freewind/free-the-monads
Everything seems good at first, the code is clean and beautiful:
def insertAndGet() = for {
_ <- Script.insert("name", "Freewind")
value <- Script.get("name")
} yield value
def insertAndDelete() = for {
_ <- Script.insert("name", "Freewind")
_ <- Script.delete("name")
value <- Script.get("name")
} yield value
def insertAndUpdateAndDelete() = for {
_ <- Script.insert("name", "Freewind1")
oriValue <- Script.update("name", "Freewind2")
_ <- Script.delete("name")
finalValue <- Script.get("name")
} yield (oriValue, finalValue)
But when my logic is complex, e.g. there is some Script[Option[_]], and I need to check the option value to decide to do something, I can't use the for-comprehension any more, the code is like:
private def isLongName(name: String): Script[Boolean] = for {
size <- Script.getLongNameConfig
} yield size.exists(name.length > _)
def upcaseLongName(key: String): Script[Option[String]] = {
Script.get(key) flatMap {
case Some(n) => for {
isLong <- isLongName(n)
} yield isLong match {
case true => Some(n.toUpperCase)
case false => Some(n)
}
case _ => Script.pure(None)
}
}
I found the Free Monad approach is really interesting and cool, but I'm not familiar with scalaz, and just beginning to learn Monad things, not sure how to improve it.
Is there any way to make it better?
PS: You can just clone the project https://github.com/freewind/free-the-monads and try it yourself
This is a good use case for the OptionT monad transformer:
import scalaz.OptionT, scalaz.syntax.monad._
def upcaseLongName(key: String): OptionT[Script, String] = for {
n <- OptionT.optionT(Script.get(key))
isLong <- isLongName(n).liftM[OptionT]
} yield if (isLong) n.toUpperCase else n
Here OptionT.optionT converts a Script[Option[String]] into an OptionT[Script, String], and .liftM[OptionT] raises a Script[Boolean] into the same monad.
Now instead of this:
println(upcaseLongName("name1").runWith(interpreter))
You'd write this:
println(upcaseLongName("name1").run.runWith(interpreter))
You could also have upcaseLongName return an Script[Option[String]] directly by calling run there, but if there's any chance you'll need to be composing it with other option-y script-y things it's probably best to have it return OptionT[Script, String].

Scala async calculation exercise

I have outlined the problem below. Method calculate represents a calculation that is to run subCalc1 and subCalc2 asynchroniously, and mainCalc that will be passed results from the last two 'sub' calculations. Significantly, before starting these calculations a preliminary calculation isCalcNecessary is to return a Boolean. If true, the calculation will continue and eventually return Future[Some[Result]]. If preliminary calculation returns false, it should return Future[None], as to illustrate that calculation was not necessary. This small algorithm should be maximally asynchronious.
def isCalcNecessary:Future[Boolean] = ...
def subCalc1(param:Param):Future[SubResult1] = ...
def subCalc2(param:Param):Future[SubResult2] = ...
def mainCalc(subResult1:SubResult1, subResult2:SubResult2):Future[Result] = .
def calcute(param:Param):Future[Option[Result]] = for {
necessary <- isCalcNecessary(param)
if necessary // this illustration fails at runtime if 'necessary' is false
subResult1 <- subCalc1(param)
subResult2 <- subCalc2(param)
result <- mainCalc(subResult1, subResult2)
} yield Some(result)
The above illustration fails at runtime with (NoSuchElementException: Future.filter predicate is not satisfied (Future.scala:312)), if the if necessary conditional is not satisfied.
How would you write this algorithm?
isCalcNecessary(param).flatMap { necessary =>
if (necessary)
for {
subResult1 <- subCalc1(param)
subResult2 <- subCalc2(param)
result <- mainCalc(subResult1, subResult2)
} yield Some(result)
else
future(None)
}
Another option would be to nest for-comprehensions
def calculate(param:Param):Future[Option[Result]] = for {
necessary <- isCalcNecessary(param)
endResult <- if (necessary) for {
subResult1 <- subCalc1(param)
subResult2 <- subCalc2(param)
result <- mainCalc(subResult1, subResult2)
} yield Some(result)
else future(None)
} yield endResult

How to yield a single element from for loop in scala?

Much like this question:
Functional code for looping with early exit
Say the code is
def findFirst[T](objects: List[T]):T = {
for (obj <- objects) {
if (expensiveFunc(obj) != null) return /*???*/ Some(obj)
}
None
}
How to yield a single element from a for loop like this in scala?
I do not want to use find, as proposed in the original question, i am curious about if and how it could be implemented using the for loop.
* UPDATE *
First, thanks for all the comments, but i guess i was not clear in the question. I am shooting for something like this:
val seven = for {
x <- 1 to 10
if x == 7
} return x
And that does not compile. The two errors are:
- return outside method definition
- method main has return statement; needs result type
I know find() would be better in this case, i am just learning and exploring the language. And in a more complex case with several iterators, i think finding with for can actually be usefull.
Thanks commenters, i'll start a bounty to make up for the bad posing of the question :)
If you want to use a for loop, which uses a nicer syntax than chained invocations of .find, .filter, etc., there is a neat trick. Instead of iterating over strict collections like list, iterate over lazy ones like iterators or streams. If you're starting with a strict collection, make it lazy with, e.g. .toIterator.
Let's see an example.
First let's define a "noisy" int, that will show us when it is invoked
def noisyInt(i : Int) = () => { println("Getting %d!".format(i)); i }
Now let's fill a list with some of these:
val l = List(1, 2, 3, 4).map(noisyInt)
We want to look for the first element which is even.
val r1 = for(e <- l; val v = e() ; if v % 2 == 0) yield v
The above line results in:
Getting 1!
Getting 2!
Getting 3!
Getting 4!
r1: List[Int] = List(2, 4)
...meaning that all elements were accessed. That makes sense, given that the resulting list contains all even numbers. Let's iterate over an iterator this time:
val r2 = (for(e <- l.toIterator; val v = e() ; if v % 2 == 0) yield v)
This results in:
Getting 1!
Getting 2!
r2: Iterator[Int] = non-empty iterator
Notice that the loop was executed only up to the point were it could figure out whether the result was an empty or non-empty iterator.
To get the first result, you can now simply call r2.next.
If you want a result of an Option type, use:
if(r2.hasNext) Some(r2.next) else None
Edit Your second example in this encoding is just:
val seven = (for {
x <- (1 to 10).toIterator
if x == 7
} yield x).next
...of course, you should be sure that there is always at least a solution if you're going to use .next. Alternatively, use headOption, defined for all Traversables, to get an Option[Int].
You can turn your list into a stream, so that any filters that the for-loop contains are only evaluated on-demand. However, yielding from the stream will always return a stream, and what you want is I suppose an option, so, as a final step you can check whether the resulting stream has at least one element, and return its head as a option. The headOption function does exactly that.
def findFirst[T](objects: List[T], expensiveFunc: T => Boolean): Option[T] =
(for (obj <- objects.toStream if expensiveFunc(obj)) yield obj).headOption
Why not do exactly what you sketched above, that is, return from the loop early? If you are interested in what Scala actually does under the hood, run your code with -print. Scala desugares the loop into a foreach and then uses an exception to leave the foreach prematurely.
So what you are trying to do is to break out a loop after your condition is satisfied. Answer here might be what you are looking for. How do I break out of a loop in Scala?.
Overall, for comprehension in Scala is translated into map, flatmap and filter operations. So it will not be possible to break out of these functions unless you throw an exception.
If you are wondering, this is how find is implemented in LineerSeqOptimized.scala; which List inherits
override /*IterableLike*/
def find(p: A => Boolean): Option[A] = {
var these = this
while (!these.isEmpty) {
if (p(these.head)) return Some(these.head)
these = these.tail
}
None
}
This is a horrible hack. But it would get you the result you wished for.
Idiomatically you'd use a Stream or View and just compute the parts you need.
def findFirst[T](objects: List[T]): T = {
def expensiveFunc(o : T) = // unclear what should be returned here
case class MissusedException(val data: T) extends Exception
try {
(for (obj <- objects) {
if (expensiveFunc(obj) != null) throw new MissusedException(obj)
})
objects.head // T must be returned from loop, dummy
} catch {
case MissusedException(obj) => obj
}
}
Why not something like
object Main {
def main(args: Array[String]): Unit = {
val seven = (for (
x <- 1 to 10
if x == 7
) yield x).headOption
}
}
Variable seven will be an Option holding Some(value) if value satisfies condition
I hope to help you.
I think ... no 'return' impl.
object TakeWhileLoop extends App {
println("first non-null: " + func(Seq(null, null, "x", "y", "z")))
def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else
seq(seq.takeWhile(_ == null).size)
}
object OptionLoop extends App {
println("first non-null: " + func(Seq(null, null, "x", "y", "z")))
def func[T](seq: Seq[T], index: Int = 0): T = if (seq.isEmpty) null.asInstanceOf[T] else
Option(seq(index)) getOrElse func(seq, index + 1)
}
object WhileLoop extends App {
println("first non-null: " + func(Seq(null, null, "x", "y", "z")))
def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else {
var i = 0
def obj = seq(i)
while (obj == null)
i += 1
obj
}
}
objects iterator filter { obj => (expensiveFunc(obj) != null } next
The trick is to get some lazy evaluated view on the colelction, either an iterator or a Stream, or objects.view. The filter will only execute as far as needed.