diff --git a/core/src/main/scala/cats/TraverseFilter.scala b/core/src/main/scala/cats/TraverseFilter.scala index 0bf3f7330a..7c18be45bb 100644 --- a/core/src/main/scala/cats/TraverseFilter.scala +++ b/core/src/main/scala/cats/TraverseFilter.scala @@ -69,6 +69,19 @@ trait TraverseFilter[F[_]] extends FunctorFilter[F] { def filterA[G[_], A](fa: F[A])(f: A => G[Boolean])(implicit G: Applicative[G]): G[F[A]] = traverseFilter(fa)(a => G.map(f(a))(if (_) Some(a) else None)) + /** + * Like [[traverseFilter]], but uses `Either` instead of `Option` and allows for an action to be run on each filtered value. + */ + def traverseEither[G[_], A, B, E]( + fa: F[A] + )(f: A => G[Either[E, B]])(g: (A, E) => G[Unit])(implicit G: Monad[G]): G[F[B]] = + traverseFilter(fa)(a => + G.flatMap(f(a)) { + case Left(e) => G.as(g(a, e), Option.empty[B]) + case Right(b) => G.pure(Some(b)) + } + ) + override def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] = traverseFilter[Id, A, B](fa)(f) } diff --git a/laws/src/main/scala/cats/laws/TraverseFilterLaws.scala b/laws/src/main/scala/cats/laws/TraverseFilterLaws.scala index 15961a1799..9e642080d8 100644 --- a/laws/src/main/scala/cats/laws/TraverseFilterLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseFilterLaws.scala @@ -27,6 +27,11 @@ trait TraverseFilterLaws[F[_]] extends FunctorFilterLaws[F] { def filterAConsistentWithTraverseFilter[G[_]: Applicative, A](fa: F[A], f: A => G[Boolean]): IsEq[G[F[A]]] = fa.filterA(f) <-> fa.traverseFilter(a => f(a).map(if (_) Some(a) else None)) + + def traverseEitherConsistentWithTraverseFilter[G[_], E, A, B](fa: F[A], f: A => G[Option[B]], e: E)( + implicit G: Monad[G] + ): IsEq[G[F[B]]] = + fa.traverseEither(a => f(a).map(_.toRight(e)))((_, _) => Applicative[G].unit) <-> fa.traverseFilter(f) } object TraverseFilterLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala index 342bf10b79..a79eb3bcce 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala @@ -33,7 +33,10 @@ trait TraverseFilterTests[F[_]] extends FunctorFilterTests[F] { "traverseFilter identity" -> forAll(laws.traverseFilterIdentity[Option, A] _), "traverseFilter nested composition" -> forAll(laws.traverseFilterComposition[A, B, C, Option, Option] _), "traverseFilter consistent with traverse" -> forAll(laws.traverseFilterConsistentWithTraverse[Option, A] _), - "filterA consistent with traverseFilter" -> forAll(laws.filterAConsistentWithTraverseFilter[Option, A] _) + "filterA consistent with traverseFilter" -> forAll(laws.filterAConsistentWithTraverseFilter[Option, A] _), + "traverseEither consistent with traverseFilter" -> forAll( + laws.traverseEitherConsistentWithTraverseFilter[Option, F[A], A, B] _ + ) ) }