Skip to content

Commit be5dd58

Browse files
authored
Merge pull request #4801 from typelevel/oscar/20251218-optimize_functor_methods
Implement several Functor methods to avoid using map when not needed
2 parents e94ddf0 + 1661cad commit be5dd58

25 files changed

+450
-8
lines changed

core/src/main/scala/cats/data/Ior.scala

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,55 @@ sealed abstract private[data] class IorInstances0 {
985985

986986
override def map[B, C](fa: A Ior B)(f: B => C): A Ior C =
987987
fa.map(f)
988+
989+
override def as[B, C](fa: A Ior B, c: C): A Ior C =
990+
fa match {
991+
case Ior.Left(_) => fa.asInstanceOf[A Ior C]
992+
case Ior.Right(_) => Ior.Right(c)
993+
case Ior.Both(a, _) => Ior.Both(a, c)
994+
}
995+
996+
override def tupleLeft[B, C](fa: A Ior B, c: C): A Ior (C, B) =
997+
fa match {
998+
case Ior.Left(_) => fa.asInstanceOf[A Ior (C, B)]
999+
case Ior.Right(b) => Ior.Right((c, b))
1000+
case Ior.Both(a, b) => Ior.Both(a, (c, b))
1001+
}
1002+
1003+
override def tupleRight[B, C](fa: A Ior B, c: C): A Ior (B, C) =
1004+
fa match {
1005+
case Ior.Left(_) => fa.asInstanceOf[A Ior (B, C)]
1006+
case Ior.Right(b) => Ior.Right((b, c))
1007+
case Ior.Both(a, b) => Ior.Both(a, (b, c))
1008+
}
1009+
1010+
override def fproduct[B, C](fa: A Ior B)(f: B => C): A Ior (B, C) =
1011+
fa match {
1012+
case Ior.Left(_) => fa.asInstanceOf[A Ior (B, C)]
1013+
case Ior.Right(b) => Ior.Right((b, f(b)))
1014+
case Ior.Both(a, b) => Ior.Both(a, (b, f(b)))
1015+
}
1016+
1017+
override def fproductLeft[B, C](fa: A Ior B)(f: B => C): A Ior (C, B) =
1018+
fa match {
1019+
case Ior.Left(_) => fa.asInstanceOf[A Ior (C, B)]
1020+
case Ior.Right(b) => Ior.Right((f(b), b))
1021+
case Ior.Both(a, b) => Ior.Both(a, (f(b), b))
1022+
}
1023+
1024+
override def unzip[B, C](fab: A Ior (B, C)): (A Ior B, A Ior C) =
1025+
fab match {
1026+
case Ior.Left(a) => (Ior.Left(a), Ior.Left(a))
1027+
case Ior.Right((b, c)) => (Ior.Right(b), Ior.Right(c))
1028+
case Ior.Both(a, (b, c)) => (Ior.Both(a, b), Ior.Both(a, c))
1029+
}
1030+
1031+
override def void[B](fa: A Ior B): A Ior Unit =
1032+
fa match {
1033+
case Ior.Left(_) => fa.asInstanceOf[A Ior Unit]
1034+
case Ior.Right(_) => Ior.Right(())
1035+
case Ior.Both(a, _) => Ior.Both(a, ())
1036+
}
9881037
}
9891038

9901039
implicit def catsDataEqForIor[A: Eq, B: Eq]: Eq[A Ior B] = _ === _

