Skip to content

Commit c7af0bc

Browse files
authored
Merge pull request #4678 from LaurenceWarne/fix-set-functor-ambiguous-implicits
Fix alleycats Set Functor ambiguous implicits
2 parents 320b22d + 78af8a7 commit c7af0bc

File tree

2 files changed

+36
-21
lines changed
  • alleycats-core/src/main/scala/alleycats/std
  • alleycats-laws/shared/src/test/scala/alleycats/tests

2 files changed

+36
-21
lines changed

alleycats-core/src/main/scala/alleycats/std/set.scala

+31-19
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ object set extends SetInstances
3131

3232
@suppressUnusedImportWarningForScalaVersionSpecific
3333
trait SetInstances {
34+
import SetInstances._
35+
36+
// We use a def instead of val here as a workaround to the MiMa
37+
// 'ReversedMissingMethodProblem' error.
38+
implicit def alleycatsStdInstancesForSet
3439
// Monad advertises parametricity, but Set relies on using
3540
// universal hash codes and equality, which hurts our ability to
3641
// rely on free theorems.
@@ -55,8 +60,31 @@ trait SetInstances {
5560
// If we accept Monad for Set, we can also have Alternative, as
5661
// Alternative only requires MonoidK (already accepted by cats-core) and
5762
// the Applicative that comes from Monad.
58-
implicit val alleyCatsStdSetMonad: Monad[Set] & Alternative[Set] =
59-
new Monad[Set] with Alternative[Set] {
63+
: Monad[Set] & Alternative[Set] & Traverse[Set] & TraverseFilter[Set] =
64+
alleycatsStdInstancesForSet_
65+
66+
@deprecated("Use alleycatsStdInstancesForSet", "2.13.0")
67+
val alleyCatsSetTraverse: Traverse[Set] = alleycatsStdInstancesForSet_
68+
@deprecated("Use alleycatsStdInstancesForSet", "2.13.0")
69+
val alleyCatsStdSetMonad: Monad[Set] & Alternative[Set] = alleycatsStdInstancesForSet_
70+
@deprecated("Use alleycatsStdInstancesForSet", "2.13.0")
71+
val alleyCatsSetTraverseFilter: TraverseFilter[Set] = alleycatsStdInstancesForSet_
72+
}
73+
74+
private[alleycats] object SetInstances {
75+
private val alleycatsStdInstancesForSet_ : Monad[Set] & Alternative[Set] & Traverse[Set] & TraverseFilter[Set] =
76+
new Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] {
77+
78+
// Since iteration order is not guaranteed for sets, folds and other
79+
// traversals may produce different results for input sets which
80+
// appear to be the same.
81+
val traverse: Traverse[Set] = this
82+
83+
def traverseFilter[G[_], A, B](fa: Set[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[Set[B]] =
84+
traverse
85+
.foldRight(fa, Eval.now(G.pure(Set.empty[B])))((x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(o + _)))
86+
.value
87+
6088
def pure[A](a: A): Set[A] = Set(a)
6189
override def map[A, B](fa: Set[A])(f: A => B): Set[B] = fa.map(f)
6290
def flatMap[A, B](fa: Set[A])(f: A => Set[B]): Set[B] = fa.flatMap(f)
@@ -69,7 +97,7 @@ trait SetInstances {
6997
if (fa.isEmpty) Eval.now(Set.empty[Z]) // no need to evaluate fb
7098
else fb.map(fb => map2(fa, fb)(f))
7199

72-
def tailRecM[A, B](a: A)(f: (A) => Set[Either[A, B]]): Set[B] = {
100+
def tailRecM[A, B](a: A)(f: A => Set[Either[A, B]]): Set[B] = {
73101
val bldr = Set.newBuilder[B]
74102

75103
@tailrec def go(set: Set[Either[A, B]]): Unit = {
@@ -98,13 +126,7 @@ trait SetInstances {
98126
override def prependK[A](a: A, fa: Set[A]): Set[A] = fa + a
99127

100128
override def appendK[A](fa: Set[A], a: A): Set[A] = fa + a
101-
}
102129

103-
// Since iteration order is not guaranteed for sets, folds and other
104-
// traversals may produce different results for input sets which
105-
// appear to be the same.
106-
implicit val alleyCatsSetTraverse: Traverse[Set] =
107-
new Traverse[Set] {
108130
def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B =
109131
fa.foldLeft(b)(f)
110132
def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
@@ -172,14 +194,4 @@ trait SetInstances {
172194
override def collectFirstSome[A, B](fa: Set[A])(f: A => Option[B]): Option[B] =
173195
fa.collectFirst(Function.unlift(f))
174196
}
175-
176-
implicit val alleyCatsSetTraverseFilter: TraverseFilter[Set] =
177-
new TraverseFilter[Set] {
178-
val traverse: Traverse[Set] = alleyCatsSetTraverse
179-
180-
def traverseFilter[G[_], A, B](fa: Set[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[Set[B]] =
181-
traverse
182-
.foldRight(fa, Eval.now(G.pure(Set.empty[B])))((x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(o + _)))
183-
.value
184-
}
185197
}

alleycats-laws/shared/src/test/scala/alleycats/tests/SetSuite.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import cats.instances.all._
2828
import cats.kernel.laws.discipline.SerializableTests
2929
import cats.laws.discipline.SemigroupalTests.Isomorphisms
3030
import cats.laws.discipline.arbitrary._
31-
import cats.laws.discipline.{AlternativeTests, ShortCircuitingTests, TraverseFilterTests}
31+
import cats.laws.discipline.{AlternativeTests, FunctorTests, ShortCircuitingTests, TraverseFilterTests}
3232

3333
class SetSuite extends AlleycatsSuite {
34-
implicit val iso: Isomorphisms[Set] = Isomorphisms.invariant[Set](alleyCatsStdSetMonad)
34+
implicit val iso: Isomorphisms[Set] = Isomorphisms.invariant[Set](alleycatsStdInstancesForSet)
3535

3636
checkAll("FlatMapRec[Set]", FlatMapRecTests[Set].tailRecM[Int])
3737

@@ -40,6 +40,9 @@ class SetSuite extends AlleycatsSuite {
4040
checkAll("TraverseFilter[Set]", TraverseFilterTests[Set].traverseFilter[Int, Int, Int])
4141

4242
checkAll("Set[Int]", AlternativeTests[Set].alternative[Int, Int, Int])
43+
44+
checkAll("Functor[Int]", FunctorTests[Set].functor[Int, Int, Int])
45+
4346
checkAll("Alternative[Set]", SerializableTests.serializable(Alternative[Set]))
4447

4548
checkAll("Set[Int]", ShortCircuitingTests[Set].traverseFilter[Int])

0 commit comments

Comments
 (0)