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

Add when and unless to OptionT #3233

Merged
merged 2 commits into from
Jan 2, 2020
Merged

Add when and unless to OptionT #3233

merged 2 commits into from
Jan 2, 2020

Conversation

strong-zero
Copy link
Contributor

In scala 2.13, we have Option.{when, unless}. So should cats, I guess 🤔

@codecov-io
Copy link

codecov-io commented Dec 31, 2019

Codecov Report

Merging #3233 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3233      +/-   ##
==========================================
+ Coverage   93.05%   93.05%   +<.01%     
==========================================
  Files         376      376              
  Lines        7412     7413       +1     
  Branches      192      200       +8     
==========================================
+ Hits         6897     6898       +1     
  Misses        515      515
Flag Coverage Δ
#scala_version_212 93.38% <100%> (ø) ⬆️
#scala_version_213 92.81% <100%> (-0.02%) ⬇️
Impacted Files Coverage Δ
core/src/main/scala/cats/data/OptionT.scala 95.9% <100%> (+0.21%) ⬆️
core/src/main/scala/cats/data/Chain.scala 98.43% <0%> (-0.01%) ⬇️
core/src/main/scala/cats/Monad.scala 96.29% <0%> (ø) ⬆️
core/src/main/scala/cats/data/EitherT.scala 98.18% <0%> (ø) ⬆️
core/src/main/scala/cats/instances/queue.scala 100% <0%> (ø) ⬆️
core/src/main/scala/cats/data/WriterT.scala 91.26% <0%> (ø) ⬆️
core/src/main/scala/cats/data/NonEmptyVector.scala 99.15% <0%> (ø) ⬆️
laws/src/main/scala/cats/laws/discipline/Eq.scala 32.83% <0%> (ø) ⬆️
core/src/main/scala/cats/Foldable.scala 98.5% <0%> (ø) ⬆️
core/src/main/scala/cats/instances/list.scala 100% <0%> (ø) ⬆️
... and 11 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 851f03b...49be177. Read the comment docs.

Copy link
Member

@rossabaker rossabaker left a comment

Choose a reason for hiding this comment

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

I like it. Scaladoc nit: we've used both, but more commonly "non-empty" than "nonempty".

Copy link
Member

@rossabaker rossabaker left a comment

Choose a reason for hiding this comment

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

👍 Implementing laziness with call-by-name is something we've debated in Cats, but this seems consistent with both Option (as the base monad) and EitherT (as how Cats does monad transformers).

@LukaJCB LukaJCB merged commit 534d392 into typelevel:master Jan 2, 2020
* Otherwise, the empty `OptionT[F, A]` is returned. Analogous to `Option.when`.
*/
def whenF[F[_], A](cond: Boolean)(fa: => F[A])(implicit F: Applicative[F]): OptionT[F, A] =
if (cond) OptionT.liftF(fa) else OptionT(F.map(fa)(_ => Option.empty))
Copy link
Member

Choose a reason for hiding this comment

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

I find the implementation of the else branch surprising. I would have thought that it is just OptionT.none[F, A] if cond is false (analogous to OptionT.when) but the current implementation runs the F-effect and then replaces the A with None: Option[A].

Copy link
Contributor Author

@strong-zero strong-zero Jan 2, 2020

Choose a reason for hiding this comment

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

It seems odd and it works when F = List and fa = List.empty because we want to get OptionT(List.empty) not OptionT(List(None)). But it breaks the case OptionT.whenF[List, Int](false)(List(1,2)) because we have OptionT(List(None, None)) not OptionT(List.empty)😱

I will create a follow-up pull request to fix it. I find no easy "fix" 😱
whenF uses liftF if cond is true. liftF is OptionT(F.map(fa)(Some(_))) so if cond is false, I think it should use something similar to liftF so that some "good" properties hold:

List(1, 2, 3).map(Option.when(false)(_)) == OptionT.whenF(false)(List(1, 2, 3)).value

List(1, 2, 3).map(Option.when(true)(_)) == OptionT.whenF(true)(List(1, 2, 3)).value

List().map(Option.when(true)(_)) == OptionT.whenF(true)(List()).value

List().map(Option.when(false)(_)) == OptionT.whenF(false)(List()).value

I should have implemented the case when F assumes the empty value or F is a Monoid separately. Any comments and suggestions are welcome!

Copy link
Member

Choose a reason for hiding this comment

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

If these properties imply that val launchMissiles: IO[Unit] = ...; OptionT.whenF(cond)(launchMissiles) always launches the missiles, no matter what cond is, then I think we should get rid of either these properties or whenF. I would prefer the former because whenF seems like a useful utility to me.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @fthomas. Good catch.

How about this for whenF? Many of the F methods on transformers are specified in terms of Id, and passes with Frank's proposed change:

  test("OptionT.whenF[Id, A] consistent with Option.when") {
    def when[A] = (c: Boolean, a: A) => if (c) Some(a) else None
    forAll { (i: Int, b: Boolean) =>
      OptionT.whenF[Id, Int](b)(i).value should ===(when(b, i))
    }
  }

We might even relate it to .sequence for less trivial effects:

forAll { (i: List[Int], b: Boolean) =>
  OptionT.whenF[List, Int](b)(i).value should ===(when(b, i).sequence)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@fthomas @rossabaker Thank you both for your helpful examples. I will make a follow-up pull request.

@strong-zero strong-zero deleted the add-optiont-when-and-unless branch January 3, 2020 04:07
@strong-zero strong-zero mentioned this pull request Jan 4, 2020
rossabaker pushed a commit that referenced this pull request Jan 4, 2020
* Fix whenF, unlessF

* Add comments on inlined Option.{when, unless}

* Remove some test cases of whenF and unlessF
@travisbrown travisbrown added this to the 2.2.0-M1 milestone Jan 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants