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

Backport #3103 traverseFilter Queue instance to scala_2.11 #3292

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/scala/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ trait AllInstancesBinCompat7
with VectorInstancesBinCompat1
with EitherInstancesBinCompat0
with StreamInstancesBinCompat1
with QueueInstancesBinCompat0
29 changes: 29 additions & 0 deletions core/src/main/scala/cats/instances/queue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cats
package instances

import cats.syntax.show._

import scala.annotation.tailrec
import scala.collection.immutable.Queue
import scala.util.Try
Expand Down Expand Up @@ -153,3 +154,31 @@ trait QueueInstances extends cats.kernel.instances.QueueInstances {
fa.iterator.map(_.show).mkString("Queue(", ", ", ")")
}
}

private[instances] trait QueueInstancesBinCompat0 {
implicit val catsStdTraverseFilterForQueue: TraverseFilter[Queue] = new TraverseFilter[Queue] {
val traverse: Traverse[Queue] = cats.instances.queue.catsStdInstancesForQueue

override def mapFilter[A, B](fa: Queue[A])(f: (A) => Option[B]): Queue[B] =
fa.collect(Function.unlift(f))

override def filter[A](fa: Queue[A])(f: (A) => Boolean): Queue[A] = fa.filter(f)

override def collect[A, B](fa: Queue[A])(f: PartialFunction[A, B]): Queue[B] = fa.collect(f)

override def flattenOption[A](fa: Queue[Option[A]]): Queue[A] = fa.flatten

def traverseFilter[G[_], A, B](fa: Queue[A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[Queue[B]] =
fa.foldRight(Eval.now(G.pure(Queue.empty[B])))(
(x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o))
)
.value
Copy link
Contributor Author

@gagandeepkalra gagandeepkalra Feb 11, 2020

Choose a reason for hiding this comment

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

We can have an implementation without using Eval, might even perform a little better (for a bigger queue); without all those extra Eval wrappers.

fa.foldRight(G.pure(Queue.empty[B]))( (x, xse) => G.map2(f(x), xse)((i, o) => i.fold(o)(_ +: o)))

Copy link
Contributor

Choose a reason for hiding this comment

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

That won't short-circuit, though, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@travisbrown yes agreed it won't, but for the same reason the present implementation wouldn't.

If by short circuit you mean f wouldn't have to run on the remaining Queue elements (given we are folding from the right).

This is my understanding and derived reasoning-
map2Eval short-circuits: given the underlying G structure allows it, then the Eval argument skips running.

In this case however, the Eval description is computed by running f on each element of the Queue already. The first argument to map2Eval is not Lazy. In every situation f will run on each element.

Please correct me If I understood this wrongly.

Copy link
Contributor Author

@gagandeepkalra gagandeepkalra Feb 23, 2020

Choose a reason for hiding this comment

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

I see it now, we are using the wrong foldRight here, queue.foldRight ends up with a while loop (standard library). This should have been-

traverse.foldRight[A, G[Queue[B]]](fa, Always(G.pure(Queue.empty))) { (a, lglb) => G.map2Eval(f(a), lglb)((i, o) => i.fold(o)(_ +: o)) } .value

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 see the same problem with other traverseFilter implementations, should I open a new issue?


override def filterA[G[_], A](fa: Queue[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[Queue[A]] =
fa.foldRight(Eval.now(G.pure(Queue.empty[A])))(
(x, xse) => G.map2Eval(f(x), xse)((b, vec) => if (b) x +: vec else vec)
)
.value
}

}
5 changes: 4 additions & 1 deletion tests/src/test/scala/cats/tests/QueueSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package cats
package tests

import scala.collection.immutable.Queue

import cats.laws.discipline.{
AlternativeTests,
CoflatMapTests,
MonadTests,
SemigroupalTests,
SerializableTests,
TraverseFilterTests,
TraverseTests
}

Expand All @@ -28,6 +28,9 @@ class QueueSuite extends CatsSuite {
checkAll("Queue[Int] with Option", TraverseTests[Queue].traverse[Int, Int, Int, Set[Int], Option, Option])
checkAll("Traverse[Queue]", SerializableTests.serializable(Traverse[Queue]))

checkAll("Queue[Int]", TraverseFilterTests[Queue].traverseFilter[Int, Int, Int])
checkAll("TraverseFilter[Queue]", SerializableTests.serializable(TraverseFilter[Queue]))

test("show") {
Queue(1, 2, 3).show should ===("Queue(1, 2, 3)")
Queue.empty[Int].show should ===("Queue()")
Expand Down