From 61f13d08e58c6b090283f33692a6316fdc6a35c8 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 8 Jan 2018 18:07:21 +0100 Subject: [PATCH 01/12] Add NonEmptySet --- .../main/scala/cats/data/NonEmptySet.scala | 218 +++++++++++++++++ .../cats/laws/discipline/Arbitrary.scala | 7 + .../scala/cats/tests/NonEmptySetSuite.scala | 225 ++++++++++++++++++ 3 files changed, 450 insertions(+) create mode 100644 core/src/main/scala/cats/data/NonEmptySet.scala create mode 100644 tests/src/test/scala/cats/tests/NonEmptySetSuite.scala diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala new file mode 100644 index 0000000000..5aa00a520d --- /dev/null +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2018 Luka Jacobowitz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats +package data + +import cats.instances.sortedSet._ +import cats.kernel._ +import cats.{Always, Eq, Eval, Foldable, Later, Now, Reducible, SemigroupK, Show} + +import scala.collection.immutable._ + +final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { + + private implicit def ordering: Ordering[A] = set.ordering + private implicit def order: Order[A] = Order.fromOrdering + + def +(a: A): NonEmptySet[A] = new NonEmptySet(set + a) + def ++(as: NonEmptySet[A]): NonEmptySet[A] = concat(as) + + def concat(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set ++ as.set) + + def -(a: A): SortedSet[A] = set - a + def map[B: Order](f: A ⇒ B): NonEmptySet[B] = + new NonEmptySet(SortedSet(set.map(f).to: _*)(Order[B].toOrdering)) + + def toNonEmptyList: NonEmptyList[A] = NonEmptyList.fromListUnsafe(set.toList) + + def head: A = set.head + def tail: SortedSet[A] = set.tail + def last: A = set.last + + def apply(a: A): Boolean = set(a) + def contains(a: A): Boolean = set.contains(a) + + + def union(as: NonEmptySet[A]): NonEmptySet[Ag] = new NonEmptySet(set.union(as.toSet)) + def size: Int = set.size + def forall(p: A ⇒ Boolean): Boolean = set.forall(p) + def exists(f: A ⇒ Boolean): Boolean = set.exists(f) + def find(f: A ⇒ Boolean): Option[A] = set.find(f) + def collect[B](pf: PartialFunction[A, B]): Set[B] = set.collect(pf) + def filter(p: A ⇒ Boolean): SortedSet[A] = set.filter(p) + def filterNot(p: A ⇒ Boolean): SortedSet[A] = filter(t => !p(t)) + + + /** + * Left-associative fold using f. + */ + def foldLeft[B](b: B)(f: (B, A) => B): B = + set.foldLeft(b)(f) + + /** + * Right-associative fold using f. + */ + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable[SortedSet].foldRight(set, lb)(f) + + /** + * Left-associative reduce using f. + */ + def reduceLeft(f: (A, A) => A): A = + set.reduceLeft(f) + + def reduceLeftTo[B](f: A => B)(g: (B, A) => B): B = { + tail.foldLeft(f(head))((b, a) => g(b, a)) + } + + /** + * Left-associative reduce using f. + */ + def reduceRight(f: (A, Eval[A]) => Eval[A]): Eval[A] = + reduceRightTo(identity)(f) + + def reduceRightTo[B](f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + Always((head, tail)).flatMap { case (a, ga) => + Foldable[SortedSet].reduceRightToOption(ga)(f)(g).flatMap { + case Some(b) => g(a, Now(b)) + case None => Later(f(a)) + } + } + + /** + * Reduce using the Semigroup of A + */ + def reduce[AA >: A](implicit S: Semigroup[AA]): AA = + S.combineAllOption(set).get + + def toSet: SortedSet[A] = set + + /** + * Typesafe stringification method. + * + * This method is similar to .toString except that it stringifies + * values according to Show[_] instances, rather than using the + * universal .toString method. + */ + def show(implicit A: Show[A]): String = + s"NonEmpty${Show[SortedSet[A]].show(set)}" + + /** + * Typesafe equality operator. + * + * This method is similar to == except that it only allows two + * NonEmptySet[A] values to be compared to each other, and uses + * equality provided by Eq[_] instances, rather than using the + * universal equality provided by .equals. + */ + def ===(that: NonEmptySet[A]): Boolean = + Eq[SortedSet[A]].eqv(set, that.toSet) + + def length: Int = size + + /** + * Zips this `NonEmptySet` with another `NonEmptySet` and applies a function for each pair of elements. + * + * {{{ + * scala> import cats.data.NonEmptySet + * scala> val as = NonEmptySet.of(1, 2, 3) + * scala> val bs = NonEmptySet.of("A", "B", "C") + * scala> as.zipWith(bs)(_ + _) + * res0: cats.data.NonEmptySet[String] = NonEmptySet(1A, 2B, 3C) + * }}} + */ + def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] = + new NonEmptySet(SortedSet((set, b.toSet).zipped.map(f).to: _*)(Order[C].toOrdering)) + + def zipWithIndex: NonEmptySet[(A, Int)] = + new NonEmptySet(set.zipWithIndex) +} + +private[data] sealed abstract class NonEmptySetInstances { + implicit val catsDataInstancesForNonEmptySet: SemigroupK[NonEmptySet] with Reducible[NonEmptySet] = + new SemigroupK[NonEmptySet] with Reducible[NonEmptySet] { + + def combineK[A](a: NonEmptySet[A], b: NonEmptySet[A]): NonEmptySet[A] = + a ++ b + + override def size[A](fa: NonEmptySet[A]): Long = fa.length.toLong + + override def reduceLeft[A](fa: NonEmptySet[A])(f: (A, A) => A): A = + fa.reduceLeft(f) + + override def reduce[A](fa: NonEmptySet[A])(implicit A: Semigroup[A]): A = + fa.reduce + + def reduceLeftTo[A, B](fa: NonEmptySet[A])(f: A => B)(g: (B, A) => B): B = fa.reduceLeftTo(f)(g) + + def reduceRightTo[A, B](fa: NonEmptySet[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.reduceRightTo(f)(g) + + override def foldLeft[A, B](fa: NonEmptySet[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + override def foldRight[A, B](fa: NonEmptySet[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) + + override def foldMap[A, B](fa: NonEmptySet[A])(f: A => B)(implicit B: Monoid[B]): B = + B.combineAll(fa.toSet.iterator.map(f)) + + override def fold[A](fa: NonEmptySet[A])(implicit A: Monoid[A]): A = + fa.reduce + + override def find[A](fa: NonEmptySet[A])(f: A => Boolean): Option[A] = + fa.find(f) + + override def forall[A](fa: NonEmptySet[A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def exists[A](fa: NonEmptySet[A])(p: A => Boolean): Boolean = + fa.exists(p) + + override def toList[A](fa: NonEmptySet[A]): List[A] = fa.toSet.toList + + override def toNonEmptyList[A](fa: NonEmptySet[A]): NonEmptyList[A] = + NonEmptyList(fa.head, fa.tail.toList) + } + + implicit def catsDataEqForNonEmptySet[A: Order]: Eq[NonEmptySet[A]] = + new Eq[NonEmptySet[A]]{ + def eqv(x: NonEmptySet[A], y: NonEmptySet[A]): Boolean = x === y + } + + implicit def catsDataShowForNonEmptySet[A](implicit A: Show[A]): Show[NonEmptySet[A]] = + Show.show[NonEmptySet[A]](_.show) + + implicit def catsDataBandForNonEmptySet[A]: Band[NonEmptySet[A]] = new Band[NonEmptySet[A]] { + def combine(x: NonEmptySet[A], y: NonEmptySet[A]): NonEmptySet[A] = x ++ y + } +} + +object NonEmptySet extends NonEmptySetInstances { + def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] = + if (as.nonEmpty) Option(new NonEmptySet(as)) else None + + def fromSetUnsafe[A: Order](set: SortedSet[A]): NonEmptySet[A] = + if (set.nonEmpty) new NonEmptySet(set) + else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set") + + + def of[A: Order](a: A, as: A*): NonEmptySet[A] = + new NonEmptySet(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a) + def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = new NonEmptySet(SortedSet(head)(Order[A].toOrdering) ++ tail) + def one[A: Order](a: A): NonEmptySet[A] = new NonEmptySet(SortedSet(a)(Order[A].toOrdering)) +} diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 54f2238353..7b1775e355 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -51,6 +51,13 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsCogenForNonEmptyVector[A](implicit A: Cogen[A]): Cogen[NonEmptyVector[A]] = Cogen[Vector[A]].contramap(_.toVector) + implicit def catsLawsArbitraryForNonEmptySet[A: Order](implicit A: Arbitrary[A]): Arbitrary[NonEmptySet[A]] = + Arbitrary(implicitly[Arbitrary[SortedSet[A]]].arbitrary.flatMap(fa => + A.arbitrary.map(a => NonEmptySet(a, fa)))) + + implicit def catsLawsCogenForNonEmptySet[A: Order: Cogen]: Cogen[NonEmptySet[A]] = + Cogen[SortedSet[A]].contramap(_.toSet) + implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] = Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v))) diff --git a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala new file mode 100644 index 0000000000..e6b4a34c12 --- /dev/null +++ b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2018 Luka Jacobowitz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats +package tests + +import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ +import cats.data.NonEmptySet +import cats.kernel.laws.discipline.{BandTests, EqTests} + +import scala.collection.immutable.SortedSet + +class NonEmptySetSuite extends CatsSuite { + + checkAll("NonEmptySet[Int]", SemigroupKTests[NonEmptySet].semigroupK[Int]) + checkAll("NonEmptySet[Int]", ReducibleTests[NonEmptySet].reducible[Option, Int, Int]) + checkAll("NonEmptySet[String]", BandTests[NonEmptySet[String]].band) + checkAll("NonEmptySet[String]", EqTests[NonEmptySet[String]].eqv) + + + test("Show is not empty and is formatted as expected") { + forAll { (nes: NonEmptySet[Int]) => + nes.show.nonEmpty should === (true) + nes.show.startsWith("NonEmptySortedSet(") should === (true) + nes.show should === (implicitly[Show[NonEmptySet[Int]]].show(nes)) + nes.show.contains(nes.head.show) should === (true) + } + } + + test("Show is formatted correctly") { + val nonEmptySet = NonEmptySet("Test", SortedSet.empty[String]) + nonEmptySet.show should === ("NonEmptySortedSet(Test)") + } + + test("Creating NonEmptySet + toSet is identity") { + forAll { (i: Int, tail: SortedSet[Int]) => + val set = tail + i + val nonEmptySet = NonEmptySet(i, tail) + set should === (nonEmptySet.toSet) + } + } + + test("NonEmptySet#filter is consistent with Set#filter") { + forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => + val set = nes.toSet + nes.filter(p) should === (set.filter(p)) + } + } + + test("NonEmptySet#filterNot is consistent with Set#filterNot") { + forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => + val set = nes.toSet + nes.filterNot(p) should === (set.filterNot(p)) + } + } + + test("NonEmptySet#collect is consistent with Set#collect") { + forAll { (nes: NonEmptySet[Int], pf: PartialFunction[Int, String]) => + val set = nes.toSet + nes.collect(pf) should === (set.collect(pf)) + } + } + + test("NonEmptySet#find is consistent with Set#find") { + forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => + val set = nes.toSet + nes.find(p) should === (set.find(p)) + } + } + + test("NonEmptySet#exists is consistent with Set#exists") { + forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => + val set = nes.toSet + nes.exists(p) should === (set.exists(p)) + } + } + + test("NonEmptySet#forall is consistent with Set#forall") { + forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => + val set = nes.toSet + nes.forall(p) should === (set.forall(p)) + } + } + + test("NonEmptySet#map is consistent with Set#map") { + forAll { (nes: NonEmptySet[Int], p: Int => String) => + val set = nes.toSet + nes.map(p).toSet should === (set.map(p)) + } + } + + test("reduceLeft consistent with foldLeft") { + forAll { (nes: NonEmptySet[Int], f: (Int, Int) => Int) => + nes.reduceLeft(f) should === (nes.tail.foldLeft(nes.head)(f)) + } + } + + test("reduceRight consistent with foldRight") { + forAll { (nes: NonEmptySet[Int], f: (Int, Eval[Int]) => Eval[Int]) => + val got = nes.reduceRight(f).value + val last = nes.last + val rev = nes - last + val expected = rev.foldRight(last)((a, b) => f(a, Now(b)).value) + got should === (expected) + } + } + + test("reduce consistent with fold") { + forAll { (nes: NonEmptySet[Int]) => + nes.reduce should === (nes.fold) + } + } + + + test("reduce consistent with reduceK") { + forAll { (nes: NonEmptySet[Option[Int]]) => + nes.reduce(SemigroupK[Option].algebra[Int]) should === (nes.reduceK) + } + } + + test("reduceLeftToOption consistent with foldLeft + Option") { + forAll { (nes: NonEmptySet[Int], f: Int => String, g: (String, Int) => String) => + val expected = nes.tail.foldLeft(Option(f(nes.head))) { (opt, i) => + opt.map(s => g(s, i)) + } + nes.reduceLeftToOption(f)(g) should === (expected) + } + } + + test("reduceRightToOption consistent with foldRight + Option") { + forAll { (nes: NonEmptySet[Int], f: Int => String, g: (Int, Eval[String]) => Eval[String]) => + val got = nes.reduceRightToOption(f)(g).value + val last = nes.last + val rev = nes - last + val expected = rev.foldRight(Option(f(last))) { (i, opt) => + opt.map(s => g(i, Now(s)).value) + } + got should === (expected) + } + } + + test("reduceLeftM consistent with foldM") { + forAll { (nes: NonEmptySet[Int], f: Int => Option[Int]) => + val got = nes.reduceLeftM(f)((acc, i) => f(i).map(acc + _)) + val expected = f(nes.head).flatMap { hd => + nes.tail.foldM(hd)((acc, i) => f(i).map(acc + _)) + } + got should === (expected) + } + } + + test("reduceMapM consistent with foldMapM") { + forAll { (nes: NonEmptySet[Int], f: Int => Option[Int]) => + nes.reduceMapM(f) should === (nes.foldMapM(f)) + } + } + + test("fromSet round trip") { + forAll { l: SortedSet[Int] => + NonEmptySet.fromSet(l).map(_.toSet).getOrElse(SortedSet.empty[Int]) should === (l) + } + + forAll { nes: NonEmptySet[Int] => + NonEmptySet.fromSet(nes.toSet) should === (Some(nes)) + } + } + + test("fromSetUnsafe/fromSet consistency") { + forAll { nes: NonEmptySet[Int] => + NonEmptySet.fromSet(nes.toSet) should === (Some(NonEmptySet.fromSetUnsafe(nes.toSet))) + } + } + + test("fromSetUnsafe empty set") { + val _ = intercept[IllegalArgumentException] { + NonEmptySet.fromSetUnsafe(SortedSet.empty[Int]) + } + } + + test("+ consistent with Set") { + forAll { (nes: NonEmptySet[Int], i: Int) => + (nes + i).toSet should === (nes.toSet + i) + } + } + + test("NonEmptySet#zipWithIndex is consistent with Set#zipWithIndex") { + forAll { nes: NonEmptySet[Int] => + nes.zipWithIndex.toSet should === (nes.toSet.zipWithIndex) + } + } + + test("NonEmptySet#size and length is consistent with Set#size") { + forAll { nes: NonEmptySet[Int] => + nes.size should === (nes.toSet.size) + nes.length should === (nes.toSet.size) + } + } + + test("NonEmptySet#concat is consistent with Set#++") { + forAll { (nes: NonEmptySet[Int], l: SortedSet[Int], n: Int) => + nes.concat(NonEmptySet(n, l)).toSet should === (nes.toSet ++ (l + n)) + } + } + + test("NonEmptySet#zipWith is consistent with Set#zip and then Set#map") { + forAll { (a: NonEmptySet[Int], b: NonEmptySet[Int], f: (Int, Int) => Int) => + a.zipWith(b)(f).toSet should ===(a.toSet.zip(b.toSet).map { case (x, y) => f(x, y) }) + } + } + +} From 5193dd1ca4c918b76cbde32d62cfd7172d2f8a52 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 8 Jan 2018 18:43:40 +0100 Subject: [PATCH 02/12] Make NonEmptySet a semilattice and add extra test --- core/src/main/scala/cats/data/NonEmptySet.scala | 4 ++-- tests/src/test/scala/cats/tests/NonEmptySetSuite.scala | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 5aa00a520d..6329c08b3b 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -47,7 +47,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { def contains(a: A): Boolean = set.contains(a) - def union(as: NonEmptySet[A]): NonEmptySet[Ag] = new NonEmptySet(set.union(as.toSet)) + def union(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set.union(as.toSet)) def size: Int = set.size def forall(p: A ⇒ Boolean): Boolean = set.forall(p) def exists(f: A ⇒ Boolean): Boolean = set.exists(f) @@ -197,7 +197,7 @@ private[data] sealed abstract class NonEmptySetInstances { implicit def catsDataShowForNonEmptySet[A](implicit A: Show[A]): Show[NonEmptySet[A]] = Show.show[NonEmptySet[A]](_.show) - implicit def catsDataBandForNonEmptySet[A]: Band[NonEmptySet[A]] = new Band[NonEmptySet[A]] { + implicit def catsDataSemilatticeForNonEmptySet[A]: Semilattice[NonEmptySet[A]] = new Semilattice[NonEmptySet[A]] { def combine(x: NonEmptySet[A], y: NonEmptySet[A]): NonEmptySet[A] = x ++ y } } diff --git a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala index e6b4a34c12..eb44463ac0 100644 --- a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala @@ -20,7 +20,7 @@ package tests import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.data.NonEmptySet -import cats.kernel.laws.discipline.{BandTests, EqTests} +import cats.kernel.laws.discipline.{SemilatticeTests, EqTests} import scala.collection.immutable.SortedSet @@ -28,9 +28,15 @@ class NonEmptySetSuite extends CatsSuite { checkAll("NonEmptySet[Int]", SemigroupKTests[NonEmptySet].semigroupK[Int]) checkAll("NonEmptySet[Int]", ReducibleTests[NonEmptySet].reducible[Option, Int, Int]) - checkAll("NonEmptySet[String]", BandTests[NonEmptySet[String]].band) + checkAll("NonEmptySet[String]", SemilatticeTests[NonEmptySet[String]].band) checkAll("NonEmptySet[String]", EqTests[NonEmptySet[String]].eqv) + test("First element is always the smallest") { + forAll { (nes: NonEmptySet[Int]) => + nes.forall { v => Order[Int].lteqv(nes.head, v) } should === (true) + + } + } test("Show is not empty and is formatted as expected") { forAll { (nes: NonEmptySet[Int]) => From 8104b40f594df489a1a6a51a596d763c8d8d7469 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 8 Jan 2018 19:49:14 +0100 Subject: [PATCH 03/12] Fix doctest --- core/src/main/scala/cats/data/NonEmptySet.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 6329c08b3b..1d3be43d7b 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -124,15 +124,18 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { def length: Int = size + override def toString: String = s"NonEmpty${set.toString}" + /** * Zips this `NonEmptySet` with another `NonEmptySet` and applies a function for each pair of elements. * * {{{ * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ * scala> val as = NonEmptySet.of(1, 2, 3) * scala> val bs = NonEmptySet.of("A", "B", "C") * scala> as.zipWith(bs)(_ + _) - * res0: cats.data.NonEmptySet[String] = NonEmptySet(1A, 2B, 3C) + * res0: cats.data.NonEmptySet[String] = NonEmptyTreeSet(1A, 2B, 3C) * }}} */ def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] = From 34aa71d0823ede2fd8ef7e0c159a95e7e51e70d5 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 8 Jan 2018 21:19:14 +0100 Subject: [PATCH 04/12] Remove AnyVal and rename to toSortedSet --- .../main/scala/cats/data/NonEmptySet.scala | 28 ++++++++------- .../cats/laws/discipline/Arbitrary.scala | 2 +- .../scala/cats/tests/NonEmptySetSuite.scala | 36 +++++++++---------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 1d3be43d7b..1409563b2d 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -23,15 +23,18 @@ import cats.{Always, Eq, Eval, Foldable, Later, Now, Reducible, SemigroupK, Show import scala.collection.immutable._ -final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { +final class NonEmptySet[A] private (val set: SortedSet[A]) { private implicit def ordering: Ordering[A] = set.ordering private implicit def order: Order[A] = Order.fromOrdering def +(a: A): NonEmptySet[A] = new NonEmptySet(set + a) - def ++(as: NonEmptySet[A]): NonEmptySet[A] = concat(as) + def ++(as: NonEmptySet[A]): NonEmptySet[A] = union(as) + def | (as: NonEmptySet[A]): NonEmptySet[A] = union(as) + def --(as: NonEmptySet[A]): SortedSet[A] = diff(as) + def &~(as: NonEmptySet[A]): SortedSet[A] = diff(as) + def &(as: NonEmptySet[A]): SortedSet[A] = intersect(as) - def concat(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set ++ as.set) def -(a: A): SortedSet[A] = set - a def map[B: Order](f: A ⇒ B): NonEmptySet[B] = @@ -46,8 +49,9 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { def apply(a: A): Boolean = set(a) def contains(a: A): Boolean = set.contains(a) - - def union(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set.union(as.toSet)) + def diff(as: NonEmptySet[A]): SortedSet[A] = set -- as.set + def union(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set ++ as.set) + def intersect(as: NonEmptySet[A]): SortedSet[A] = set.filter(as.apply) def size: Int = set.size def forall(p: A ⇒ Boolean): Boolean = set.forall(p) def exists(f: A ⇒ Boolean): Boolean = set.exists(f) @@ -99,7 +103,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { def reduce[AA >: A](implicit S: Semigroup[AA]): AA = S.combineAllOption(set).get - def toSet: SortedSet[A] = set + def toSortedSet: SortedSet[A] = set /** * Typesafe stringification method. @@ -120,7 +124,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { * universal equality provided by .equals. */ def ===(that: NonEmptySet[A]): Boolean = - Eq[SortedSet[A]].eqv(set, that.toSet) + Eq[SortedSet[A]].eqv(set, that.toSortedSet) def length: Int = size @@ -139,7 +143,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { * }}} */ def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] = - new NonEmptySet(SortedSet((set, b.toSet).zipped.map(f).to: _*)(Order[C].toOrdering)) + new NonEmptySet(SortedSet((set, b.toSortedSet).zipped.map(f).to: _*)(Order[C].toOrdering)) def zipWithIndex: NonEmptySet[(A, Int)] = new NonEmptySet(set.zipWithIndex) @@ -150,7 +154,7 @@ private[data] sealed abstract class NonEmptySetInstances { new SemigroupK[NonEmptySet] with Reducible[NonEmptySet] { def combineK[A](a: NonEmptySet[A], b: NonEmptySet[A]): NonEmptySet[A] = - a ++ b + a | b override def size[A](fa: NonEmptySet[A]): Long = fa.length.toLong @@ -172,7 +176,7 @@ private[data] sealed abstract class NonEmptySetInstances { fa.foldRight(lb)(f) override def foldMap[A, B](fa: NonEmptySet[A])(f: A => B)(implicit B: Monoid[B]): B = - B.combineAll(fa.toSet.iterator.map(f)) + B.combineAll(fa.toSortedSet.iterator.map(f)) override def fold[A](fa: NonEmptySet[A])(implicit A: Monoid[A]): A = fa.reduce @@ -186,7 +190,7 @@ private[data] sealed abstract class NonEmptySetInstances { override def exists[A](fa: NonEmptySet[A])(p: A => Boolean): Boolean = fa.exists(p) - override def toList[A](fa: NonEmptySet[A]): List[A] = fa.toSet.toList + override def toList[A](fa: NonEmptySet[A]): List[A] = fa.toSortedSet.toList override def toNonEmptyList[A](fa: NonEmptySet[A]): NonEmptyList[A] = NonEmptyList(fa.head, fa.tail.toList) @@ -201,7 +205,7 @@ private[data] sealed abstract class NonEmptySetInstances { Show.show[NonEmptySet[A]](_.show) implicit def catsDataSemilatticeForNonEmptySet[A]: Semilattice[NonEmptySet[A]] = new Semilattice[NonEmptySet[A]] { - def combine(x: NonEmptySet[A], y: NonEmptySet[A]): NonEmptySet[A] = x ++ y + def combine(x: NonEmptySet[A], y: NonEmptySet[A]): NonEmptySet[A] = x | y } } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7b1775e355..0e9c7bf3df 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -56,7 +56,7 @@ object arbitrary extends ArbitraryInstances0 { A.arbitrary.map(a => NonEmptySet(a, fa)))) implicit def catsLawsCogenForNonEmptySet[A: Order: Cogen]: Cogen[NonEmptySet[A]] = - Cogen[SortedSet[A]].contramap(_.toSet) + Cogen[SortedSet[A]].contramap(_.toSortedSet) implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] = Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v))) diff --git a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala index eb44463ac0..8cc8f555c3 100644 --- a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala @@ -56,56 +56,56 @@ class NonEmptySetSuite extends CatsSuite { forAll { (i: Int, tail: SortedSet[Int]) => val set = tail + i val nonEmptySet = NonEmptySet(i, tail) - set should === (nonEmptySet.toSet) + set should === (nonEmptySet.toSortedSet) } } test("NonEmptySet#filter is consistent with Set#filter") { forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => - val set = nes.toSet + val set = nes.toSortedSet nes.filter(p) should === (set.filter(p)) } } test("NonEmptySet#filterNot is consistent with Set#filterNot") { forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => - val set = nes.toSet + val set = nes.toSortedSet nes.filterNot(p) should === (set.filterNot(p)) } } test("NonEmptySet#collect is consistent with Set#collect") { forAll { (nes: NonEmptySet[Int], pf: PartialFunction[Int, String]) => - val set = nes.toSet + val set = nes.toSortedSet nes.collect(pf) should === (set.collect(pf)) } } test("NonEmptySet#find is consistent with Set#find") { forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => - val set = nes.toSet + val set = nes.toSortedSet nes.find(p) should === (set.find(p)) } } test("NonEmptySet#exists is consistent with Set#exists") { forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => - val set = nes.toSet + val set = nes.toSortedSet nes.exists(p) should === (set.exists(p)) } } test("NonEmptySet#forall is consistent with Set#forall") { forAll { (nes: NonEmptySet[Int], p: Int => Boolean) => - val set = nes.toSet + val set = nes.toSortedSet nes.forall(p) should === (set.forall(p)) } } test("NonEmptySet#map is consistent with Set#map") { forAll { (nes: NonEmptySet[Int], p: Int => String) => - val set = nes.toSet - nes.map(p).toSet should === (set.map(p)) + val set = nes.toSortedSet + nes.map(p).toSortedSet should === (set.map(p)) } } @@ -177,17 +177,17 @@ class NonEmptySetSuite extends CatsSuite { test("fromSet round trip") { forAll { l: SortedSet[Int] => - NonEmptySet.fromSet(l).map(_.toSet).getOrElse(SortedSet.empty[Int]) should === (l) + NonEmptySet.fromSet(l).map(_.toSortedSet).getOrElse(SortedSet.empty[Int]) should === (l) } forAll { nes: NonEmptySet[Int] => - NonEmptySet.fromSet(nes.toSet) should === (Some(nes)) + NonEmptySet.fromSet(nes.toSortedSet) should === (Some(nes)) } } test("fromSetUnsafe/fromSet consistency") { forAll { nes: NonEmptySet[Int] => - NonEmptySet.fromSet(nes.toSet) should === (Some(NonEmptySet.fromSetUnsafe(nes.toSet))) + NonEmptySet.fromSet(nes.toSortedSet) should === (Some(NonEmptySet.fromSetUnsafe(nes.toSortedSet))) } } @@ -199,32 +199,32 @@ class NonEmptySetSuite extends CatsSuite { test("+ consistent with Set") { forAll { (nes: NonEmptySet[Int], i: Int) => - (nes + i).toSet should === (nes.toSet + i) + (nes + i).toSortedSet should === (nes.toSortedSet + i) } } test("NonEmptySet#zipWithIndex is consistent with Set#zipWithIndex") { forAll { nes: NonEmptySet[Int] => - nes.zipWithIndex.toSet should === (nes.toSet.zipWithIndex) + nes.zipWithIndex.toSortedSet should === (nes.toSortedSet.zipWithIndex) } } test("NonEmptySet#size and length is consistent with Set#size") { forAll { nes: NonEmptySet[Int] => - nes.size should === (nes.toSet.size) - nes.length should === (nes.toSet.size) + nes.size should === (nes.toSortedSet.size) + nes.length should === (nes.toSortedSet.size) } } test("NonEmptySet#concat is consistent with Set#++") { forAll { (nes: NonEmptySet[Int], l: SortedSet[Int], n: Int) => - nes.concat(NonEmptySet(n, l)).toSet should === (nes.toSet ++ (l + n)) + nes.union(NonEmptySet(n, l)).toSortedSet should === (nes.toSortedSet ++ (l + n)) } } test("NonEmptySet#zipWith is consistent with Set#zip and then Set#map") { forAll { (a: NonEmptySet[Int], b: NonEmptySet[Int], f: (Int, Int) => Int) => - a.zipWith(b)(f).toSet should ===(a.toSet.zip(b.toSet).map { case (x, y) => f(x, y) }) + a.zipWith(b)(f).toSortedSet should ===(a.toSortedSet.zip(b.toSortedSet).map { case (x, y) => f(x, y) }) } } From ff49e94ea6ea529f9ac3d73c518cea7d2e21a555 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 15 Jan 2018 16:31:14 +0100 Subject: [PATCH 05/12] Add more docs --- .../main/scala/cats/data/NonEmptySet.scala | 176 +++++++++++++++++- 1 file changed, 172 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 1409563b2d..223b7672c0 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -28,36 +28,187 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { private implicit def ordering: Ordering[A] = set.ordering private implicit def order: Order[A] = Order.fromOrdering + /** + * Adds an element to this set, returning a new `NonEmptySet` + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 4, 5) + * scala> nes + 3 + * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 3, 4, 5) + * }}} + */ def +(a: A): NonEmptySet[A] = new NonEmptySet(set + a) + + /** + * Alias for [[union]] + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 4, 5) + * scala> nes ++ NonEmptySet.of(1, 2, 7) + * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 4, 5, 7) + * }}} + */ def ++(as: NonEmptySet[A]): NonEmptySet[A] = union(as) + + /** + * Alias for [[union]] + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 4, 5) + * scala> nes | NonEmptySet.of(1, 2, 7) + * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 4, 5, 7) + * }}} + */ def | (as: NonEmptySet[A]): NonEmptySet[A] = union(as) + + /** + * Alias for [[diff]] + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 4, 5) + * scala> nes -- NonEmptySet.of(1, 2, 7) + * res0: scala.collection.immutable.SortedSet[Int] = TreeSet(4, 5) + * }}} + */ def --(as: NonEmptySet[A]): SortedSet[A] = diff(as) + + /** + * Alias for [[diff]] + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 4, 5) + * scala> nes &~ NonEmptySet.of(1, 2, 7) + * res0: scala.collection.immutable.SortedSet[Int] = TreeSet(4, 5) + * }}} + */ def &~(as: NonEmptySet[A]): SortedSet[A] = diff(as) + + /** + * Alias for [[intersect]] + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 4, 5) + * scala> nes & NonEmptySet.of(1, 2, 7) + * res0: scala.collection.immutable.SortedSet[Int] = TreeSet(1, 2) + * }}} + */ def &(as: NonEmptySet[A]): SortedSet[A] = intersect(as) + /** + * Removes a key from this set, returning a new `SortedSet`. + */ def -(a: A): SortedSet[A] = set - a + + /** + * Applies f to all the elements + */ def map[B: Order](f: A ⇒ B): NonEmptySet[B] = new NonEmptySet(SortedSet(set.map(f).to: _*)(Order[B].toOrdering)) + /** + * Converts this set to a `NonEmptyList`. + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 3, 4, 5) + * scala> nes.toNonEmptyList + * res0: cats.data.NonEmptyList[Int] = NonEmptyList(1, 2, 3, 4, 5) + * }}} + */ def toNonEmptyList: NonEmptyList[A] = NonEmptyList.fromListUnsafe(set.toList) + /** + * Returns the first element of this set. + */ def head: A = set.head + + /** + * Returns all but the first element of this set. + */ def tail: SortedSet[A] = set.tail + + /** + * Returns the last element of this set. + */ def last: A = set.last - def apply(a: A): Boolean = set(a) - def contains(a: A): Boolean = set.contains(a) + /** + * Alias for [[contains]] + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 3, 4, 5) + * scala> nes(3) + * res0: Boolean = true + * scala> nes(7) + * res1: Boolean = false + * }}} + */ + def apply(a: A): Boolean = contains(a) + /** + * Tests if some element is contained in this set. + */ + def contains(a: A): Boolean = set(a) + + /** + * Computes the difference of this set and another set. + */ def diff(as: NonEmptySet[A]): SortedSet[A] = set -- as.set + + /** + * Computes the union between this NES and another NES. + */ def union(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set ++ as.set) + + /** + * Computes the intersection between this set and another set. + */ def intersect(as: NonEmptySet[A]): SortedSet[A] = set.filter(as.apply) + + /** + * Returns the number of elements in this set. + */ def size: Int = set.size + + /** + * Tests whether a predicate holds for all elements of this set. + */ def forall(p: A ⇒ Boolean): Boolean = set.forall(p) + + /** + * Tests whether a predicate holds for at least one element of this set. + */ def exists(f: A ⇒ Boolean): Boolean = set.exists(f) + + /** + * Returns the first value that matches the given predicate. + */ def find(f: A ⇒ Boolean): Option[A] = set.find(f) - def collect[B](pf: PartialFunction[A, B]): Set[B] = set.collect(pf) + + /** + * Returns a new `SortedSet` containing all elements where the result of `pf` is defined. + */ + def collect[B: Order](pf: PartialFunction[A, B]): SortedSet[B] = { + implicit val ordering = Order[B].toOrdering + set.collect(pf) + } + + /** + * Filters all elements of this set that do not satisfy the given predicate. + */ def filter(p: A ⇒ Boolean): SortedSet[A] = set.filter(p) + + /** + * Filters all elements of this set that satisfy the given predicate. + */ def filterNot(p: A ⇒ Boolean): SortedSet[A] = filter(t => !p(t)) @@ -79,6 +230,10 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { def reduceLeft(f: (A, A) => A): A = set.reduceLeft(f) + /** + * Apply `f` to the "initial element" of this set and lazily combine it + * with every other value using the given function `g`. + */ def reduceLeftTo[B](f: A => B)(g: (B, A) => B): B = { tail.foldLeft(f(head))((b, a) => g(b, a)) } @@ -89,6 +244,10 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { def reduceRight(f: (A, Eval[A]) => Eval[A]): Eval[A] = reduceRightTo(identity)(f) + /** + * Apply `f` to the "initial element" of this set and lazily combine it + * with every other value using the given function `g`. + */ def reduceRightTo[B](f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = Always((head, tail)).flatMap { case (a, ga) => Foldable[SortedSet].reduceRightToOption(ga)(f)(g).flatMap { @@ -103,6 +262,9 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { def reduce[AA >: A](implicit S: Semigroup[AA]): AA = S.combineAllOption(set).get + /** + * Converts this set to a `SortedSet` + */ def toSortedSet: SortedSet[A] = set /** @@ -126,6 +288,9 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { def ===(that: NonEmptySet[A]): Boolean = Eq[SortedSet[A]].eqv(set, that.toSortedSet) + /** + * Alias for [[size]] + */ def length: Int = size override def toString: String = s"NonEmpty${set.toString}" @@ -145,6 +310,9 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] = new NonEmptySet(SortedSet((set, b.toSortedSet).zipped.map(f).to: _*)(Order[C].toOrdering)) + /** + * Zips this `NonEmptySet` with its index. + */ def zipWithIndex: NonEmptySet[(A, Int)] = new NonEmptySet(set.zipWithIndex) } @@ -193,7 +361,7 @@ private[data] sealed abstract class NonEmptySetInstances { override def toList[A](fa: NonEmptySet[A]): List[A] = fa.toSortedSet.toList override def toNonEmptyList[A](fa: NonEmptySet[A]): NonEmptyList[A] = - NonEmptyList(fa.head, fa.tail.toList) + fa.toNonEmptyList } implicit def catsDataEqForNonEmptySet[A: Order]: Eq[NonEmptySet[A]] = From b46f5fe3368c0ab64f12ec58d9258698c9afa4ac Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 15 Jan 2018 17:51:46 +0100 Subject: [PATCH 06/12] Add concatMap --- core/src/main/scala/cats/data/NonEmptySet.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 223b7672c0..3653c46688 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -262,6 +262,22 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { def reduce[AA >: A](implicit S: Semigroup[AA]): AA = S.combineAllOption(set).get + /** + * Map a function over all the elements of this set and concatenate the resulting sets into one. + * {{{ + * scala> import cats.data.NonEmptySet + * scala> import cats.implicits._ + * scala> val nes = NonEmptySet.of(1, 2, 3) + * scala> nes.concatMap(n => NonEmptySet.of(n, n * 4, n * 5)) + * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 3, 4, 5, 8, 10, 12, 15) + * }}} + */ + def concatMap[B: Order](f: A => NonEmptySet[B]): NonEmptySet[B] = { + implicit val ordering = Order[B].toOrdering + new NonEmptySet(set.flatMap(f andThen (_.set))) + } + + /** * Converts this set to a `SortedSet` */ From bba1a5df631fcf2dda3d213a6c525078bd7641db Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 5 Feb 2018 09:58:24 +0100 Subject: [PATCH 07/12] Newtype encoding --- .../main/scala/cats/data/NonEmptySet.scala | 140 +++++++++--------- core/src/main/scala/cats/data/package.scala | 3 + .../scala/cats/tests/NonEmptySetSuite.scala | 5 +- 3 files changed, 78 insertions(+), 70 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 3653c46688..2f12179d6b 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -23,22 +23,54 @@ import cats.{Always, Eq, Eval, Foldable, Later, Now, Reducible, SemigroupK, Show import scala.collection.immutable._ -final class NonEmptySet[A] private (val set: SortedSet[A]) { +trait Newtype { self => + private[data] type Base + private[data] trait Tag extends Any + private[cats] type Type[A] <: Base with Tag +} + +private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype { + + private[cats] def create[A](s: SortedSet[A]): Type[A] = + s.asInstanceOf[Type[A]] + + private[cats] def unwrap[A](s: Type[A]): SortedSet[A] = + s.asInstanceOf[SortedSet[A]] + + + def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] = + if (as.nonEmpty) Option(create(as)) else None + + def fromSetUnsafe[A: Order](set: SortedSet[A]): NonEmptySet[A] = + if (set.nonEmpty) create(set) + else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set") + + + def of[A: Order](a: A, as: A*): NonEmptySet[A] = + create(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a) + def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = create(SortedSet(head)(Order[A].toOrdering) ++ tail) + def one[A: Order](a: A): NonEmptySet[A] = create(SortedSet(a)(Order[A].toOrdering)) + + implicit def catsNonEmptySetOps[A](value: NonEmptySet[A]): NonEmptySetOps[A] = + new NonEmptySetOps(value) +} + + +private[data] sealed class NonEmptySetOps[A](val value: NonEmptySetImpl.Type[A]) { + + private implicit val ordering: Ordering[A] = toSortedSet.ordering + private implicit val order: Order[A] = Order.fromOrdering + + /** + * Converts this set to a `SortedSet` + */ + def toSortedSet: SortedSet[A] = NonEmptySetImpl.unwrap(value) - private implicit def ordering: Ordering[A] = set.ordering - private implicit def order: Order[A] = Order.fromOrdering /** * Adds an element to this set, returning a new `NonEmptySet` - * {{{ - * scala> import cats.data.NonEmptySet - * scala> import cats.implicits._ - * scala> val nes = NonEmptySet.of(1, 2, 4, 5) - * scala> nes + 3 - * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 3, 4, 5) - * }}} */ - def +(a: A): NonEmptySet[A] = new NonEmptySet(set + a) + def add(a: A): NonEmptySet[A] = NonEmptySet.create(toSortedSet + a) /** * Alias for [[union]] @@ -47,7 +79,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * scala> import cats.implicits._ * scala> val nes = NonEmptySet.of(1, 2, 4, 5) * scala> nes ++ NonEmptySet.of(1, 2, 7) - * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 4, 5, 7) + * res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 4, 5, 7) * }}} */ def ++(as: NonEmptySet[A]): NonEmptySet[A] = union(as) @@ -59,7 +91,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * scala> import cats.implicits._ * scala> val nes = NonEmptySet.of(1, 2, 4, 5) * scala> nes | NonEmptySet.of(1, 2, 7) - * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 4, 5, 7) + * res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 4, 5, 7) * }}} */ def | (as: NonEmptySet[A]): NonEmptySet[A] = union(as) @@ -104,13 +136,13 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { /** * Removes a key from this set, returning a new `SortedSet`. */ - def -(a: A): SortedSet[A] = set - a + def -(a: A): SortedSet[A] = toSortedSet - a /** * Applies f to all the elements */ def map[B: Order](f: A ⇒ B): NonEmptySet[B] = - new NonEmptySet(SortedSet(set.map(f).to: _*)(Order[B].toOrdering)) + NonEmptySetImpl.create(SortedSet(toSortedSet.map(f).to: _*)(Order[B].toOrdering)) /** * Converts this set to a `NonEmptyList`. @@ -122,22 +154,22 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * res0: cats.data.NonEmptyList[Int] = NonEmptyList(1, 2, 3, 4, 5) * }}} */ - def toNonEmptyList: NonEmptyList[A] = NonEmptyList.fromListUnsafe(set.toList) + def toNonEmptyList: NonEmptyList[A] = NonEmptyList.fromListUnsafe(toSortedSet.toList) /** * Returns the first element of this set. */ - def head: A = set.head + def head: A = toSortedSet.head /** * Returns all but the first element of this set. */ - def tail: SortedSet[A] = set.tail + def tail: SortedSet[A] = toSortedSet.tail /** * Returns the last element of this set. */ - def last: A = set.last + def last: A = toSortedSet.last /** * Alias for [[contains]] @@ -156,55 +188,50 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { /** * Tests if some element is contained in this set. */ - def contains(a: A): Boolean = set(a) + def contains(a: A): Boolean = toSortedSet(a) /** * Computes the difference of this set and another set. */ - def diff(as: NonEmptySet[A]): SortedSet[A] = set -- as.set + def diff(as: NonEmptySet[A]): SortedSet[A] = toSortedSet -- as.toSortedSet /** * Computes the union between this NES and another NES. */ - def union(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set ++ as.set) + def union(as: NonEmptySet[A]): NonEmptySet[A] = NonEmptySetImpl.create(toSortedSet ++ as.toSortedSet) /** * Computes the intersection between this set and another set. */ - def intersect(as: NonEmptySet[A]): SortedSet[A] = set.filter(as.apply) - - /** - * Returns the number of elements in this set. - */ - def size: Int = set.size + def intersect(as: NonEmptySet[A]): SortedSet[A] = toSortedSet.filter(as.apply) /** * Tests whether a predicate holds for all elements of this set. */ - def forall(p: A ⇒ Boolean): Boolean = set.forall(p) + def forall(p: A ⇒ Boolean): Boolean = toSortedSet.forall(p) /** * Tests whether a predicate holds for at least one element of this set. */ - def exists(f: A ⇒ Boolean): Boolean = set.exists(f) + def exists(f: A ⇒ Boolean): Boolean = toSortedSet.exists(f) /** * Returns the first value that matches the given predicate. */ - def find(f: A ⇒ Boolean): Option[A] = set.find(f) + def find(f: A ⇒ Boolean): Option[A] = toSortedSet.find(f) /** * Returns a new `SortedSet` containing all elements where the result of `pf` is defined. */ def collect[B: Order](pf: PartialFunction[A, B]): SortedSet[B] = { implicit val ordering = Order[B].toOrdering - set.collect(pf) + toSortedSet.collect(pf) } /** * Filters all elements of this set that do not satisfy the given predicate. */ - def filter(p: A ⇒ Boolean): SortedSet[A] = set.filter(p) + def filter(p: A ⇒ Boolean): SortedSet[A] = toSortedSet.filter(p) /** * Filters all elements of this set that satisfy the given predicate. @@ -216,19 +243,19 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * Left-associative fold using f. */ def foldLeft[B](b: B)(f: (B, A) => B): B = - set.foldLeft(b)(f) + toSortedSet.foldLeft(b)(f) /** * Right-associative fold using f. */ def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Foldable[SortedSet].foldRight(set, lb)(f) + Foldable[SortedSet].foldRight(toSortedSet, lb)(f) /** * Left-associative reduce using f. */ def reduceLeft(f: (A, A) => A): A = - set.reduceLeft(f) + toSortedSet.reduceLeft(f) /** * Apply `f` to the "initial element" of this set and lazily combine it @@ -260,7 +287,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * Reduce using the Semigroup of A */ def reduce[AA >: A](implicit S: Semigroup[AA]): AA = - S.combineAllOption(set).get + S.combineAllOption(toSortedSet).get /** * Map a function over all the elements of this set and concatenate the resulting sets into one. @@ -269,20 +296,15 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * scala> import cats.implicits._ * scala> val nes = NonEmptySet.of(1, 2, 3) * scala> nes.concatMap(n => NonEmptySet.of(n, n * 4, n * 5)) - * res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 3, 4, 5, 8, 10, 12, 15) + * res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 3, 4, 5, 8, 10, 12, 15) * }}} */ def concatMap[B: Order](f: A => NonEmptySet[B]): NonEmptySet[B] = { implicit val ordering = Order[B].toOrdering - new NonEmptySet(set.flatMap(f andThen (_.set))) + NonEmptySetImpl.create(toSortedSet.flatMap(f andThen (_.toSortedSet))) } - /** - * Converts this set to a `SortedSet` - */ - def toSortedSet: SortedSet[A] = set - /** * Typesafe stringification method. * @@ -291,7 +313,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * universal .toString method. */ def show(implicit A: Show[A]): String = - s"NonEmpty${Show[SortedSet[A]].show(set)}" + s"NonEmpty${Show[SortedSet[A]].show(toSortedSet)}" /** * Typesafe equality operator. @@ -302,14 +324,12 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * universal equality provided by .equals. */ def ===(that: NonEmptySet[A]): Boolean = - Eq[SortedSet[A]].eqv(set, that.toSortedSet) + Eq[SortedSet[A]].eqv(toSortedSet, that.toSortedSet) /** - * Alias for [[size]] + * Returns the number of elements in this set. */ - def length: Int = size - - override def toString: String = s"NonEmpty${set.toString}" + def length: Int = toSortedSet.size /** * Zips this `NonEmptySet` with another `NonEmptySet` and applies a function for each pair of elements. @@ -320,17 +340,17 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) { * scala> val as = NonEmptySet.of(1, 2, 3) * scala> val bs = NonEmptySet.of("A", "B", "C") * scala> as.zipWith(bs)(_ + _) - * res0: cats.data.NonEmptySet[String] = NonEmptyTreeSet(1A, 2B, 3C) + * res0: cats.data.NonEmptySet[String] = TreeSet(1A, 2B, 3C) * }}} */ def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] = - new NonEmptySet(SortedSet((set, b.toSortedSet).zipped.map(f).to: _*)(Order[C].toOrdering)) + NonEmptySetImpl.create(SortedSet((toSortedSet, b.toSortedSet).zipped.map(f).to: _*)(Order[C].toOrdering)) /** * Zips this `NonEmptySet` with its index. */ def zipWithIndex: NonEmptySet[(A, Int)] = - new NonEmptySet(set.zipWithIndex) + NonEmptySetImpl.create(toSortedSet.zipWithIndex) } private[data] sealed abstract class NonEmptySetInstances { @@ -393,17 +413,3 @@ private[data] sealed abstract class NonEmptySetInstances { } } -object NonEmptySet extends NonEmptySetInstances { - def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] = - if (as.nonEmpty) Option(new NonEmptySet(as)) else None - - def fromSetUnsafe[A: Order](set: SortedSet[A]): NonEmptySet[A] = - if (set.nonEmpty) new NonEmptySet(set) - else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set") - - - def of[A: Order](a: A, as: A*): NonEmptySet[A] = - new NonEmptySet(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a) - def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = new NonEmptySet(SortedSet(head)(Order[A].toOrdering) ++ tail) - def one[A: Order](a: A): NonEmptySet[A] = new NonEmptySet(SortedSet(a)(Order[A].toOrdering)) -} diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 59775d7161..a132c68589 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -11,6 +11,9 @@ package object data { def NonEmptyStream[A](head: A, tail: A*): NonEmptyStream[A] = OneAnd(head, tail.toStream) + type NonEmptySet[A] = NonEmptySetImpl.Type[A] + val NonEmptySet = NonEmptySetImpl + type ReaderT[F[_], A, B] = Kleisli[F, A, B] val ReaderT = Kleisli diff --git a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala index 8cc8f555c3..83d52297c0 100644 --- a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala @@ -199,7 +199,7 @@ class NonEmptySetSuite extends CatsSuite { test("+ consistent with Set") { forAll { (nes: NonEmptySet[Int], i: Int) => - (nes + i).toSortedSet should === (nes.toSortedSet + i) + (nes add i).toSortedSet should === (nes.toSortedSet + i) } } @@ -209,9 +209,8 @@ class NonEmptySetSuite extends CatsSuite { } } - test("NonEmptySet#size and length is consistent with Set#size") { + test("NonEmptySet#length is consistent with Set#size") { forAll { nes: NonEmptySet[Int] => - nes.size should === (nes.toSortedSet.size) nes.length should === (nes.toSortedSet.size) } } From 65fe31b1df4bbaaba23d4651a405b987352633a1 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 7 Feb 2018 13:00:05 +0100 Subject: [PATCH 08/12] Move newtype to its own file --- core/src/main/scala/cats/data/Newtype.scala | 8 ++++++++ core/src/main/scala/cats/data/NonEmptySet.scala | 7 +------ 2 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 core/src/main/scala/cats/data/Newtype.scala diff --git a/core/src/main/scala/cats/data/Newtype.scala b/core/src/main/scala/cats/data/Newtype.scala new file mode 100644 index 0000000000..bb295d3933 --- /dev/null +++ b/core/src/main/scala/cats/data/Newtype.scala @@ -0,0 +1,8 @@ +package cats +package data + +trait Newtype { self => + private[data] type Base + private[data] trait Tag extends Any + type Type[A] <: Base with Tag +} diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 2f12179d6b..6a8792c099 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -23,11 +23,6 @@ import cats.{Always, Eq, Eval, Foldable, Later, Now, Reducible, SemigroupK, Show import scala.collection.immutable._ -trait Newtype { self => - private[data] type Base - private[data] trait Tag extends Any - private[cats] type Type[A] <: Base with Tag -} private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype { @@ -56,7 +51,7 @@ private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype { } -private[data] sealed class NonEmptySetOps[A](val value: NonEmptySetImpl.Type[A]) { +sealed class NonEmptySetOps[A](val value: NonEmptySet[A]) { private implicit val ordering: Ordering[A] = toSortedSet.ordering private implicit val order: Order[A] = Order.fromOrdering From a5da2654139edc72fa0f53621ab56afde863e7a4 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 12 Feb 2018 09:47:58 +0100 Subject: [PATCH 09/12] Address feedback --- core/src/main/scala/cats/data/Newtype.scala | 4 ++++ core/src/main/scala/cats/data/NonEmptySet.scala | 5 +++-- tests/src/test/scala/cats/tests/NonEmptySetSuite.scala | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Newtype.scala b/core/src/main/scala/cats/data/Newtype.scala index bb295d3933..926ed02c3e 100644 --- a/core/src/main/scala/cats/data/Newtype.scala +++ b/core/src/main/scala/cats/data/Newtype.scala @@ -1,6 +1,10 @@ package cats package data +/** + * Helper trait for `newtype`s. These allow you to create a zero-allocation wrapper around a specific type. + * Similar to `AnyVal` value classes, but never have any runtime overhead. + */ trait Newtype { self => private[data] type Base private[data] trait Tag extends Any diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index 6a8792c099..b05a2b4f96 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -42,8 +42,9 @@ private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype { def of[A: Order](a: A, as: A*): NonEmptySet[A] = - create(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a) - def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = create(SortedSet(head)(Order[A].toOrdering) ++ tail) + create(SortedSet((a +: as): _*)(Order[A].toOrdering)) + def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = + create(SortedSet(head)(Order[A].toOrdering) ++ tail) def one[A: Order](a: A): NonEmptySet[A] = create(SortedSet(a)(Order[A].toOrdering)) implicit def catsNonEmptySetOps[A](value: NonEmptySet[A]): NonEmptySetOps[A] = diff --git a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala index 83d52297c0..36250049e4 100644 --- a/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptySetSuite.scala @@ -227,4 +227,10 @@ class NonEmptySetSuite extends CatsSuite { } } + test("NonEmptySet.of is consistent with removal") { + forAll { (is: SortedSet[Int], i: Int) => + NonEmptySet.of(i, is.toList: _*) - i should ===(is - i) + } + } + } From 018560121af6f798dabeabbf82400d9f5e8290fd Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 5 Mar 2018 21:45:25 +0100 Subject: [PATCH 10/12] Remove Order constraint for fromSet constructor --- core/src/main/scala/cats/data/NonEmptySet.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index b05a2b4f96..bc7c2a5c45 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -33,10 +33,10 @@ private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype { s.asInstanceOf[SortedSet[A]] - def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] = + def fromSet[A](as: SortedSet[A]): Option[NonEmptySet[A]] = if (as.nonEmpty) Option(create(as)) else None - def fromSetUnsafe[A: Order](set: SortedSet[A]): NonEmptySet[A] = + def fromSetUnsafe[A](set: SortedSet[A]): NonEmptySet[A] = if (set.nonEmpty) create(set) else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set") From 9d16f11bdfc1c00860ec413c090967db3e05720d Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 11 Mar 2018 16:07:13 +0100 Subject: [PATCH 11/12] Remove Context bounds --- .../main/scala/cats/data/NonEmptySet.scala | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptySet.scala b/core/src/main/scala/cats/data/NonEmptySet.scala index bc7c2a5c45..ade9b6184f 100644 --- a/core/src/main/scala/cats/data/NonEmptySet.scala +++ b/core/src/main/scala/cats/data/NonEmptySet.scala @@ -19,7 +19,6 @@ package data import cats.instances.sortedSet._ import cats.kernel._ -import cats.{Always, Eq, Eval, Foldable, Later, Now, Reducible, SemigroupK, Show} import scala.collection.immutable._ @@ -41,11 +40,11 @@ private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype { else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set") - def of[A: Order](a: A, as: A*): NonEmptySet[A] = - create(SortedSet((a +: as): _*)(Order[A].toOrdering)) - def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = - create(SortedSet(head)(Order[A].toOrdering) ++ tail) - def one[A: Order](a: A): NonEmptySet[A] = create(SortedSet(a)(Order[A].toOrdering)) + def of[A](a: A, as: A*)(implicit A: Order[A]): NonEmptySet[A] = + create(SortedSet(a +: as: _*)(A.toOrdering)) + def apply[A](head: A, tail: SortedSet[A])(implicit A: Order[A]): NonEmptySet[A] = + create(SortedSet(head)(A.toOrdering) ++ tail) + def one[A](a: A)(implicit A: Order[A]): NonEmptySet[A] = create(SortedSet(a)(A.toOrdering)) implicit def catsNonEmptySetOps[A](value: NonEmptySet[A]): NonEmptySetOps[A] = new NonEmptySetOps(value) @@ -137,8 +136,8 @@ sealed class NonEmptySetOps[A](val value: NonEmptySet[A]) { /** * Applies f to all the elements */ - def map[B: Order](f: A ⇒ B): NonEmptySet[B] = - NonEmptySetImpl.create(SortedSet(toSortedSet.map(f).to: _*)(Order[B].toOrdering)) + def map[B](f: A => B)(implicit B: Order[B]): NonEmptySet[B] = + NonEmptySetImpl.create(SortedSet(toSortedSet.map(f).to: _*)(B.toOrdering)) /** * Converts this set to a `NonEmptyList`. @@ -219,8 +218,8 @@ sealed class NonEmptySetOps[A](val value: NonEmptySet[A]) { /** * Returns a new `SortedSet` containing all elements where the result of `pf` is defined. */ - def collect[B: Order](pf: PartialFunction[A, B]): SortedSet[B] = { - implicit val ordering = Order[B].toOrdering + def collect[B](pf: PartialFunction[A, B])(implicit B: Order[B]): SortedSet[B] = { + implicit val ordering = B.toOrdering toSortedSet.collect(pf) } @@ -295,8 +294,8 @@ sealed class NonEmptySetOps[A](val value: NonEmptySet[A]) { * res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 3, 4, 5, 8, 10, 12, 15) * }}} */ - def concatMap[B: Order](f: A => NonEmptySet[B]): NonEmptySet[B] = { - implicit val ordering = Order[B].toOrdering + def concatMap[B](f: A => NonEmptySet[B])(implicit B: Order[B]): NonEmptySet[B] = { + implicit val ordering = B.toOrdering NonEmptySetImpl.create(toSortedSet.flatMap(f andThen (_.toSortedSet))) } @@ -339,8 +338,8 @@ sealed class NonEmptySetOps[A](val value: NonEmptySet[A]) { * res0: cats.data.NonEmptySet[String] = TreeSet(1A, 2B, 3C) * }}} */ - def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] = - NonEmptySetImpl.create(SortedSet((toSortedSet, b.toSortedSet).zipped.map(f).to: _*)(Order[C].toOrdering)) + def zipWith[B, C](b: NonEmptySet[B])(f: (A, B) => C)(implicit C: Order[C]): NonEmptySet[C] = + NonEmptySetImpl.create(SortedSet((toSortedSet, b.toSortedSet).zipped.map(f).to: _*)(C.toOrdering)) /** * Zips this `NonEmptySet` with its index. From b7097730ebe422ab73b6884e49ced05b1186f61f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 14 Mar 2018 10:01:23 +0100 Subject: [PATCH 12/12] Make newtype private --- core/src/main/scala/cats/data/Newtype.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Newtype.scala b/core/src/main/scala/cats/data/Newtype.scala index 926ed02c3e..20e9b02b6d 100644 --- a/core/src/main/scala/cats/data/Newtype.scala +++ b/core/src/main/scala/cats/data/Newtype.scala @@ -5,7 +5,7 @@ package data * Helper trait for `newtype`s. These allow you to create a zero-allocation wrapper around a specific type. * Similar to `AnyVal` value classes, but never have any runtime overhead. */ -trait Newtype { self => +private[data] trait Newtype { self => private[data] type Base private[data] trait Tag extends Any type Type[A] <: Base with Tag