D Artem
D Artem

Reputation: 41

partialFunction in collectFirst

I have a sequence of some objects and I want to collect the first element for which another function returns Some()

For now, my code works this way:

mySeq.collectFirst{
  case elem if doSmth(elem).nonEmpty => 
    (doSmth(elem).get, elem)
}

Is there a way to:

  1. Refactor it not to call doSmth twice?

  2. change this code to log smth in case doSmth returns None

res.match {
  case Some(v) => v
  case None => log.info("Searching further...")//partial function doesn't match
}

I would say, the main question is - is it possible to create a PartialFunction that matches all cases, but for some of the cases we do produce some side effect (log message in this case) and explicitly say that it is not matched?

Edit:

I am wondering, why would the following code throw an error when I run tests (not compilation error though)

mySeq.collectFirst{
case elem => 
  val r: Option[Int] = doSmth(elem)
  r match {
    case Some(res) => res
  }
}

Upvotes: 1

Views: 404

Answers (2)

Tim
Tim

Reputation: 27356

You can use a custom pattern matcher for this. This is an object with an unapply method that can be used in a pattern match.

This code will do the same as the original, but will only call doSmth once:

object MatchSomething {
  def unapply[T](elem: T): Option[(String, T)] =
    doSmth(elem).map(r => (r, elem))
}

mySeq.collectFirst {
  case MatchSomething((smth, elem)) =>
    (smth, elem)
}

You can log the failed matches in the unapply method:

object MatchSomething {
  def unapply[T](elem: T): Option[(String, T)] =
    doSmth(elem) match {
      case Some(r) =>
        Some((r, elem))
      case None =>
        println(s"$elem did not match, searching further")
        None
    }
}

Upvotes: 0

Dima
Dima

Reputation: 40500

Yeah, there may be a way to not call doSmth twice and log the empties, but that would look pretty ugly (you'd have to subclass PartialFunction explicitly, spell out isDefined, and have a var member to keep the result).

Overall, this is just not a good use case for collectFirst. Just do something like this instead:

   mySeq
     .view
     .map(doSmth)
     .flatMap { 
       case None => 
         log("nope")
         None
       case x => x
     }.headOption

Or if you are like me, and prefer a series of short transformations chained together to one monolithic function, here's a fancy way:

   mySeq
    .view
    .map(doSmth)
    .map(_.toRight(println("searching")))
    .collectFirst { case Right(x) => x }

Upvotes: 4

Related Questions