core/src/main/scala/cats/data/Validated.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,36 @@ sealed abstract private[data] class ValidatedInstances2 {
10081008
override def map[A, B](fa: Validated[E, A])(f: (A) => B): Validated[E, B] =
10091009
fa.map(f)
10101010

1011+
override def as[A, B](fa: Validated[E, A], b: B): Validated[E, B] =
1012+
fa match {
1013+
case Valid(_) => Valid(b)
1014+
case i @ Invalid(_) => i.asInstanceOf[Validated[E, B]]
1015+
}
1016+
1017+
override def tupleLeft[A, B](fa: Validated[E, A], b: B): Validated[E, (B, A)] =
1018+
fa match {
1019+
case Valid(a) => Valid((b, a))
1020+
case i @ Invalid(_) => i.asInstanceOf[Validated[E, (B, A)]]
1021+
}
1022+
1023+
override def tupleRight[A, B](fa: Validated[E, A], b: B): Validated[E, (A, B)] =
1024+
fa match {
1025+
case Valid(a) => Valid((a, b))
1026+
case i @ Invalid(_) => i.asInstanceOf[Validated[E, (A, B)]]
1027+
}
1028+
1029+
override def fproduct[A, B](fa: Validated[E, A])(f: A => B): Validated[E, (A, B)] =
1030+
fa match {
1031+
case Valid(a) => Valid((a, f(a)))
1032+
case i @ Invalid(_) => i.asInstanceOf[Validated[E, (A, B)]]
1033+
}
1034+
1035+
override def fproductLeft[A, B](fa: Validated[E, A])(f: A => B): Validated[E, (B, A)] =
1036+
fa match {
1037+
case Valid(a) => Valid((f(a), a))
1038+
case i @ Invalid(_) => i.asInstanceOf[Validated[E, (B, A)]]
1039+
}
1040+
10111041
override def reduceLeftToOption[A, B](fa: Validated[E, A])(f: A => B)(g: (B, A) => B): Option[B] =
10121042
fa.map(f).toOption
10131043

@@ -1052,6 +1082,13 @@ sealed abstract private[data] class ValidatedInstances2 {
10521082
case _ => Nil
10531083
}
10541084

1085+
override def unzip[A, B](fab: Validated[E, (A, B)]): (Validated[E, A], Validated[E, B]) =
1086+
fab match {
1087+
case Valid((a, b)) => (Valid(a), Valid(b))
1088+
case i @ Invalid(_) =>
1089+
(i.asInstanceOf[Validated[E, A]], i.asInstanceOf[Validated[E, B]])
1090+
}
1091+
10551092
override def isEmpty[A](fa: Validated[E, A]): Boolean = fa.isInvalid
10561093

10571094
override def void[A](fa: Validated[E, A]): Validated[E, Unit] =

core/src/main/scala/cats/instances/either.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,36 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances {
7777
override def map[B, C](fa: Either[A, B])(f: B => C): Either[A, C] =
7878
fa.map(f)
7979

80+
override def as[B, C](fa: Either[A, B], c: C): Either[A, C] =
81+
fa match {
82+
case Right(_) => Right(c)
83+
case left @ Left(_) => left.rightCast[C]
84+
}
85+
86+
override def tupleLeft[B, C](fa: Either[A, B], c: C): Either[A, (C, B)] =
87+
fa match {
88+
case Right(b) => Right((c, b))
89+
case left @ Left(_) => left.rightCast[(C, B)]
90+
}
91+
92+
override def tupleRight[B, C](fa: Either[A, B], c: C): Either[A, (B, C)] =
93+
fa match {
94+
case Right(b) => Right((b, c))
95+
case left @ Left(_) => left.rightCast[(B, C)]
96+
}
97+
98+
override def fproduct[B, C](fa: Either[A, B])(f: B => C): Either[A, (B, C)] =
99+
fa match {
100+
case Right(b) => Right((b, f(b)))
101+
case left @ Left(_) => left.rightCast[(B, C)]
102+
}
103+
104+
override def fproductLeft[B, C](fa: Either[A, B])(f: B => C): Either[A, (C, B)] =
105+
fa match {
106+
case Right(b) => Right((f(b), b))
107+
case left @ Left(_) => left.rightCast[(C, B)]
108+
}
109+
80110
@tailrec
81111
def tailRecM[B, C](b: B)(f: B => Either[A, Either[B, C]]): Either[A, C] =
82112
f(b) match {
@@ -210,6 +240,12 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances {
210240
}
211241
}
212242

243+
override def unzip[B, C](fab: Either[A, (B, C)]): (Either[A, B], Either[A, C]) =
244+
fab match {
245+
case Right((b, c)) => (Right(b), Right(c))
246+
case left @ Left(_) => (left.rightCast[B], left.rightCast[C])
247+
}
248+
213249
override def void[B](e: Either[A, B]): Either[A, Unit] =
214250
if (e.isRight) Either.unit
215251
else e.asInstanceOf[Either[A, Unit]] // it is Left(a)

core/src/main/scala/cats/instances/list.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
203203
override def zipWithIndex[A](fa: List[A]): List[(A, Int)] =
204204
fa.zipWithIndex
205205

206+
override def unzip[A, B](fab: List[(A, B)]): (List[A], List[B]) =
207+
fab.unzip
208+
206209
override def partitionEither[A, B, C](
207210
fa: List[A]
208211
)(f: A => Either[B, C])(implicit A: Alternative[List]): (List[B], List[C]) =

core/src/main/scala/cats/instances/map.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
5959
override def map[A, B](fa: Map[K, A])(f: A => B): Map[K, B] =
6060
fa.map { case (k, a) => (k, f(a)) }
6161

62+
override def unzip[A, B](fab: Map[K, (A, B)]): (Map[K, A], Map[K, B]) = {
63+
val leftBuilder = Map.newBuilder[K, A]
64+
val rightBuilder = Map.newBuilder[K, B]
65+
leftBuilder.sizeHint(fab.size)
66+
rightBuilder.sizeHint(fab.size)
67+
fab.foreach { case (k, (a, b)) =>
68+
leftBuilder += k -> a
69+
rightBuilder += k -> b
70+
}
71+
(leftBuilder.result(), rightBuilder.result())
72+
}
73+
6274
override def map2[A, B, Z](fa: Map[K, A], fb: Map[K, B])(f: (A, B) => Z): Map[K, Z] =
6375
if (fb.isEmpty) Map.empty // do O(1) work if fb is empty
6476
else fa.flatMap { case (k, a) => fb.get(k).map(b => (k, f(a, b))) }

core/src/main/scala/cats/instances/option.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,33 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances {
7777
override def map[A, B](fa: Option[A])(f: A => B): Option[B] =
7878
fa.map(f)
7979

80+
override def as[A, B](fa: Option[A], b: B): Option[B] =
81+
if (fa.isDefined) Some(b) else None
82+
83+
override def tupleLeft[A, B](fa: Option[A], b: B): Option[(B, A)] =
84+
fa match {
85+
case Some(a) => Some((b, a))
86+
case None => None
87+
}
88+
89+
override def tupleRight[A, B](fa: Option[A], b: B): Option[(A, B)] =
90+
fa match {
91+
case Some(a) => Some((a, b))
92+
case None => None
93+
}
94+
95+
override def fproduct[A, B](fa: Option[A])(f: A => B): Option[(A, B)] =
96+
fa match {
97+
case Some(a) => Some((a, f(a)))
98+
case None => None
99+
}
100+
101+
override def fproductLeft[A, B](fa: Option[A])(f: A => B): Option[(B, A)] =
102+
fa match {
103+
case Some(a) => Some((f(a), a))
104+
case None => None
105+
}
106+
80107
def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] =
81108
fa.flatMap(f)
82109

@@ -244,6 +271,12 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances {
244271
case (Some(a), Some(b)) => Some(f(Ior.both(a, b)))
245272
}
246273

274+
override def unzip[A, B](fab: Option[(A, B)]): (Option[A], Option[B]) =
275+
fab match {
276+
case Some((a, b)) => (Some(a), Some(b))
277+
case None => (None, None)
278+
}
279+
247280
override def unit: Option[Unit] = someUnit
248281
override def void[A](oa: Option[A]): Option[Unit] =
249282
if (oa.isDefined) someUnit else None

core/src/main/scala/cats/instances/queue.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ trait QueueInstances extends cats.kernel.instances.QueueInstances {
189189

190190
override def toIterable[A](fa: Queue[A]): Iterable[A] = fa
191191

192+
override def unzip[A, B](fab: Queue[(A, B)]): (Queue[A], Queue[B]) =
193+
fab.unzip
194+
192195
override def reduceLeftOption[A](fa: Queue[A])(f: (A, A) => A): Option[A] =
193196
fa.reduceLeftOption(f)
194197

core/src/main/scala/cats/instances/seq.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ trait SeqInstances extends cats.kernel.instances.SeqInstances {
150150
override def zipWithIndex[A](fa: Seq[A]): Seq[(A, Int)] =
151151
fa.zipWithIndex
152152

153+
override def unzip[A, B](fab: Seq[(A, B)]): (Seq[A], Seq[B]) =
154+
fab.unzip
155+
153156
override def exists[A](fa: Seq[A])(p: A => Boolean): Boolean =
154157
fa.exists(p)
155158

core/src/main/scala/cats/instances/sortedMap.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,19 @@ trait SortedMapInstances extends SortedMapInstances2 {
7878
fa.map { case (k, a) => (k, f(a)) }
7979
}
8080

81+
override def unzip[A, B](fab: SortedMap[K, (A, B)]): (SortedMap[K, A], SortedMap[K, B]) = {
82+
implicit val ordering: Ordering[K] = fab.ordering
83+
val leftBuilder = SortedMap.newBuilder[K, A](ordering)
84+
val rightBuilder = SortedMap.newBuilder[K, B](ordering)
85+
leftBuilder.sizeHint(fab.size)
86+
rightBuilder.sizeHint(fab.size)
87+
fab.foreach { case (k, (a, b)) =>
88+
leftBuilder += k -> a
89+
rightBuilder += k -> b
90+
}
91+
(leftBuilder.result(), rightBuilder.result())
92+
}
93+
8194
override def map2Eval[A, B, Z](
8295
fa: SortedMap[K, A],
8396
fb: Eval[SortedMap[K, B]]

core/src/main/scala/cats/instances/try.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,36 @@ trait TryInstances extends TryInstances1 {
120120

121121
override def map[A, B](ta: Try[A])(f: A => B): Try[B] = ta.map(f)
122122

123+
override def as[A, B](ta: Try[A], b: B): Try[B] =
124+
ta match {
125+
case Success(_) => Success(b)
126+
case err: Failure[?] => castFailure[B](err)
127+
}
128+
129+
override def tupleLeft[A, B](ta: Try[A], b: B): Try[(B, A)] =
130+
ta match {
131+
case Success(a) => Success((b, a))
132+
case err: Failure[?] => castFailure[(B, A)](err)
133+
}
134+
135+
override def tupleRight[A, B](ta: Try[A], b: B): Try[(A, B)] =
136+
ta match {
137+
case Success(a) => Success((a, b))
138+
case err: Failure[?] => castFailure[(A, B)](err)
139+
}
140+
141+
override def fproduct[A, B](ta: Try[A])(f: A => B): Try[(A, B)] =
142+
ta match {
143+
case Success(a) => Success((a, f(a)))
144+
case err: Failure[?] => castFailure[(A, B)](err)
145+
}
146+
147+
override def fproductLeft[A, B](ta: Try[A])(f: A => B): Try[(B, A)] =
148+
ta match {
149+
case Success(a) => Success((f(a), a))
150+
case err: Failure[?] => castFailure[(B, A)](err)
151+
}
152+
123153
override def reduceLeftToOption[A, B](fa: Try[A])(f: A => B)(g: (B, A) => B): Option[B] =
124154
fa.map(f).toOption
125155

@@ -165,6 +195,12 @@ trait TryInstances extends TryInstances1 {
165195
case Success(a) => a :: Nil
166196
}
167197

198+
override def unzip[A, B](fab: Try[(A, B)]): (Try[A], Try[B]) =
199+
fab match {
200+
case Success((a, b)) => (Success(a), Success(b))
201+
case err: Failure[?] => (castFailure[A](err), castFailure[B](err))
202+
}
203+
168204
override def isEmpty[A](fa: Try[A]): Boolean = fa.isFailure
169205

170206
override def catchNonFatal[A](a: => A)(implicit ev: Throwable <:< Throwable): Try[A] = Try(a)

0 commit comments

Comments
 (0)