Skip to content

Add traverseCollect to TraverseFilter typeclass - #4277

Merged
armanbilge merged 16 commits into
typelevel:mainfrom
emilhotkowski:4276-added-traverse-collect
Aug 17, 2022
Merged

Add traverseCollect to TraverseFilter typeclass#4277
armanbilge merged 16 commits into
typelevel:mainfrom
emilhotkowski:4276-added-traverse-collect

Conversation

@emilhotkowski

Copy link
Copy Markdown
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.

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

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)?

Comment thread core/src/main/scala/cats/TraverseFilter.scala Outdated
Comment thread core/src/main/scala/cats/TraverseFilter.scala
@emilhotkowski

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

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

Comment thread laws/src/main/scala/cats/laws/TraverseFilterLaws.scala Outdated
Comment thread laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala Outdated
Comment thread laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala Outdated
@emilhotkowski
emilhotkowski requested a review from johnynek August 2, 2022 23:18
johnynek
johnynek previously approved these changes Aug 2, 2022
@johnynek

johnynek commented Aug 2, 2022

Copy link
Copy Markdown
Contributor

thanks for your PR!

package cats

import cats.data.State
import cats.implicits.toTraverseOps

Copy link
Copy Markdown
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
Copy Markdown
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?

*/
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

@armanbilge armanbilge Aug 16, 2022

Copy link
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
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!

Comment thread laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala Outdated
…cala

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

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

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