21

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 :)

Community
  • 1
  • 1
Julio Faerman
  • 13,228
  • 9
  • 57
  • 75
  • 1
    The code example is precisely returning a single element from a `for` loop. What do you want to do differently? – Alexey Romanov Nov 12 '12 at 12:33
  • 1
    Change the return type to `Option[T]` and the code you've written will work. Not sure I understand your question. – missingfaktor Nov 12 '12 at 12:33
  • 1
    I'd advise you to use the version with `find` and also avoid the use of `null` and `return` to make your code more idiomatic Scala. – Jesper Nov 12 '12 at 12:35
  • Totally agree with Jesper. Your question sounds like: yeah, I know how to solve this problem in a concise and obvious way, but could anyone teach me to make this unidiomatic and tangled? There's no better solution for this problem than the one based on `find. – Nikita Volkov Nov 12 '12 at 17:13

9 Answers9

26

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].

Philippe
  • 9,582
  • 4
  • 39
  • 59
  • That is close, is it possible to write this so that the type of the value returned is actually a single Int, instead of Vector[Int] or List[Int]? – Julio Faerman Nov 15 '12 at 12:13
  • The result type is an `Iterator` in my example. Call `.next` on it to get an `Int` (will throw an exception if there were no solutions). – Philippe Nov 15 '12 at 12:20
  • I see... is there a method that returns None instead of exception? (and Some[Int] if found). If not, it would be doable in a "OptionIterator" trait of my own and a implicit conversion, right? – Julio Faerman Nov 15 '12 at 16:48
  • There used to be a method 'headOpt' but it got deprecated ages ago. You can indeed redefine it yourself with an implicit conversion. – Philippe Nov 15 '12 at 21:10
18

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
dapek
  • 291
  • 1
  • 4
3

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.

Malte Schwerhoff
  • 12,684
  • 4
  • 41
  • 71
2

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.

Community
  • 1
  • 1
Udayakumar Rayala
  • 2,264
  • 1
  • 20
  • 17
1

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
  }
Faruk Sahin
  • 8,406
  • 5
  • 28
  • 34
1

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
}

}

Andreas Neumann
  • 10,734
  • 1
  • 32
  • 52
1

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

Vincenzo Maggio
  • 3,787
  • 26
  • 42
  • 1
    This (using 'if' in for comprehensions) is actually a good idea, and should be the first thing to consider. However, it's not always possible - there are cases where multiple matches would be created, and the second level trick (presented in @dapek's answer) is to do lazy evaluation on them. – akauppi Jul 08 '16 at 10:29
0

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
    }
}
J Camphor
  • 303
  • 1
  • 7
0
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.

Jürgen Strobel
  • 2,200
  • 18
  • 30