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

Conversation

mox692
Copy link
Contributor

@mox692 mox692 commented Jul 11, 2023

Try to fix #4475.

@mox692 mox692 marked this pull request as draft July 11, 2023 19:04
@mox692
Copy link
Contributor Author

mox692 commented Jul 11, 2023

I plan to make modifications for syntax other than Apply in this PR as well. (but I may not be able to respond a bit until next week.)

Comment on lines 51 to 55
implicit final def catsSyntaxProductOps[F[_], A, B](fa: F[A]): ProductOps[F, A, B] =
new ProductOps[F, A, B](fa)

implicit final def catsSyntaxMap2Ops[F[_], A, B, C](fa: F[A]): Map2Ops[F, A, B, C] =
new Map2Ops[F, A, B, C](fa)
Copy link
Member

Choose a reason for hiding this comment

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

Why separate these ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no strong reason to separate them, so I can put these together. (but it was a bit difficult to come up with a unique syntax name that doesn't conflict with exisisting one. ApplySyntaxFA or ApplyOps2 would be fine?)

Copy link
Member

Choose a reason for hiding this comment

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

ApplyOps2 or ApplyOpsBincompat1 or similar seems fine. We probably have some similar situations somewhere else in Cats.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fix: f97affd

Comment on lines 51 to 52
implicit final def catsSyntaxProductOps[F[_], A, B](fa: F[A]): ProductOps[F, A, B] =
new ProductOps[F, A, B](fa)
Copy link
Contributor

@satorg satorg Jul 24, 2023

Choose a reason for hiding this comment

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

Oh, I doubt that type inference would work out well in this case. An implicit method should only include types that are parts of its parameters. Otherwise the compiler may not have a clue how to infer them. I would suggest

Suggested change
implicit final def catsSyntaxProductOps[F[_], A, B](fa: F[A]): ProductOps[F, A, B] =
new ProductOps[F, A, B](fa)
implicit final def catsSyntaxProductOps[F[_], A](fa: F[A]): YouNameIt[F, A] =
new YouNameIt[F, A](fa)

then in the YouNameIt:

final class YouNameIt[F[_], A](private val fa: F[A]) extends AnyVal {
  def productR[B](fb: F[B])(implicit F: Apply[F]): F[B] = F.productR(fa)(fb)
}

I.e. the B type is introduced when it is actually used.
And the same for the other implicit methods below.

Feel free to give a try to your versions of course, but it looks suspicious to me personally.

Copy link
Contributor

Choose a reason for hiding this comment

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

And of course all the methods that require just F[A] can be grouped together. Only methods that require more specialized types like F[A => B] have to be extracted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I didn't consider that aspect, thank you for pointing it out.

Fix: 0ee124e

Comment on lines +69 to +71
@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
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.

@mox692
Copy link
Contributor Author

mox692 commented Jul 24, 2023

I also tried to address ContravariantSemigroupal and ContravariantMonoidal mentioned in the issue, but I think the Ops that causing allocations doesn't provide any syntax (Or I may have missed something.)

trait Ops[F[_], A] extends Serializable {
type TypeClassType <: ContravariantSemigroupal[F]
def self: F[A]
val typeClassInstance: TypeClassType
}

trait Ops[F[_], A] extends Serializable {
type TypeClassType <: ContravariantMonoidal[F]
def self: F[A]
val typeClassInstance: TypeClassType
}

Or should I deprecate the syntax that causes allocation and just add a new empty syntax class for future use?

@mox692 mox692 marked this pull request as ready for review July 24, 2023 15:04
@armanbilge armanbilge self-requested a review July 24, 2023 17:15
@satorg satorg self-requested a review July 24, 2023 17:30
@armanbilge armanbilge added the bug label Jul 25, 2023
@armanbilge armanbilge added this to the 2.10.0 milestone Jul 25, 2023
@satorg
Copy link
Contributor

satorg commented Jul 25, 2023

I also tried to address ContravariantSemigroupal and ContravariantMonoidal mentioned in the issue, but I think the Ops that causing allocations doesn't provide any syntax (Or I may have missed something.)
Or should I deprecate the syntax that causes allocation and just add a new empty syntax class for future use?

If there's no actual syntax that can be used then I would suggest simply to deprecate it.
I think it should not cause any source incompatibility in such a case.

mox692 and others added 2 commits July 26, 2023 20:28
Co-authored-by: Sergey Torgashov <satorg@users.noreply.github.com>
@mox692 mox692 force-pushed the fix/unnecessary_syntax_allocation branch from 8ae9b9c to f09b400 Compare July 28, 2023 13:28
@satorg satorg requested a review from armanbilge July 28, 2023 17:32
@armanbilge armanbilge closed this Jul 31, 2023
@armanbilge armanbilge reopened this Jul 31, 2023
armanbilge
armanbilge previously approved these changes Aug 2, 2023
Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

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

This looks good to me. Very nice work and thanks for your patience!

After we merge this it is probably time to release Cats v2.10.0 :)

}

private[syntax] trait ApplySyntaxBinCompat0 {
implicit final def catsSyntaxIfApplyOps[F[_]](fa: F[Boolean]): IfApplyOps[F] =
new IfApplyOps[F](fa)
}

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

@satorg satorg Aug 2, 2023

Choose a reason for hiding this comment

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

