Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix unnecessary syntax allocation #4477

Merged
merged 28 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
99e9a71
fix apply syntax allocation
mox692 Jul 11, 2023
af45152
not remove original ops
mox692 Jul 11, 2023
ef328b8
fix ApplyBinCompat1 naming
mox692 Jul 11, 2023
0ba479b
remove implicit for F
mox692 Jul 19, 2023
0c642f6
Add deprecate annotation to old ops
mox692 Jul 20, 2023
e7d544f
create another syntax classes
mox692 Jul 23, 2023
7ca6743
Add doc comments
mox692 Jul 23, 2023
2f5dc8f
fix naming
mox692 Jul 23, 2023
0ee124e
Fix: implicit method type parameter
mox692 Jul 24, 2023
f97affd
Fix: group together F[A] syntax
mox692 Jul 24, 2023
e1ca164
Add: new Semigroupal syntax with no unnecessary allocations
mox692 Jul 24, 2023
5603fee
Fix: failing doctest
mox692 Jul 24, 2023
fc6ac27
Fix: failing doc test
mox692 Jul 25, 2023
342996f
remove implicit from catsSyntaxSemigroupal
mox692 Jul 26, 2023
649c38c
Workaround to the failing test
mox692 Jul 26, 2023
9ebe3a7
Deprecate some synax causing allocation
mox692 Jul 26, 2023
1962dc3
Fix: typo
mox692 Jul 28, 2023
1d5102f
Fix: delete ApplySyntaxBinCompat1
mox692 Jul 28, 2023
c18c981
Move ApplyOps2
mox692 Jul 28, 2023
e74b301
Rename Apply2Ops to ApplyFABCOps
mox692 Jul 28, 2023
0fbc093
Uncurry implicits
mox692 Jul 28, 2023
3f1e8e5
Fix: typo
mox692 Jul 28, 2023
f09b400
Tidying
mox692 Jul 28, 2023
8ae9b9c
Remove unrelated diff
mox692 Jul 28, 2023
a0fdb0b
Merge `ApplyOps` and `ApplyOps2`
armanbilge Aug 2, 2023
119f350
Remove spurious whitespace change
armanbilge Aug 3, 2023
5c06b8a
Remove `Serializable` from `ApplyFABOps`
armanbilge Aug 3, 2023
288587e
Add tests related to the change
mox692 Aug 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ trait AllSyntaxBinCompat3 extends UnorderedFoldableSyntax with Function1Syntax
trait AllSyntaxBinCompat4
extends TraverseFilterSyntaxBinCompat0
with ApplySyntaxBinCompat0
with ApplySyntaxBinCompat1
with ParallelApplySyntax
with FoldableSyntaxBinCompat0
with ReducibleSyntaxBinCompat0
Expand Down
202 changes: 201 additions & 1 deletion core/src/main/scala/cats/syntax/apply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ package cats
package syntax

