Filtering a set of Either[x, y], with logging

Viewed 92

Given a Set[Either[BadObject, GoodObject]], I'd like to convert it into a Set[GoodObject], while logging all the BadObjects.

The problem I am having is that when I try to add logging in a collect call, like:

someMethodThatReurnsTheSet.collect {
  case Right(value) => value
  case Left(res) => logger.warn(s"Bad object : $res")
}

This changes the return value and I am getting a Set[None], which is not what I want.

4 Answers

Try partition in combination with chaining

import util.chaining._

someMethodThatReurnsTheSet
  .partition(_.isRight)
  .tap { case (_, lefts) => logger.warn(s"Bad objects $lefts") }
  .pipe { case (rights, _) => rights }

Simple way:

someMethodThatReurnsTheSet().collect {
  case Right(value) => Some(value)
  case Left(res) => 
    logger.warn(s"Bad object : $res")
    None
}.flatten

many alternatives are possible, for instance or see the other answers :)

val (lefts, rights) = someMethodThatReurnsTheSet().partition(_.isLeft)
lefts.foreach(err => logger.warn(s"Bad object : ${err.left.get}"))
val set = rights.map(_.right.get)

Another way using foldLeft:

someMethodThatReturnsTheSet.foldLeft(Set.empty[GoodObject]) {
   case (acc, goodOrBad) => goodOrBad match {
     case Right(good) => acc + good
     case Left(bad) => 
       logger.warn("Bad object: $bad")
       acc
   }
}

If you don't mind allocating:

someMethodThatReturnsTheSet.flatMap {
  case Right(good) => Set(good)
  case Left(bad) => 
    logger.warn("Bad object: $bad")
    Set.empty
}

If you want to take an action only in the Left-case, you can use .left.foreach:

setOfEithers.flatMap { e =>
    e.left.foreach { bad => logger.warn(s"Bad object: $bad") }
    e.toOption
}
Related