From 2b329d9f5da2a4a5a0fd469bd5f651f083de43ed Mon Sep 17 00:00:00 2001 From: David Gregory Date: Thu, 28 Jul 2022 14:01:12 +0100 Subject: [PATCH] Add collectFirst to the NonEmptyCollection interface --- core/src/main/scala/cats/data/NonEmptyCollection.scala | 1 + core/src/main/scala/cats/data/NonEmptyList.scala | 10 ++++++++++ core/src/main/scala/cats/data/NonEmptySeq.scala | 2 ++ core/src/main/scala/cats/data/NonEmptyVector.scala | 2 ++ .../scala/cats/tests/NonEmptyCollectionSuite.scala | 6 ++++++ 5 files changed, 21 insertions(+) diff --git a/core/src/main/scala/cats/data/NonEmptyCollection.scala b/core/src/main/scala/cats/data/NonEmptyCollection.scala index d48b435b85..72c158a84e 100644 --- a/core/src/main/scala/cats/data/NonEmptyCollection.scala +++ b/core/src/main/scala/cats/data/NonEmptyCollection.scala @@ -40,6 +40,7 @@ private[cats] trait NonEmptyCollection[+A, U[+_], NE[+_]] extends Any { def filter(f: A => Boolean): U[A] def filterNot(p: A => Boolean): U[A] def collect[B](pf: PartialFunction[A, B]): U[B] + def collectFirst[B](pf: PartialFunction[A, B]): Option[B] def find(p: A => Boolean): Option[A] def exists(p: A => Boolean): Boolean def forall(p: A => Boolean): Boolean diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index aff19f3a8c..613f3e7479 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -217,6 +217,16 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) extends NonEmptyCollec tail.collect(pf) } + /** + * Find the first element matching the partial function, if one exists + */ + def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = + if (pf.isDefinedAt(head)) { + Some(pf.apply(head)) + } else { + tail.collectFirst(pf) + } + /** * Find the first element matching the predicate, if one exists */ diff --git a/core/src/main/scala/cats/data/NonEmptySeq.scala b/core/src/main/scala/cats/data/NonEmptySeq.scala index 38b88d8c17..8b08da2a4a 100644 --- a/core/src/main/scala/cats/data/NonEmptySeq.scala +++ b/core/src/main/scala/cats/data/NonEmptySeq.scala @@ -97,6 +97,8 @@ final class NonEmptySeq[+A] private (val toSeq: Seq[A]) extends AnyVal with NonE def collect[B](pf: PartialFunction[A, B]): Seq[B] = toSeq.collect(pf) + def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = toSeq.collectFirst(pf) + /** * Alias for [[concat]] */ diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index ec8849e172..2a1ca6c363 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -100,6 +100,8 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) def collect[B](pf: PartialFunction[A, B]): Vector[B] = toVector.collect(pf) + def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = toVector.collectFirst(pf) + /** * Alias for [[concat]] */ diff --git a/tests/shared/src/test/scala/cats/tests/NonEmptyCollectionSuite.scala b/tests/shared/src/test/scala/cats/tests/NonEmptyCollectionSuite.scala index aab0d423c5..b7a3bdb3f4 100644 --- a/tests/shared/src/test/scala/cats/tests/NonEmptyCollectionSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/NonEmptyCollectionSuite.scala @@ -103,6 +103,12 @@ abstract class NonEmptyCollectionSuite[U[+_], NE[+_], NEC[x] <: NonEmptyCollecti } } + test("collectFirst is consistent with iterator.toList.collectFirst") { + forAll { (is: NE[Int], pf: PartialFunction[Int, String]) => + assert(is.collectFirst(pf) === is.iterator.toList.collectFirst(pf)) + } + } + test("find is consistent with iterator.toList.find") { forAll { (is: NE[Int], pred: Int => Boolean) => assert(is.find(pred) === (is.iterator.toList.find(pred)))