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 foldF, cataF and emptyflatTap to OptionT #3335

Merged
merged 9 commits into from
May 13, 2020

Conversation

ybasket
Copy link
Contributor

@ybasket ybasket commented Feb 29, 2020

Adds several handy methods to OptionT:

  • foldF allows to fold effectually, similar to EitherT.foldF
  • cataF is a variant of foldF with one parameter list, similar as cata is to fold
  • emptyflatTap (suggestions for better names welcome) allows to perform an effect if the OptionT carries a None value inside, leaving the result unchanged (except if raising an error, that would propagate).

An example for a use case for emptyflatTap is looking up a value in a cache (returning F[Option[V]] which you wrap in OptionT for further handling, but logging (as F[Unit]) if the value was empty:

val cachedResult: F[Option[V]] = cache.get(...)
OptionT(cachedResult)
  .emptyflatTap(logger.info("Cache was empty"))
  .doMoreStuffOnOptionT

@codecov-io
Copy link

codecov-io commented Feb 29, 2020

Codecov Report

Merging #3335 into master will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3335   +/-   ##
=======================================
  Coverage   92.70%   92.71%           
=======================================
  Files         379      379           
  Lines        7981     7985    +4     
  Branches      230      230           
=======================================
+ Hits         7399     7403    +4     
  Misses        582      582           
Flag Coverage Δ
#scala_version_212 92.74% <100.00%> (+<0.01%) ⬆️
#scala_version_213 92.50% <100.00%> (+<0.01%) ⬆️
Impacted Files Coverage Δ
core/src/main/scala/cats/data/OptionT.scala 96.09% <100.00%> (+0.12%) ⬆️

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 def9a6f...468e4f7. Read the comment docs.

@travisbrown travisbrown self-requested a review March 12, 2020 07:54
travisbrown
travisbrown previously approved these changes Mar 12, 2020
Copy link
Contributor

@travisbrown travisbrown left a comment

Choose a reason for hiding this comment

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

Looks good to me. I'm happy to resolve the merge conflict (it's just imports following a testing change) if you'd like.

# Conflicts:
#	tests/src/test/scala/cats/tests/OptionTSuite.scala
travisbrown
travisbrown previously approved these changes Mar 12, 2020
Copy link
Contributor

@travisbrown travisbrown left a comment

Choose a reason for hiding this comment

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

👍 on green.

@ybasket
Copy link
Contributor Author

ybasket commented Mar 12, 2020

@travisbrown thank you (also for the offer to resolve the conflict)! Just fixed the formatting, all checks green now.

@travisbrown travisbrown self-requested a review March 12, 2020 12:34
travisbrown
travisbrown previously approved these changes Mar 12, 2020
Copy link
Contributor

@travisbrown travisbrown left a comment

Choose a reason for hiding this comment

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

Great, thanks, now we just need a second review!

@travisbrown travisbrown added this to the 2.2.0-M1 milestone Mar 20, 2020
* Perform an effect if the value inside the is a `None`, leaving the value untouched. Equivalent to [[orElseF]]
* with an effect returning `None` as argument.
*/
def flatTapNone[B](f: => F[B])(implicit F: Monad[F]): OptionT[F, A] =
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def flatTapNone[B](f: => F[B])(implicit F: Monad[F]): OptionT[F, A] =
def flatTapNone[B](ifNone: => F[B])(implicit F: Monad[F]): OptionT[F, A] =

* res0: List[Int] = List(23, 46)
* }}}
*/
def foldF[B](default: => F[B])(f: A => F[B])(implicit F: FlatMap[F]): F[B] =
Copy link
Contributor

@diesalbla diesalbla Apr 11, 2020

Choose a reason for hiding this comment

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

Suggested change
def foldF[B](default: => F[B])(f: A => F[B])(implicit F: FlatMap[F]): F[B] =
def foldF[B](ifNone: => F[B])(withSome: A => F[B])(implicit F: FlatMap[F]): F[B] =

Should we add more informative names?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I agree with your intention (and have hence changed it on flatTapNone – thx), I would value consistency with fold more and keep the parameter names the same.

* one parameter list, which can result in better type inference in some
* contexts.
*/
def cataF[B](default: => F[B], f: A => F[B])(implicit F: FlatMap[F]): F[B] =
Copy link
Contributor

Choose a reason for hiding this comment

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

I am aware of the notions of catamorphisms, but is that also a unified term or notion across cats? I can only grep the words def cata in OptionTand in CoFree.

Unless we want to extend the use across the whole of cats, perhaps we should drop them, to avoid confusion.

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's a cata to fold already, so when seeing foldF, I'd expect cataF there as well. Whether cata should have been introduced with that name in the first place seems to be outside of the scope of this PR…but that's just my two cents.

@@ -45,6 +68,9 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def semiflatMap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, B] =
flatMap(a => OptionT.liftF(f(a)))

def semiflatTap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, A] =
Copy link
Contributor

@diesalbla diesalbla Apr 11, 2020

Choose a reason for hiding this comment

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

We have had this conversation before: given that the B has no relevance on the types of the outputs, should we just make it an existential?

Suggested change
def semiflatTap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, A] =
def semiflatTap(withSome: A => F[_])(implicit F: Monad[F]): OptionT[F, A] =

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FlatMap has this type parameter for flatTap, EitherT has it for it's semiFlatTap, so again, I would vote for consistency to give cats users a reliable experience in terms of how signatures look and when type inference works. Or do you see any other advantage of the existential which would outweigh this aspect?

Yannick Heiber added 2 commits April 11, 2020 18:16
@ybasket
Copy link
Contributor Author

ybasket commented May 13, 2020

@travisbrown As you approved the code once already and I changed only an argument name since then, would you mind merging? 🙏

@travisbrown
Copy link
Contributor

Sure, I’ll merge when I’m back at a computer.

@LukaJCB LukaJCB merged commit cac6fdc into typelevel:master May 13, 2020
@travisbrown travisbrown modified the milestones: 2.2.0-M1, 2.2.0-M2 May 24, 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