trait ApplySyntax extends TupleSemigroupalSyntax {
implicit final def catsSyntaxApply[F[_], A](fa: F[A])(implicit F: Apply[F]): Apply.Ops[F, A] =
mox692 marked this conversation as resolved.
Show resolved Hide resolved
@deprecated("Use `ApplySyntaxBinCompat1`", "2.10.0")
final def catsSyntaxApply[F[_], A](fa: F[A], F: Apply[F]): Apply.Ops[F, A] =
new Apply.Ops[F, A] {
type TypeClassType = Apply[F]

Expand All @@ -40,6 +41,205 @@ private[syntax] trait ApplySyntaxBinCompat0 {
new IfApplyOps[F](fa)
}

private[syntax] trait ApplySyntaxBinCompat1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we need a new trait, can we add it to existing ApplySyntax?

implicit final def catsSyntaxApplyFABOps[F[_], A, B](fab: F[A => B]): ApplyFABOps[F, A, B] =
new ApplyFABOps[F, A, B](fab)

implicit final def catsSyntaxApply2Ops[F[_], A, B, C](ff: F[(A, B) => C]): Apply2Ops[F, A, B, C] =
new Apply2Ops[F, A, B, C](ff)

implicit final def catsSyntaxApplyOps2[F[_], A](fa: F[A]): ApplyOps2[F, A] =
new ApplyOps2[F, A](fa)
}

final class ApplyFABOps[F[_], A, B](private val fab: F[A => B]) extends AnyVal {

/**
* @see [[Apply.ap]].
*
* Example:
* {{{
* scala> import cats.syntax.all._
*
* scala> val someF: Option[Int => Long] = Some(_.toLong + 1L)
* scala> val noneF: Option[Int => Long] = None
* scala> val someInt: Option[Int] = Some(3)
* scala> val noneInt: Option[Int] = None
*
* scala> someF.ap(someInt)
* res0: Option[Long] = Some(4)
*
* scala> noneF.ap(someInt)
* res1: Option[Long] = None
*
* scala> someF.ap(noneInt)
* res2: Option[Long] = None
*
* scala> noneF.ap(noneInt)
* res3: Option[Long] = None
* }}}
*/
def ap(fa: F[A])(implicit F: Apply[F]): F[B] = F.ap(fab)(fa)

/**
* Alias for [[ap]].
*/
def <*>(fa: F[A])(implicit F: Apply[F]): F[B] = F.<*>[A, B](fab)(fa)
}

final class Apply2Ops[F[_], A, B, C](private val ff: F[(A, B) => C]) extends AnyVal {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe ApplyFABCOps for consistency with ApplyFABOps? We could also name them ApplyFunctionOps and ApplyFunction2Ops maybe.


/**
* @see [[Apply.ap2]].
*
* Example:
* {{{
* scala> import cats.syntax.all._
*
* scala> val someF: Option[(Int, Int) => Long] = Some((a, b) => (a + b).toLong)
* scala> val noneF: Option[(Int, Int) => Long] = None
* scala> val someInt1: Option[Int] = Some(3)
* scala> val someInt2: Option[Int] = Some(2)
* scala> val noneInt: Option[Int] = None
*
* scala> someF.ap2(someInt1, someInt2)
* res0: Option[Long] = Some(5)
*
* scala> noneF.ap2(someInt1, someInt2)
* res1: Option[Long] = None
*
* scala> someF.ap2(noneInt, noneInt)
* res2: Option[Long] = None
*
* scala> noneF.ap2(noneInt, noneInt)
* res3: Option[Long] = None
* }}}
*
*/
def ap2(fa: F[A], fb: F[B])(implicit F: Apply[F]): F[C] = F.ap2(ff)(fa, fb)
}

final class ApplyOps2[F[_], A](private val fa: F[A]) extends AnyVal {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually can we just move these into ApplyOps below?


/**
* @see [[Apply.productR]].
*
* Example:
* {{{
* scala> import cats.syntax.all._
* scala> import cats.data.Validated
* scala> import Validated.{Valid, Invalid}
*
* scala> type ErrOr[A] = Validated[String, A]
*
* scala> val validInt: ErrOr[Int] = Valid(3)
* scala> val validBool: ErrOr[Boolean] = Valid(true)
* scala> val invalidInt: ErrOr[Int] = Invalid("Invalid int.")
* scala> val invalidBool: ErrOr[Boolean] = Invalid("Invalid boolean.")
*
* scala> validInt.productR(validBool)
* res0: ErrOr[Boolean] = Valid(true)
*
* scala> invalidInt.productR(validBool)
* res1: ErrOr[Boolean] = Invalid(Invalid int.)
*
* scala> validInt.productR(invalidBool)
* res2: ErrOr[Boolean] = Invalid(Invalid boolean.)
*
* scala> invalidInt.productR(invalidBool)
* res3: ErrOr[Boolean] = Invalid(Invalid int.Invalid boolean.)
* }}}
*/
def productR[B](fb: F[B])(implicit F: Apply[F]): F[B] = F.productR(fa)(fb)

/**
* @see [[Apply.productL]].
*
* Example:
* {{{
* scala> import cats.syntax.all._
* scala> import cats.data.Validated
* scala> import Validated.{Valid, Invalid}
*
* scala> type ErrOr[A] = Validated[String, A]
*
* scala> val validInt: ErrOr[Int] = Valid(3)
* scala> val validBool: ErrOr[Boolean] = Valid(true)
* scala> val invalidInt: ErrOr[Int] = Invalid("Invalid int.")
* scala> val invalidBool: ErrOr[Boolean] = Invalid("Invalid boolean.")
*
* scala> validInt.productL(validBool)
* res0: ErrOr[Int] = Valid(3)
*
* scala> invalidInt.productL(validBool)
* res1: ErrOr[Int] = Invalid(Invalid int.)
*
* scala> validInt.productL(invalidBool)
* res2: ErrOr[Int] = Invalid(Invalid boolean.)
*
* scala> invalidInt.productL(invalidBool)
* res3: ErrOr[Int] = Invalid(Invalid int.Invalid boolean.)
* }}}
*/
def productL[B](fb: F[B])(implicit F: Apply[F]): F[A] = F.productL(fa)(fb)

/**
* Alias for [[productR]].
*/
def *>[B](fb: F[B])(implicit F: Apply[F]): F[B] = F.*>(fa)(fb)

/**
* Alias for [[productL]].
*/
def <*[B](fb: F[B])(implicit F: Apply[F]): F[A] = F.<*(fa)(fb)

/**
* @see [[Apply.map2]].
*
* Example:
* {{{
* scala> import cats.syntax.all._
*
* scala> val someInt: Option[Int] = Some(3)
* scala> val noneInt: Option[Int] = None
* scala> val someLong: Option[Long] = Some(4L)
* scala> val noneLong: Option[Long] = None
*
* scala> someInt.map2(someLong)((i, l) => i.toString + l.toString)
* res0: Option[String] = Some(34)
*
* scala> someInt.map2(noneLong)((i, l) => i.toString + l.toString)
* res0: Option[String] = None
*
* scala> noneInt.map2(noneLong)((i, l) => i.toString + l.toString)
* res0: Option[String] = None
*
* scala> noneInt.map2(someLong)((i, l) => i.toString + l.toString)
* res0: Option[String] = None
* }}}
*/
def map2[B, C](fb: F[B])(f: (A, B) => C)(implicit F: Apply[F]): F[C] =
F.map2(fa, fb)(f)

/**
* @see [[Apply.map2Eval]].
*
* Example:
* {{{
* scala> import cats.{Eval, Later}
* scala> import cats.syntax.all._
*
* scala> val bomb: Eval[Option[Int]] = Later(sys.error("boom"))
* scala> val x: Option[Int] = None
*
* scala> x.map2Eval(bomb)(_ + _).value
* res0: Option[Int] = None
* }}}
*/
def map2Eval[B, C](fb: Eval[F[B]])(f: (A, B) => C)(implicit F: Apply[F]): Eval[F[C]] =
F.map2Eval(fa, fb)(f)
}

final class IfApplyOps[F[_]](private val fcond: F[Boolean]) extends AnyVal {

@deprecated("Dangerous method, use ifM (a flatMap) or ifF (a map) instead", "2.6.2")
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ package object syntax {
object alternative extends AlternativeSyntax
object applicative extends ApplicativeSyntax
object applicativeError extends ApplicativeErrorSyntax
object apply extends ApplySyntax with ApplySyntaxBinCompat0
object apply extends ApplySyntax with ApplySyntaxBinCompat0 with ApplySyntaxBinCompat1
object arrow extends ArrowSyntax
object arrowChoice extends ArrowChoiceSyntax
object bifunctor extends BifunctorSyntax
Expand Down
41 changes: 40 additions & 1 deletion core/src/main/scala/cats/syntax/semigroupal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,52 @@ package cats
package syntax

trait SemigroupalSyntax {
implicit final def catsSyntaxSemigroupal[F[_], A](fa: F[A])(implicit F: Semigroupal[F]): SemigroupalOps[F, A] =
@deprecated("Use `catsSntaxSemigroupalOps2`", "2.10.0")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@deprecated("Use `catsSntaxSemigroupalOps2`", "2.10.0")
@deprecated("Use `catsSyntaxSemigroupalOps2`", "2.10.0")

final def catsSyntaxSemigroupal[F[_], A](fa: F[A])(implicit F: Semigroupal[F]): SemigroupalOps[F, A] =
mox692 marked this conversation as resolved.
Show resolved Hide resolved
new SemigroupalOps[F, A] {
type TypeClassType = Semigroupal[F]

val self = fa
val typeClassInstance = F
}

implicit def catsSntaxSemigroupalOps2[F[_], A](fa: F[A]): SemigroupalOps2[F, A] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
implicit def catsSntaxSemigroupalOps2[F[_], A](fa: F[A]): SemigroupalOps2[F, A] =
implicit def catsSyntaxSemigroupalOps2[F[_], A](fa: F[A]): SemigroupalOps2[F, A] =

new SemigroupalOps2[F, A](fa)

}

final class SemigroupalOps2[F[_], A](private val fa: F[A]) extends AnyVal {

/**
* @see [[Semigroupal.product]]
*
* Example:
* {{{
* scala> import cats.syntax.all._
*
* scala> val noneInt: Option[Int] = None
* scala> val some3: Option[Int] = Some(3)
* scala> val noneString: Option[String] = None
* scala> val someFoo: Option[String] = Some("foo")
*
* scala> noneInt.product(noneString)
* res0: Option[(Int, String)] = None
*
* scala> noneInt.product(someFoo)
* res1: Option[(Int, String)] = None
*
* scala> some3.product(noneString)
* res2: Option[(Int, String)] = None
*
* scala> some3.product(someFoo)
* res3: Option[(Int, String)] = Some((3,foo))
* }}}
*/
def product[B](fb: F[B])(implicit F: Semigroupal[F]): F[(A, B)] = F.product(fa, fb)

@deprecated("Replaced by an apply syntax, e.g. instead of (a |@| b).map(...) use (a, b).mapN(...)", "1.0.0-MF")
def |@|[B](fb: F[B]): SemigroupalBuilder[F]#SemigroupalBuilder2[A, B] =
new SemigroupalBuilder[F] |@| fa |@| fb
Comment on lines +69 to +71
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This syntax is already deprecated, but I added it to the new syntax class for compatibility.

}

abstract class SemigroupalOps[F[_], A] extends Semigroupal.Ops[F, A] {
Expand Down