From 66137d80ab22cf2e0661626871ba02fde600445d Mon Sep 17 00:00:00 2001 From: Laurence Warne Date: Tue, 19 Nov 2024 18:55:29 +0000 Subject: [PATCH 1/5] Fix alleycats set functor ambiguous implicits --- .../src/main/scala/alleycats/std/set.scala | 28 +++++++++++-------- .../test/scala/alleycats/tests/SetSuite.scala | 5 +++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala index 9421e69aac..1e5ed6934f 100644 --- a/alleycats-core/src/main/scala/alleycats/std/set.scala +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -30,7 +30,20 @@ import scala.annotation.tailrec object set extends SetInstances @suppressUnusedImportWarningForScalaVersionSpecific -trait SetInstances { +trait SetInstances extends SetInstances0 { + + implicit val alleyCatsSetTraverseFilter: TraverseFilter[Set] = + new TraverseFilter[Set] { + val traverse: Traverse[Set] = alleyCatsSetTraverse + + def traverseFilter[G[_], A, B](fa: Set[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[Set[B]] = + traverse + .foldRight(fa, Eval.now(G.pure(Set.empty[B])))((x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(o + _))) + .value + } +} + +private[std] trait SetInstances0 extends SetInstances1 { // Monad advertises parametricity, but Set relies on using // universal hash codes and equality, which hurts our ability to // rely on free theorems. @@ -99,6 +112,9 @@ trait SetInstances { override def appendK[A](fa: Set[A], a: A): Set[A] = fa + a } +} + +private[std] trait SetInstances1 { // Since iteration order is not guaranteed for sets, folds and other // traversals may produce different results for input sets which @@ -172,14 +188,4 @@ trait SetInstances { override def collectFirstSome[A, B](fa: Set[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f)) } - - implicit val alleyCatsSetTraverseFilter: TraverseFilter[Set] = - new TraverseFilter[Set] { - val traverse: Traverse[Set] = alleyCatsSetTraverse - - def traverseFilter[G[_], A, B](fa: Set[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[Set[B]] = - traverse - .foldRight(fa, Eval.now(G.pure(Set.empty[B])))((x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(o + _))) - .value - } } diff --git a/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala b/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala index c71091bc78..3619111fef 100644 --- a/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala +++ b/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala @@ -28,7 +28,7 @@ import cats.instances.all._ import cats.kernel.laws.discipline.SerializableTests import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{AlternativeTests, ShortCircuitingTests, TraverseFilterTests} +import cats.laws.discipline.{AlternativeTests, FunctorTests, ShortCircuitingTests, TraverseFilterTests} class SetSuite extends AlleycatsSuite { implicit val iso: Isomorphisms[Set] = Isomorphisms.invariant[Set](alleyCatsStdSetMonad) @@ -40,6 +40,9 @@ class SetSuite extends AlleycatsSuite { checkAll("TraverseFilter[Set]", TraverseFilterTests[Set].traverseFilter[Int, Int, Int]) checkAll("Set[Int]", AlternativeTests[Set].alternative[Int, Int, Int]) + + checkAll("Functor[Int]", FunctorTests[Set].functor[Int, Int, Int]) + checkAll("Alternative[Set]", SerializableTests.serializable(Alternative[Set])) checkAll("Set[Int]", ShortCircuitingTests[Set].traverseFilter[Int]) From 8496e12dc9f4b03f119c4111237f87d35c79d020 Mon Sep 17 00:00:00 2001 From: Laurence Warne Date: Tue, 26 Nov 2024 16:48:03 +0000 Subject: [PATCH 2/5] Mark old instances as deprecated to preserve bin compat --- .../src/main/scala/alleycats/std/set.scala | 41 ++++++++----------- .../test/scala/alleycats/tests/SetSuite.scala | 2 +- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala index 1e5ed6934f..a9231f0b3a 100644 --- a/alleycats-core/src/main/scala/alleycats/std/set.scala +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -30,20 +30,14 @@ import scala.annotation.tailrec object set extends SetInstances @suppressUnusedImportWarningForScalaVersionSpecific -trait SetInstances extends SetInstances0 { +trait SetInstances { + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsSetTraverse: Traverse[Set] = alleycatsStdInstancesForSet + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsStdSetMonad: Monad[Set] with Alternative[Set] = alleycatsStdInstancesForSet + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsSetTraverseFilter: TraverseFilter[Set] = alleycatsStdInstancesForSet - implicit val alleyCatsSetTraverseFilter: TraverseFilter[Set] = - new TraverseFilter[Set] { - val traverse: Traverse[Set] = alleyCatsSetTraverse - - def traverseFilter[G[_], A, B](fa: Set[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[Set[B]] = - traverse - .foldRight(fa, Eval.now(G.pure(Set.empty[B])))((x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(o + _))) - .value - } -} - -private[std] trait SetInstances0 extends SetInstances1 { // Monad advertises parametricity, but Set relies on using // universal hash codes and equality, which hurts our ability to // rely on free theorems. @@ -68,8 +62,16 @@ private[std] trait SetInstances0 extends SetInstances1 { // If we accept Monad for Set, we can also have Alternative, as // Alternative only requires MonoidK (already accepted by cats-core) and // the Applicative that comes from Monad. - implicit val alleyCatsStdSetMonad: Monad[Set] with Alternative[Set] = - new Monad[Set] with Alternative[Set] { + implicit def alleycatsStdInstancesForSet + : Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] = + new Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] { + val traverse: Traverse[Set] = this + + def traverseFilter[G[_], A, B](fa: Set[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[Set[B]] = + traverse + .foldRight(fa, Eval.now(G.pure(Set.empty[B])))((x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(o + _))) + .value + def pure[A](a: A): Set[A] = Set(a) override def map[A, B](fa: Set[A])(f: A => B): Set[B] = fa.map(f) def flatMap[A, B](fa: Set[A])(f: A => Set[B]): Set[B] = fa.flatMap(f) @@ -111,16 +113,7 @@ private[std] trait SetInstances0 extends SetInstances1 { override def prependK[A](a: A, fa: Set[A]): Set[A] = fa + a override def appendK[A](fa: Set[A], a: A): Set[A] = fa + a - } -} - -private[std] trait SetInstances1 { - // Since iteration order is not guaranteed for sets, folds and other - // traversals may produce different results for input sets which - // appear to be the same. - implicit val alleyCatsSetTraverse: Traverse[Set] = - new Traverse[Set] { def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = diff --git a/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala b/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala index 3619111fef..8b926b34c9 100644 --- a/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala +++ b/alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala @@ -31,7 +31,7 @@ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{AlternativeTests, FunctorTests, ShortCircuitingTests, TraverseFilterTests} class SetSuite extends AlleycatsSuite { - implicit val iso: Isomorphisms[Set] = Isomorphisms.invariant[Set](alleyCatsStdSetMonad) + implicit val iso: Isomorphisms[Set] = Isomorphisms.invariant[Set](alleycatsStdInstancesForSet) checkAll("FlatMapRec[Set]", FlatMapRecTests[Set].tailRecM[Int]) From 9f70fce414b0e6d20d28ec3c58b3babfc5ea7da8 Mon Sep 17 00:00:00 2001 From: Laurence Warne Date: Thu, 28 Nov 2024 18:08:31 +0000 Subject: [PATCH 3/5] Use val and define deprecated aliases after --- .../src/main/scala/alleycats/std/set.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala index a9231f0b3a..bff022b424 100644 --- a/alleycats-core/src/main/scala/alleycats/std/set.scala +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -31,13 +31,6 @@ object set extends SetInstances @suppressUnusedImportWarningForScalaVersionSpecific trait SetInstances { - @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") - val alleyCatsSetTraverse: Traverse[Set] = alleycatsStdInstancesForSet - @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") - val alleyCatsStdSetMonad: Monad[Set] with Alternative[Set] = alleycatsStdInstancesForSet - @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") - val alleyCatsSetTraverseFilter: TraverseFilter[Set] = alleycatsStdInstancesForSet - // Monad advertises parametricity, but Set relies on using // universal hash codes and equality, which hurts our ability to // rely on free theorems. @@ -62,7 +55,7 @@ trait SetInstances { // If we accept Monad for Set, we can also have Alternative, as // Alternative only requires MonoidK (already accepted by cats-core) and // the Applicative that comes from Monad. - implicit def alleycatsStdInstancesForSet + implicit val alleycatsStdInstancesForSet : Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] = new Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] { val traverse: Traverse[Set] = this @@ -181,4 +174,11 @@ trait SetInstances { override def collectFirstSome[A, B](fa: Set[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f)) } + + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsSetTraverse: Traverse[Set] = alleycatsStdInstancesForSet + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsStdSetMonad: Monad[Set] with Alternative[Set] = alleycatsStdInstancesForSet + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsSetTraverseFilter: TraverseFilter[Set] = alleycatsStdInstancesForSet } From 41d24a863d32d89ff8fafb9fc6b3ddd680ab5e74 Mon Sep 17 00:00:00 2001 From: Sergey Torgashov Date: Tue, 3 Dec 2024 21:19:28 -0800 Subject: [PATCH 4/5] move val to the companion object --- .../src/main/scala/alleycats/std/set.scala | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala index bff022b424..d5ef1b7f7b 100644 --- a/alleycats-core/src/main/scala/alleycats/std/set.scala +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -31,6 +31,8 @@ object set extends SetInstances @suppressUnusedImportWarningForScalaVersionSpecific trait SetInstances { + import SetInstances._ + // Monad advertises parametricity, but Set relies on using // universal hash codes and equality, which hurts our ability to // rely on free theorems. @@ -55,7 +57,20 @@ trait SetInstances { // If we accept Monad for Set, we can also have Alternative, as // Alternative only requires MonoidK (already accepted by cats-core) and // the Applicative that comes from Monad. - implicit val alleycatsStdInstancesForSet + implicit def alleycatsStdInstancesForSet + : Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] = + alleycatsStdInstancesForSet_ + + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsSetTraverse: Traverse[Set] = alleycatsStdInstancesForSet_ + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsStdSetMonad: Monad[Set] with Alternative[Set] = alleycatsStdInstancesForSet_ + @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") + val alleyCatsSetTraverseFilter: TraverseFilter[Set] = alleycatsStdInstancesForSet_ +} + +private[alleycats] object SetInstances { + private val alleycatsStdInstancesForSet_ : Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] = new Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] { val traverse: Traverse[Set] = this @@ -77,7 +92,7 @@ trait SetInstances { if (fa.isEmpty) Eval.now(Set.empty[Z]) // no need to evaluate fb else fb.map(fb => map2(fa, fb)(f)) - def tailRecM[A, B](a: A)(f: (A) => Set[Either[A, B]]): Set[B] = { + def tailRecM[A, B](a: A)(f: A => Set[Either[A, B]]): Set[B] = { val bldr = Set.newBuilder[B] @tailrec def go(set: Set[Either[A, B]]): Unit = { @@ -174,11 +189,4 @@ trait SetInstances { override def collectFirstSome[A, B](fa: Set[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f)) } - - @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") - val alleyCatsSetTraverse: Traverse[Set] = alleycatsStdInstancesForSet - @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") - val alleyCatsStdSetMonad: Monad[Set] with Alternative[Set] = alleycatsStdInstancesForSet - @deprecated("Use alleycatsStdInstancesForSet", "2.13.0") - val alleyCatsSetTraverseFilter: TraverseFilter[Set] = alleycatsStdInstancesForSet } From 336144c0ef8b38b7d3a56ed26714db6a5caf4ef5 Mon Sep 17 00:00:00 2001 From: Laurence Warne Date: Wed, 4 Dec 2024 18:09:27 +0000 Subject: [PATCH 5/5] Add note about def --- alleycats-core/src/main/scala/alleycats/std/set.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala index d5ef1b7f7b..f3bf304734 100644 --- a/alleycats-core/src/main/scala/alleycats/std/set.scala +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -33,6 +33,9 @@ object set extends SetInstances trait SetInstances { import SetInstances._ + // We use a def instead of val here as a workaround to the MiMa + // 'ReversedMissingMethodProblem' error. + implicit def alleycatsStdInstancesForSet // Monad advertises parametricity, but Set relies on using // universal hash codes and equality, which hurts our ability to // rely on free theorems. @@ -57,7 +60,6 @@ trait SetInstances { // If we accept Monad for Set, we can also have Alternative, as // Alternative only requires MonoidK (already accepted by cats-core) and // the Applicative that comes from Monad. - implicit def alleycatsStdInstancesForSet : Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] = alleycatsStdInstancesForSet_