I feel we should either add with Serializable everywhere to each *Ops or go remove it, to be consistent.
However, this class does have it whereas the next one down below does not.
I personally don't see any reason why we would need Serializable for such a kind of object, so I'd prefer to remove it. But maybe there is a reason, @armanbilge could you suggest please?

Copy link
Member

Choose a reason for hiding this comment

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

See #4477 (comment). It shouldn't be needed if the syntax classes are not being allocated, which is the hope 😅

I don't know enough of the history but at some point it seems that people did care that these were serializable /shrug

Copy link
Contributor

@satorg satorg Aug 2, 2023

Choose a reason for hiding this comment

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

It shouldn't be needed if the syntax classes are not being allocated

But even if those class instances are still allocated, what is the point in having them serialized whatsoever?
Those class instances are short-living and get instantiated just in place. They are not supposed to be saved nor transferred anywhere, are they?

Copy link
Member

Choose a reason for hiding this comment

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

They are not supposed to be saved nor transferred anywhere, are they?

I have no idea. I think typically serializability is important when you are dealing with e.g. Spark. Maybe in some (pathological) situations these classes can get caught up in the serialization graph somehow ...

Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 yeah, not sure either. I would probably suggest to remove Serializable for now. Just because it is always easier to add something later rather than remove it whenever. We are not introducing any binary incompatibility anyway, so if Spark guys or anyone else start complaining, then we can easily issue a hotfix release. But at least we will know for sure in that case :) Although I'd guess there will be no complains about it.

Otherwise, if we decide to go the opposite way, then we should add Serializable all the remaining syntax extension classes, shouldn't we?

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 fine with removing it. Anyway I think all Spark users are stuck on Cats 2.0.0-M5 or something 😛

Copy link
Contributor Author

@mox692 mox692 Aug 3, 2023

Choose a reason for hiding this comment

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

Oh sorry, I unintentionally made ApplyFABOps Serializable🤦‍♂️
Thanks for pointing that out.

@filipwiech
Copy link

Hey @mox692, @armanbilge, @satorg! 🙂
Is there anything left to do here? Would be really great to get this merged and have Cats 2.10 released. The last release was a long while ago (Nov 22) and there have been several very nice improvements made in the meantime (including this one).
Thank you for your hard work on this, cheers! 👍

armanbilge
armanbilge previously approved these changes Aug 8, 2023
@satorg
Copy link
Contributor

satorg commented Aug 8, 2023

Sorry for the delay (and thank you for the ping 😄).

The code itself looks great indeed, no concerns about it. However I've found out that there're no tests that could help us to make sure that the changes do actually work as expected. It could be especially important for the syntax moved into the ApplyFABOps and ApplyFABCOps classes.

There are syntax tests in the SyntaxSuite class (see the tests module) that check particular syntaxes without any real code execution. I feel it would be really helpful if we could add the missing syntax calls into that test suite. It gets to be the testApply method in this case, I guess.

@mox692, wdyt?

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

final class ApplyFABCOps[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.

Just came by to say this is a cool naming 😄

@armanbilge
Copy link
Member

However I've found out that there're no tests that could help us to make sure that the changes do actually work as expected. It could be especially important for the syntax moved into the ApplyFABOps and ApplyFABCOps classes.

Ouch, good catch. I agree, we should have these to protect against these kind of refactorings.

@mox692
Copy link
Contributor Author

mox692 commented Aug 9, 2023

Sure, I'll add the test too.

@mox692 mox692 requested a review from armanbilge August 12, 2023 10:46
@mox692
Copy link
Contributor Author

mox692 commented Aug 12, 2023

Hmmm, it looks like FutureSuite is failing in ci, but does this have anything to do with the change? 🤔

In mox692/cats, the test seems to succeed for the same commit 288587e.

ci logs
==> X cats.jvm.tests.FutureSuite.Future: coflatMap.coflatMap identity  3.022s munit.FailException: Failing seed: WznzS7nzhHoolYDQK4VxhrIMJ3M1xMQpRmki0cZs4_C=
You can reproduce this failure by adding the following override to your suite:

  override val scalaCheckInitialSeed = "WznzS7nzhHoolYDQK4VxhrIMJ3M1xMQpRmki0cZs4_C="

Exception raised on property evaluation.
> ARG_0: Future(Success(0))
> Exception: java.util.concurrent.TimeoutException: Future timed out after [3 seconds]
    at munit.ScalaCheckSuite.propToTry(ScalaCheckSuite.scala:98)
Caused by: java.util.concurrent.TimeoutException: Future timed out after [3 seconds]
    at scala.concurrent.impl.Promise$DefaultPromise.tryAwait0(Promise.scala:248)
    at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:261)
    at scala.concurrent.Await$.$anonfun$result$1(package.scala:201)
    at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:62)
    at scala.concurrent.Await$.result(package.scala:124)
    at cats.jvm.tests.FutureSuite.cats$jvm$tests$FutureSuite$$$anonfun$eqfa$1(FutureSuite.scala:45)

@armanbilge
Copy link
Member

Sorry, looks like it was a flake.

Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

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

Thanks for adding those tests! Ok, I think we are finally good 😁

Copy link
Contributor

@satorg satorg left a comment

Choose a reason for hiding this comment

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

Thank you, looks great!

@armanbilge armanbilge merged commit f496e25 into typelevel:main Aug 13, 2023
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Apply syntax is allocating
5 participants