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 traverseCollect to TraverseFilter typeclass #4277

Merged
merged 16 commits into from
Aug 17, 2022
Merged

Add traverseCollect to TraverseFilter typeclass #4277

merged 16 commits into from
Aug 17, 2022

Conversation

emilhotkowski
Copy link
Contributor

Thank you for contributing to Cats!

This is a kind reminder to run sbt +prePR and commit the changed files, if any, before submitting.

Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, looking good! As next steps, do you think we can add a law, syntax (and test for the syntax)?

core/src/main/scala/cats/TraverseFilter.scala Outdated Show resolved Hide resolved
@emilhotkowski
Copy link
Contributor Author

I added syntax and test for it, but I am not sure if we need laws. Looking at TraverseFilterLaws, we don't have laws for other functions like sequenceFilter. Should I add some. for traverseCollect anyway?

@armanbilge
Copy link
Member

Looking at TraverseFilterLaws, we don't have laws for other functions like sequenceFilter. Should I add some. for traverseCollect anyway?

Oh that's interesting. I think laws are good to have, in case an implementation overrides this method, and at the very least to exercise this code. It's also often good to have some tests with a concrete implementation testing it works as expected. Here's a recent PR that is a great example:

@emilhotkowski
Copy link
Contributor Author

By laws you mean we want to have a test proving that traverseCollect works the same as traverse and collect in two operations?

@armanbilge armanbilge changed the title #4276 - Added traverseCollect to TraverseFilter typeclass Add traverseCollect to TraverseFilter typeclass Jul 31, 2022
@armanbilge
Copy link
Member

The law should verify that the actual implementation of the method matches the default implementation—you can reuse the same code. You might find the commentary in #4248 (comment) helpful :) let me know if you have questions!

@armanbilge armanbilge linked an issue Jul 31, 2022 that may be closed by this pull request
@armanbilge armanbilge added this to the 2.9.0 milestone Jul 31, 2022
@emilhotkowski
Copy link
Contributor Author

I added all necessary stuff (I believe so) :)

@emilhotkowski emilhotkowski requested a review from johnynek August 2, 2022 23:18
johnynek
johnynek previously approved these changes Aug 2, 2022
@johnynek
Copy link
Contributor

johnynek commented Aug 2, 2022

thanks for your PR!

@@ -22,6 +22,7 @@
package cats

import cats.data.State
import cats.implicits.toTraverseOps
Copy link
Member

Choose a reason for hiding this comment

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

Can we write this without the ops import? Current practice is try to avoid circular dependencies between syntax and typeclasses.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to do manual sequence for this operation. WDYT?

@@ -69,7 +68,7 @@ trait TraverseFilter[F[_]] extends FunctorFilter[F] {
* res0: List[Option[String]] = List(Some(two), None)
*/
def traverseCollect[G[_], A, B](fa: F[A])(f: PartialFunction[A, G[B]])(implicit G: Applicative[G]): G[F[B]] =
traverseFilter(fa)(a => f.lift(a).sequence)
traverseFilter(fa)(a => f.lift(a).map(G.map(_)(Option(_))).getOrElse(G.pure(None)))
Copy link
Member

Choose a reason for hiding this comment

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

I think sequence was good, we just can't use the syntax :)

Suggested change
traverseFilter(fa)(a => f.lift(a).map(G.map(_)(Option(_))).getOrElse(G.pure(None)))
traverseFilter(fa)(a => Traverse[Option].sequence(f.lift(a)))

Copy link
Contributor

Choose a reason for hiding this comment

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

actually, while we are at it:

val optF = f.lift
traverseFilter(fa)(a => Traverse[Option].sequence(optF(a)))

so we don't reallocate f.lift on every item a.

Copy link
Member

Choose a reason for hiding this comment

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

@emilhotkowski I think Oscar's suggestion still applies :) it's a good optimization.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added!

Co-authored-by: Arman Bilge <armanbilge@gmail.com>
Comment on lines 31 to 32
implicit def toTraverseCollectOps[F[_], G[_], A](fa: F[A]): TraverseCollectOps[F, G, A] =
new TraverseCollectOps(fa)
Copy link
Member

Choose a reason for hiding this comment

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

Should this just be TraverseFilterOps? We can add more ops in the future; it's not specific to traverseCollect I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I followed current implementations like new SequenceFilterOps(fgoa) I don't know what would be the other solution tbh

Copy link
Member

Choose a reason for hiding this comment

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

Ah the situation for SequenceFilter is slighly different, since it only works for F[G[Option[A]]], not F[A]

implicit def toSequenceFilterOps[F[_], G[_], A](fgoa: F[G[Option[A]]]): SequenceFilterOps[F, G, 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.

Do you know where I could find examples to follow? I am not sure what I need to change here.

Copy link
Member

@armanbilge armanbilge Aug 16, 2022

Choose a reason for hiding this comment

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

Sure, check out any of the syntax implementations :)
https://github.com/typelevel/cats/tree/main/core/src/main/scala/cats/syntax

I'm just proposing that we rename TraverseCollectOps to TraverseFilterOps so that it matches the name of the typeclass and not the operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand your point now. I thought it was a remark about implementation details. Thanks for your work on this PR !

Copy link
Member

Choose a reason for hiding this comment

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

Ah, sorry I wasn't so clear on that. Great, thank you too!

…cala

Co-authored-by: Arman Bilge <armanbilge@gmail.com>
@emilhotkowski emilhotkowski dismissed a stale review via 8b7dcb6 August 17, 2022 08:49
Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for this!

@armanbilge armanbilge merged commit 791c65d into typelevel:main Aug 17, 2022
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.

Add traverseCollect to TraverseFilter typeclass
3 participants