Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repeating function call until we'll get non-empty Option result in Scala

Tags:

loops

scala

A very newbie question in Scala - how do I do "repeat function until something is returned meets my criteria" in Scala?

Given that I have a function that I'd like to call until it returns the result, for example, defined like that:

def tryToGetResult: Option[MysteriousResult]

I've come up with this solution, but I really feel that it is ugly:

var res: Option[MysteriousResult] = None
do {
  res = tryToGetResult
} while (res.isEmpty)
doSomethingWith(res.get)

or, equivalently ugly:

var res: Option[MysteriousResult] = None
while (res.isEmpty) {
  res = tryToGetResult
}
doSomethingWith(res.get)

I really feel like there is a solution without var and without so much hassle around manual checking whether Option is empty or not.

For comparison, Java alternative that I see seems to be much cleaner here:

MysteriousResult tryToGetResult(); // returns null if no result yet

MysteriousResult res;
while ((res = tryToGetResult()) == null);
doSomethingWith(res);

To add insult to injury, if we don't need to doSomethingWith(res) and we just need to return it from this function, Scala vs Java looks like that:

Scala

def getResult: MysteriousResult = {
  var res: Option[MysteriousResult] = None
  do {
    res = tryToGetResult
  } while (res.isEmpty)
  res.get
}

Java

MysteriousResult getResult() {
    while (true) {
        MysteriousResult res = tryToGetResult();
        if (res != null)  return res;
    }
}
like image 837
GreyCat Avatar asked Nov 22 '12 00:11

GreyCat


2 Answers

You can use Stream's continually method to do precisely this:

val res = Stream.continually(tryToGetResult).flatMap(_.toStream).head

Or (possibly more clearly):

val res = Stream.continually(tryToGetResult).dropWhile(!_.isDefined).head

One advantage of this approach over explicit recursion (besides the concision) is that it's much easier to tinker with. Say for example that we decided that we only wanted to try to get the result a thousand times. If a value turns up before then, we want it wrapped in a Some, and if not we want a None. We just add a few characters to our code above:

Stream.continually(tryToGetResult).take(1000).flatMap(_.toStream).headOption

And we have what we want. (Note that the Stream is lazy, so even though the take(1000) is there, if a value turns up after three calls to tryToGetResult, it will only be called three times.)

like image 152
Travis Brown Avatar answered Sep 17 '22 22:09

Travis Brown


Performing side effects like this make me die a little inside, but how about this?

scala> import scala.annotation.tailrec
import scala.annotation.tailrec

scala> @tailrec
     | def lookupUntilDefined[A](f: => Option[A]): A = f match {
     |   case Some(a) => a
     |   case None => lookupUntilDefined(f)
     | }
lookupUntilDefined: [A](f: => Option[A])A

Then call it like this

scala> def tryToGetResult(): Option[Int] = Some(10)
tryToGetResult: ()Option[Int]

scala> lookupUntilDefined(tryToGetResult())
res0: Int = 10

You may want to give lookupUntilDefined an additional parameter so it can stop eventually in case f is never defined.

like image 30
drstevens Avatar answered Sep 20 '22 22:09

drstevens