Michele Piccolini
Michele Piccolini

Reputation: 2953

How to return early without return statement?

How to write an early-return piece of code in scala with no returns/breaks?

For example

for i in 0..10000000
  if expensive_operation(i)
     return i
return -1

Upvotes: 6

Views: 1818

Answers (5)

John Godoi
John Godoi

Reputation: 21

There a lot of better answer here but I think a 'while' could work just fine in that situation.

So, this code

for i in 0..10000000
  if expensive_operation(i)
     return i
return -1

could be rewritten as

var i = 0
var result = false
while(!result && i<(10000000-1)) {
    i = i+1
    result = expensive_operation(i)
}

After the 'while' the variable 'result' will tell if it succeed or not.

Upvotes: 1

Since you asked for intuition to solve this problem generically. Let me start from the basis.

Scala is (between other things) a functional programming language, as such there is a very important concept for us. And it is that we write programs by composing expressions rather than statements.

Thus, the concept of return value for us means the evaluation of an expression.
(Note this is related to the concept of referential transparency).

val a = expr   // a is bounded to the evaluation of expr,
val b = (a, a) // and they are interchangeable, thus b === (expr, expr)

How this relates to your question. In the sense that we really do not have control structures but complex expressions. For example an if

val a = if (expr) exprA else exprB // if itself is an expression, that returns other expressions.

Thus instead of doing something like this:

def foo(a: Int): Int =
  if (a != 0) {
    val b = a * a
    return b
  }
  return -1

We would do something like:

def foo(a: Int): Int =
  if (a != 0)
    a * a
  else
   -1

Because we can bound all the if expression itself as the body of foo.


Now, returning to your specific question. How can we early return a cycle?
The answer is, you can't, at least not without mutations. But, you can use a higher concept, instead of iterating, you can traverse something. And you can do that using recursion.

Thus, let's implement ourselves the find proposed by @Thilo, as a tail-recursive function.
(It is very important that the function is recursive by tail, so the compiler optimizes it as something equivalent to a while loop, that way we will not blow up the stack).

def find(start: Int, end: Int, step: Int = 1)(predicate: Int => Boolean): Option[Int] = {
  @annotation.tailrec
  def loop(current: Int): Option[Int] =
    if (current == end)
      None // Base case.
    else if (predicate(current))
      Some(current) // Early return.
    else
      loop(current + step) // Recursive step.
  loop(current = start)
}

find(0, 10000)(_ == 10)
// res: Option[Int] = Some(10)

Or we may generalize this a little bit more, let's implement find for Lists of any kind of elements.

def find[T](list: List[T])(predicate: T => Boolean): Option[T] = {
  @annotation.tailrec
  def loop(remaining: List[T]): Option[T] =
    remaining match {
      case Nil                      => None
      case t :: _ if (predicate(t)) => Some(t)
      case _ :: tail                => loop(remaining = tail)
    }
  loop(remaining = list)
}

Upvotes: 4

pme
pme

Reputation: 14803

You can use dropWhile

Here an example:

Seq(2,6,8,3,5).dropWhile(_ % 2 == 0).headOption.getOrElse(default = -1) // -> 8

And here you find more scala-takewhile-example

With your example

(0 to 10000000).dropWhile(!expensive_operation(_)).headOption.getOrElse(default = -1)`

Upvotes: 5

muhuk
muhuk

Reputation: 16085

This is not necessarily the best solution from a practical perspective but I still wanted to add it for educational purposes:

import scala.annotation.tailrec

def expensiveOperation(i: Int): Boolean = ???

@tailrec
def findFirstBy[T](f: (T) => Boolean)(xs: Seq[T]): Option[T] = {
   xs match {
       case Seq() => None
       case Seq(head, _*) if f(head) => Some(head)
       case Seq(_, tail@_*) => findFirstBy(f)(tail)
   }
}

val result = findFirstBy(expensiveOperation)(Range(0, 10000000)).getOrElse(-1)

Please prefer collections methods (dropWhile, find, ...) in your production code.

Upvotes: 2

Thilo
Thilo

Reputation: 262504

How about

 input.find(expensiveOperation).getOrElse(-1)

Upvotes: 10

Related Questions