4

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.

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
TheDude
  • 361
  • 4
  • 13

4 Answers4

6

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 }
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
5

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)
bottaio
  • 4,963
  • 3
  • 19
  • 43
  • You can replace `collect`/`flatten` by a `flatMap`. The `collect` here is a `map` really since it handles all cases. And then `map`/`flatten` can simply be a `flatMap`. `Set(Right(1), Left("s"), Left(s"x"), Right(4)).flatMap { case Right(x) => Some(x); case Left(x) => println(x); None }` – Xavier Guihot May 29 '20 at 18:28
5

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
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
2

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
}
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93