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

alleycats - Extract[F[_]] the only way to get the syntax is the deprecated one #4670

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

aluscent
Copy link

Fixes #4642

Comment on lines 30 to 32
implicit class ExtractOps[F[_], A](fa: F[A])(implicit ev: Extract[F]) {
def extract(): A = ev.extract(fa)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it makes sense to convert it into a value class, e.g.:

Suggested change
implicit class ExtractOps[F[_], A](fa: F[A])(implicit ev: Extract[F]) {
def extract(): A = ev.extract(fa)
}
implicit final class ExtractOps[F[_], A](private val fa: F[A]) extends AnyVal {
def extract(implicit ev: Extract[F]): A = ev.extract(fa)
}

The reason why I think it could make sense is that

  1. This is a pretty valid use case for value classes in general and allows to save on memory allocations a little bit.

  2. Newer syntaxes in Cats-core use this approach a lot, see Applicative syntax for example:

    final class ApplicativeIdOps[A](private val a: A) extends AnyVal {
    def pure[F[_]](implicit F: Applicative[F]): F[A] = F.pure(a)
    }
    @deprecated("Use by-value or by-name version", "2.8.0")
    final class ApplicativeOps[F[_], A](private val fa: F[A]) extends AnyVal {
    def replicateA(n: Int)(implicit F: Applicative[F]): F[List[A]] = F.replicateA(n, fa)
    def unlessA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.unlessA(cond)(fa)
    def whenA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.whenA(cond)(fa)
    }
    final class ApplicativeByValueOps[F[_], A](private val fa: F[A]) extends AnyVal {
    def replicateA(n: Int)(implicit F: Applicative[F]): F[List[A]] = F.replicateA(n, fa)
    def replicateA_(n: Int)(implicit F: Applicative[F]): F[Unit] = F.replicateA_(n, fa)
    }
    final class ApplicativeByNameOps[F[_], A](private val fa: () => F[A]) extends AnyVal {
    def unlessA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.unlessA(cond)(fa())
    def whenA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.whenA(cond)(fa())
    }

@satorg
Copy link
Contributor

satorg commented Oct 31, 2024

Thank you for the PR!

@aluscent
Copy link
Author

aluscent commented Nov 2, 2024

Great idea! I would definitely apply that.
I also asked a question about test cases in the issue's thread. Please check it out.

The reason why I think it could make sense is that:
1. This is a pretty valid use case for value classes in general and allows to save on memory allocations a little bit.
2. Newer syntaxes in Cats-core use this approach a lot, see Applicative syntax for example:

Co-authored-by: Sergey Torgashov <satorg@users.noreply.github.com>
@aluscent
Copy link
Author

aluscent commented Nov 2, 2024

The problem here is that the code should be implemented in this way:

object extract extends ExtractSyntax

trait ExtractSyntax {
  implicit def toExtractOps[F[_], A](fa: F[A]): ExtractOps[F, A] = new ExtractOps[F, A](fa)
}

final class ExtractOps[F[_], A](private val fa: F[A]) extends AnyVal {
  def extract(implicit ev: Extract[F]): A = ev.extract(fa)
}

Doesn't this break the limitations of using value classes? I mean passing type arguments to ExtractOps and the new ExtractOps[F, A](fa) all will result in unintended boxing, right?

@aluscent aluscent requested a review from satorg November 5, 2024 08:45
@satorg
Copy link
Contributor

satorg commented Nov 11, 2024

@aluscent ,
sorry for the long reply.

Doesn't this break the limitations of using value classes? I mean passing type arguments to ExtractOps and the new ExtractOpsF, A all will result in unintended boxing, right?

To be honest, I'm not sure I'm totally getting your concern. Why do you think the above should cause the boxing, could you elaborate please? To my best knowledge, no it shouldn't in this case. But I might be missing something. So do not take my word for it :)

If you're in doubt, you can check it for yourself by decompiling scala code to java (there are a plenty of open-source tools available) and see what it looks like. I did it a few times for some other typeclasses and AnyVal worked out pretty good there (i.e. no extra allocation/boxing whatsoever), but those might not be exactly the same case as this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

alleycats - Extract[F[_]] the only way to get the syntax is the deprecated one
2 participants