Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala collectFirst with function returning Option[U]

I've had this situation occur a number of times in the library I'm writing, and I'm not particularly satisfied with the solutions I've come up with so far.

Let's say that I have an expensive function f that takes an item of type T and returns a value of type Option[U]. Now, suppose I have a collection of type T and I want to retrieve the first non-None value returned by f when performed across the elements of T without evaluating f for all elements of T if the value has already been found.

The only way I've come up with to do this is to wrap F into an Extractor Object, and use it with scala's collectFirst method.

For example:

object FMatch { def unapply(t : T) = f(t) }

collection.collectFirst{ case FMatch(result) => result }

This seems a little inelegant, and I'm not certain whether f is evaluated only once or twice for each result (I haven't tested this to find out yet). It seems like it would be useful to have a version of collectFirst that takes a parameter of type T => Option[U] instead of a PartialFunction1[T].

Is there a more elegant way to do this that I'm missing?

like image 519
Nimrand Avatar asked Sep 05 '11 04:09

Nimrand


3 Answers

Use a view over the collection, to make it lazy and defer invocation of that function until the last possible moment (e.g. it won't be called at all for elements beyond the first match):

xs.view map {f(_)} collectFirst {case Some(x) => x}

or

xs.view map {f(_)} find {_.isDefined}

or in the point-free style, as per Alexey's response:

xs.view map {f} find {_.isDefined}

That should hopefully give you a couple of alternative ways to think about the problem more generally :)

like image 177
Kevin Wright Avatar answered Nov 02 '22 02:11

Kevin Wright


Use this:

collection.toIterator.map(f).find(_.isDefined)
like image 10
Alexey Romanov Avatar answered Nov 02 '22 00:11

Alexey Romanov


@annotation.tailrec 
def first[A, B](as: Traversable[A], f: A => Option[B]): Option[B] = 
  if (as.isEmpty) None 
  else f(as.head) match {
    case s @ Some(_) => s
    case _ => first(as.tail, f)
  }
like image 2
Jed Wesley-Smith Avatar answered Nov 02 '22 02:11

Jed Wesley-Smith