From 362080c6dcf8d37f863bf5bee8d4fd976cf63a46 Mon Sep 17 00:00:00 2001 From: Eugene Platonov Date: Wed, 3 Apr 2024 22:17:54 -0400 Subject: [PATCH] Add `alter` to Functor --- core/src/main/scala/cats/Functor.scala | 13 +++++++++++++ .../src/test/scala/cats/tests/FunctorSuite.scala | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/core/src/main/scala/cats/Functor.scala b/core/src/main/scala/cats/Functor.scala index 5109a21b53c..024bc85a526 100644 --- a/core/src/main/scala/cats/Functor.scala +++ b/core/src/main/scala/cats/Functor.scala @@ -177,6 +177,18 @@ trait Functor[F[_]] extends Invariant[F] { self => */ def tupleRight[A, B](fa: F[A], b: B): F[(A, B)] = map(fa)(a => (a, b)) + /** + * Modifies the `A` value in `F[A]` with the supplied function, if the function is defined for the value. + * Example: + * {{{ + * scala> import cats.Functor + * scala> import cats.implicits.catsStdInstancesForList + * scala> Functor[List].alter(List(1, 2, 3)) { case 2 => 42 } + * res0: List[Int] = List(1, 42, 3) + * }}} + */ + def alter[A](fa: F[A])(pf: PartialFunction[A,A]): F[A] = map(fa)(a => if (pf.isDefinedAt(a)) pf(a) else a) + /** * Un-zips an `F[(A, B)]` consisting of element pairs or Tuple2 into two separate F's tupled. * @@ -258,6 +270,7 @@ object Functor { def as[B](b: B): F[B] = typeClassInstance.as[A, B](self, b) def tupleLeft[B](b: B): F[(B, A)] = typeClassInstance.tupleLeft[A, B](self, b) def tupleRight[B](b: B): F[(A, B)] = typeClassInstance.tupleRight[A, B](self, b) + def alter(pf: PartialFunction[A, A]): F[A] = typeClassInstance.alter(self)(pf) } trait AllOps[F[_], A] extends Ops[F, A] with Invariant.AllOps[F, A] { type TypeClassType <: Functor[F] diff --git a/tests/shared/src/test/scala/cats/tests/FunctorSuite.scala b/tests/shared/src/test/scala/cats/tests/FunctorSuite.scala index 1c22cc08701..37d41816a50 100644 --- a/tests/shared/src/test/scala/cats/tests/FunctorSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/FunctorSuite.scala @@ -45,6 +45,14 @@ class FunctorSuite extends CatsSuite { } } + test("alter maps matching elements preserving structure") { + forAll { (l: List[Int], o: Option[Int], m: Map[String, Int]) => + assert(l.alter { case i if i % 2 == 0 => i + 1 } === l.map(i => if (i % 2 == 0) i + 1 else i)) + assert(o.alter { case i if i > 0 => i + 1 } === (if (o.nonEmpty) if (o.get > 0) Some(o.get + 1) else o else None)) + assert(m.alter { case v if v % 2 == 0 => v + 1 } === m.map { case (k, v) => k -> (if (v % 2 == 0) v + 1 else v) }) + } + } + test("tupleLeft and tupleRight tuple values with a constant value preserving structure") { forAll { (l: List[Int], o: Option[Int], m: Map[String, Int], i: Int) => assert(l.tupleLeft(i) === (List.tabulate(l.length)(in => (i, l(in)))))