Skip to content

Commit

Permalink
Add mapOrKeep to Functor
Browse files Browse the repository at this point in the history
  • Loading branch information
jozic committed Apr 6, 2024
1 parent 67aad39 commit a041a94
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 11 deletions.
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/Functor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,19 @@ 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].mapOrKeep(List(1, 2, 3)) { case 2 => 42 }
* res0: List[Int] = List(1, 42, 3)
* }}}
*/
def mapOrKeep[A, A1 >: A](fa: F[A])(pf: PartialFunction[A, A1]): F[A1] = map(fa)(a => pf.applyOrElse(a, identity[A1]))

/**
* Un-zips an `F[(A, B)]` consisting of element pairs or Tuple2 into two separate F's tupled.
*
Expand Down Expand Up @@ -258,6 +271,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 mapOrKeep[A1 >: A](pf: PartialFunction[A, A1]): F[A1] = typeClassInstance.mapOrKeep[A, A1](self)(pf)
}
trait AllOps[F[_], A] extends Ops[F, A] with Invariant.AllOps[F, A] {
type TypeClassType <: Functor[F]
Expand Down
21 changes: 11 additions & 10 deletions docs/nomenclature.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ _WARNING_: this page is written manually, and not automatically generated, so ma

### Functor

| Type | Method Name |
| ------------- |--------------|
| `F[A] => F[Unit]` | `void` |
| `F[A] => B => F[B]` | `as` |
| `F[A] => (A => B) => F[B]` | `map` |
| `F[A] => (A => B) => F[(A,B)]` | `fproduct` |
| `F[A] => (A => B) => F[(B,A)]` | `fproductLeft` |
| `F[A] => B => F[(B, A)]` | `tupleLeft` |
| `F[A] => B => F[(A, B)]` | `tupleRight` |
| `(A => B) => (F[A] => F[B])` | `lift` |
| Type | Method Name | Notes |
|--------------------------------|----------------|-------|
| `F[A] => F[Unit]` | `void` |
| `F[A] => B => F[B]` | `as` |
| `F[A] => (A => B) => F[B]` | `map` |
| `F[A] => (A => A1) => F[A1])` | `mapOrKeep` | A1 >: A, the (A => A1) is a PartialFunction
| `F[A] => (A => B) => F[(A,B)]` | `fproduct` |
| `F[A] => (A => B) => F[(B,A)]` | `fproductLeft` |
| `F[A] => B => F[(B, A)]` | `tupleLeft` |
| `F[A] => B => F[(A, B)]` | `tupleRight` |
| `(A => B) => (F[A] => F[B])` | `lift` |

### Apply

Expand Down
9 changes: 9 additions & 0 deletions laws/src/main/scala/cats/laws/FunctorLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ trait FunctorLaws[F[_]] extends InvariantLaws[F] {
def covariantIdentity[A](fa: F[A]): IsEq[F[A]] =
fa.map(identity) <-> fa

def mapOrKeepEmpty[A](fa: F[A]): IsEq[F[A]] =
fa.mapOrKeep(PartialFunction.empty) <-> fa

def mapOrKeepIdentity[A](fa: F[A]): IsEq[F[A]] =
fa.mapOrKeep { case a => a } <-> fa

def mapOrKeepWiden[A, A1 >: A](fa: F[A]): IsEq[F[A1]] =
fa.mapOrKeep { case a => a: A1 } <-> fa.widen[A1]

def covariantComposition[A, B, C](fa: F[A], f: A => B, g: B => C): IsEq[F[C]] =
fa.map(f).map(g) <-> fa.map(f.andThen(g))
}
Expand Down
5 changes: 4 additions & 1 deletion laws/src/main/scala/cats/laws/discipline/FunctorTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ trait FunctorTests[F[_]] extends InvariantTests[F] {
name = "functor",
parent = Some(invariant[A, B, C]),
"covariant identity" -> forAll(laws.covariantIdentity[A] _),
"covariant composition" -> forAll(laws.covariantComposition[A, B, C] _)
"covariant composition" -> forAll(laws.covariantComposition[A, B, C] _),
"mapOrKeep empty" -> forAll(laws.mapOrKeepEmpty[A] _),
"mapOrKeep identity" -> forAll(laws.mapOrKeepIdentity[A] _),
"mapOrKeep widen" -> forAll(laws.mapOrKeepWiden[A, A] _)
)
}

Expand Down
12 changes: 12 additions & 0 deletions tests/shared/src/test/scala/cats/tests/FunctorSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ class FunctorSuite extends CatsSuite {
}
}

test("mapOrKeep maps matching elements preserving structure") {
forAll { (l: List[Int], o: Option[Int], m: Map[String, Int]) =>
assert(l.mapOrKeep { case i if i % 2 == 0 => i + 1 } === l.map(i => if (i % 2 == 0) i + 1 else i))
assert(
o.mapOrKeep { case i if i > 0 => i + 1 } === (if (o.nonEmpty) if (o.get > 0) Some(o.get + 1) else o else None)
)
assert(
m.mapOrKeep { 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)))))
Expand Down

0 comments on commit a041a94

Please sign in to comment.