Skip to content

Commit

Permalink
Merge pull request #4678 from LaurenceWarne/fix-set-functor-ambiguous…
Browse files Browse the repository at this point in the history
…-implicits

Fix alleycats Set Functor ambiguous implicits
  • Loading branch information
satorg authored Dec 23, 2024
2 parents 320b22d + 78af8a7 commit c7af0bc
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 21 deletions.
50 changes: 31 additions & 19 deletions alleycats-core/src/main/scala/alleycats/std/set.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ object set extends SetInstances

@suppressUnusedImportWarningForScalaVersionSpecific
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.
Expand All @@ -55,8 +60,31 @@ 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 alleyCatsStdSetMonad: Monad[Set] & Alternative[Set] =
new Monad[Set] with Alternative[Set] {
: Monad[Set] & Alternative[Set] & Traverse[Set] & TraverseFilter[Set] =
alleycatsStdInstancesForSet_

@deprecated("Use alleycatsStdInstancesForSet", "2.13.0")
val alleyCatsSetTraverse: Traverse[Set] = alleycatsStdInstancesForSet_
@deprecated("Use alleycatsStdInstancesForSet", "2.13.0")
val alleyCatsStdSetMonad: Monad[Set] & Alternative[Set] = alleycatsStdInstancesForSet_
@deprecated("Use alleycatsStdInstancesForSet", "2.13.0")
val alleyCatsSetTraverseFilter: TraverseFilter[Set] = alleycatsStdInstancesForSet_
}

private[alleycats] object SetInstances {
private val alleycatsStdInstancesForSet_ : Monad[Set] & Alternative[Set] & Traverse[Set] & TraverseFilter[Set] =
new Monad[Set] with Alternative[Set] with Traverse[Set] with TraverseFilter[Set] {

// 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.
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)
Expand All @@ -69,7 +97,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 = {
Expand Down Expand Up @@ -98,13 +126,7 @@ trait SetInstances {
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
}

// 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] =
Expand Down Expand Up @@ -172,14 +194,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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ 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)
implicit val iso: Isomorphisms[Set] = Isomorphisms.invariant[Set](alleycatsStdInstancesForSet)

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

Expand All @@ -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])
Expand Down

0 comments on commit c7af0bc

Please sign in to comment.