Reputation: 41
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:
Refactor it not to call doSmth twice?
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
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
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