-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Applicative whenA/unlessA syntax extensions should respect call-by-name parameter #3899
Applicative whenA/unlessA syntax extensions should respect call-by-name parameter #3899
Conversation
@@ -17,3 +19,8 @@ final class ApplicativeOps[F[_], A](private val fa: F[A]) extends AnyVal { | |||
def unlessA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.unlessA(cond)(fa) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also suggest deprecating these two
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could just make the old syntax not implicit and substitute the new one with the same names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joroKr21 then replicateA
will become a syntax extenstion on => F[A]
, while the method itself accepts F[A]
. Is it ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I'm not sure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@catostrophe how about replicateA(0)
? Shouldn't that also not execute fa
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but replicateA in Applicative takes a call-by-value param
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and exposing a by-name API for replicateA
might mislead users to think of a more imperative style of API like List.fill
where => F[A]
might have side-effects.
Another option would be to extract ApplicativeStrictOps
for replicateA
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add some test coverage?
Also I think this should probably be added to the Applicative trait and then the syntax is calling it so it can potentially be specialized by different type constructors when some efficiency can be gained.
@@ -17,3 +19,8 @@ final class ApplicativeOps[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) | |||
} | |||
|
|||
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()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use .void on these two.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, what for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, I got confused. Often we have foo_
as a variant of foo
that discards the value. But I see that here we are doing something else (just using it as another version that uses call by name correctly.
I would like to see another name since, I think, that pattern is established and this would be a violation of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐑 Um, if you think of a naming variant convention for "like X but call-by-name" we'd like to use it in mouse too, similar situation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I offer suffix L
for Lazy, thus whenAL
& unlessAL
. It's short, and the reason for the L is at least sane, although not immediately self-evident
@@ -17,3 +19,8 @@ final class ApplicativeOps[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) | |||
} | |||
|
|||
final class ApplicativeByNameOps[F[_], A](private val fa: () => F[A]) extends AnyVal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a minor thing, but wouldn't it be better to keep constructors of *-Ops
classes private to the syntax
package? I know, not all the existing classes do follow it, but perhaps it is a good idea for all new classes, wdyt?
final class ApplicativeByNameOps[F[_], A] private[syntax] (private val fa: () => F[A])
extends AnyVal
I mean, it is unlikely that we want to allow users to create Ops
files explicitly, isn't? Or is there some use case for that?
The current implementation of the
.whenA
/.unlessA
syntax extensions doesn't respectF[A]
being passed as call-by-name.This leads to issues where it's important.
See: typelevel/fs2#2412
Unfortunately we can't change whenA/unlessA directly due to bincompat guarantees.
But the new method names even better reflect that we throw
A
out (as intraverse_
). I would also deprecate the old ones.Closes #3687