diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala index 4860d1052e..3f763cd87a 100644 --- a/alleycats-core/src/main/scala/alleycats/std/set.scala +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -2,7 +2,7 @@ package alleycats package std import alleycats.compat.scalaVersionSpecific._ -import cats.{Always, Applicative, Eval, Foldable, Monad, Monoid, Traverse, TraverseFilter} +import cats.{Alternative, Always, Applicative, Eval, Foldable, Monad, Monoid, Traverse, TraverseFilter} import scala.annotation.tailrec @@ -10,7 +10,7 @@ object set extends SetInstances @suppressUnusedImportWarningForScalaVersionSpecific trait SetInstances { - // This method advertises parametricity, but relies on using + // Monad advertises parametricity, but Set relies on using // universal hash codes and equality, which hurts our ability to // rely on free theorems. // @@ -30,8 +30,12 @@ trait SetInstances { // contain three. Since `g` is not a function (speaking strictly) // this would not be considered a law violation, but it still makes // people uncomfortable. - implicit val alleyCatsStdSetMonad: Monad[Set] = - new Monad[Set] { + // + // 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] { 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) @@ -65,6 +69,10 @@ trait SetInstances { go(f(a)) bldr.result() } + + override def empty[A]: Set[A] = Set.empty + + override def combineK[A](x: Set[A], y: Set[A]): Set[A] = x | y } // Since iteration order is not guaranteed for sets, folds and other diff --git a/alleycats-tests/shared/src/test/scala/alleycats/tests/SetSuite.scala b/alleycats-tests/shared/src/test/scala/alleycats/tests/SetSuite.scala index 9616677683..07a7c9e49c 100644 --- a/alleycats-tests/shared/src/test/scala/alleycats/tests/SetSuite.scala +++ b/alleycats-tests/shared/src/test/scala/alleycats/tests/SetSuite.scala @@ -2,18 +2,24 @@ package alleycats.tests import alleycats.laws.discipline._ import alleycats.std.all._ -import cats.Foldable +import cats.{Alternative, Foldable} 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.{ShortCircuitingTests, TraverseFilterTests} +import cats.laws.discipline.{AlternativeTests, ShortCircuitingTests, TraverseFilterTests} class SetSuite extends AlleycatsSuite { + implicit val iso: Isomorphisms[Set] = Isomorphisms.invariant[Set](alleyCatsStdSetMonad) + checkAll("FlatMapRec[Set]", FlatMapRecTests[Set].tailRecM[Int]) checkAll("Foldable[Set]", SerializableTests.serializable(Foldable[Set])) checkAll("TraverseFilter[Set]", TraverseFilterTests[Set].traverseFilter[Int, Int, Int]) + checkAll("Set[Int]", AlternativeTests[Set].alternative[Int, Int, Int]) + checkAll("Alternative[Set]", SerializableTests.serializable(Alternative[Set])) + checkAll("Set[Int]", ShortCircuitingTests[Set].traverseFilter[Int]) }