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

whenA and unlessA by-name makes syntax inconsistent with typeclass #3687

Closed
bastewart opened this issue Nov 19, 2020 · 5 comments · Fixed by #3899 or #4207
Closed

whenA and unlessA by-name makes syntax inconsistent with typeclass #3687

bastewart opened this issue Nov 19, 2020 · 5 comments · Fixed by #3899 or #4207

Comments

@bastewart
Copy link
Contributor

Applicative.whenA and Applicative.unlessA are defined using a by-name argument. Syntax extension is via a value-class though which naturally can't use a by-name in it's constructor. This means that calling whenA is either eager or lazy depending on whether it is called from the typeclass (lazy) or the syntax (eager).

Definition:

def whenA[A](cond: Boolean)(f: => F[A]): F[Unit]

Syntax:

final class ApplicativeOps[F[_], A](private val fa: F[A]) extends AnyVal {
  def whenA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.whenA(cond)(fa)
}

I had a brief discussion with @SystemFw (sorry for ping!) on gitter and they suggested opening the issue as it's a messy problem.

@djspiewak
Copy link
Member

This is fixable but keeping bincompat is complicated

@SystemFw
Copy link
Contributor

This is fixable

how to fix is also up for debate, and hinges on some I think fundamental aspects of what cats aims to do, which I want to stress is different from cats-effect.

As an example

Future(doThis).whenA(cond)

@djspiewak
Copy link
Member

how to fix is also up for debate, and hinges on some I think fundamental aspects of what cats aims to do, which I want to stress is different from cats-effect.

I tend to think of by-name as being justified only when we're expecting users to apply combinators in recursive contexts and the combinator implements internal branching. In this case, whenA doesn't implement internal branching (though I would expect it in a recursive context), so I'm tentatively on the side of "it should have always been eager".

Conversely, >> is almost constantly used in recursive contexts, as is ifM, and thus both justifiably remain by-name.

@joroKr21
Copy link
Member

But it does implement internal branching:

def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] =
    if (cond) void(f) else unit

@djspiewak
Copy link
Member

Oooooh I was thinking of ifA. So then yeah, if it's commonly expected to be used in a recursive context, then I think the laziness is justified.

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