Skip to content

Commit bba1a5d

Browse files
author
Luka Jacobowitz
committed
Newtype encoding
1 parent b46f5fe commit bba1a5d

File tree

3 files changed

+78
-70
lines changed

3 files changed

+78
-70
lines changed

core/src/main/scala/cats/data/NonEmptySet.scala

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,54 @@ import cats.{Always, Eq, Eval, Foldable, Later, Now, Reducible, SemigroupK, Show
2323

2424
import scala.collection.immutable._
2525

26-
final class NonEmptySet[A] private (val set: SortedSet[A]) {
26+
trait Newtype { self =>
27+
private[data] type Base
28+
private[data] trait Tag extends Any
29+
private[cats] type Type[A] <: Base with Tag
30+
}
31+
32+
private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype {
33+
34+
private[cats] def create[A](s: SortedSet[A]): Type[A] =
35+
s.asInstanceOf[Type[A]]
36+
37+
private[cats] def unwrap[A](s: Type[A]): SortedSet[A] =
38+
s.asInstanceOf[SortedSet[A]]
39+
40+
41+
def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] =
42+
if (as.nonEmpty) Option(create(as)) else None
43+
44+
def fromSetUnsafe[A: Order](set: SortedSet[A]): NonEmptySet[A] =
45+
if (set.nonEmpty) create(set)
46+
else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set")
47+
48+
49+
def of[A: Order](a: A, as: A*): NonEmptySet[A] =
50+
create(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a)
51+
def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = create(SortedSet(head)(Order[A].toOrdering) ++ tail)
52+
def one[A: Order](a: A): NonEmptySet[A] = create(SortedSet(a)(Order[A].toOrdering))
53+
54+
implicit def catsNonEmptySetOps[A](value: NonEmptySet[A]): NonEmptySetOps[A] =
55+
new NonEmptySetOps(value)
56+
}
57+
58+
59+
private[data] sealed class NonEmptySetOps[A](val value: NonEmptySetImpl.Type[A]) {
60+
61+
private implicit val ordering: Ordering[A] = toSortedSet.ordering
62+
private implicit val order: Order[A] = Order.fromOrdering
63+
64+
/**
65+
* Converts this set to a `SortedSet`
66+
*/
67+
def toSortedSet: SortedSet[A] = NonEmptySetImpl.unwrap(value)
2768

28-
private implicit def ordering: Ordering[A] = set.ordering
29-
private implicit def order: Order[A] = Order.fromOrdering
3069

3170
/**
3271
* Adds an element to this set, returning a new `NonEmptySet`
33-
* {{{
34-
* scala> import cats.data.NonEmptySet
35-
* scala> import cats.implicits._
36-
* scala> val nes = NonEmptySet.of(1, 2, 4, 5)
37-
* scala> nes + 3
38-
* res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 3, 4, 5)
39-
* }}}
4072
*/
41-
def +(a: A): NonEmptySet[A] = new NonEmptySet(set + a)
73+
def add(a: A): NonEmptySet[A] = NonEmptySet.create(toSortedSet + a)
4274

4375
/**
4476
* Alias for [[union]]
@@ -47,7 +79,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
4779
* scala> import cats.implicits._
4880
* scala> val nes = NonEmptySet.of(1, 2, 4, 5)
4981
* scala> nes ++ NonEmptySet.of(1, 2, 7)
50-
* res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 4, 5, 7)
82+
* res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 4, 5, 7)
5183
* }}}
5284
*/
5385
def ++(as: NonEmptySet[A]): NonEmptySet[A] = union(as)
@@ -59,7 +91,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
5991
* scala> import cats.implicits._
6092
* scala> val nes = NonEmptySet.of(1, 2, 4, 5)
6193
* scala> nes | NonEmptySet.of(1, 2, 7)
62-
* res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 4, 5, 7)
94+
* res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 4, 5, 7)
6395
* }}}
6496
*/
6597
def | (as: NonEmptySet[A]): NonEmptySet[A] = union(as)
@@ -104,13 +136,13 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
104136
/**
105137
* Removes a key from this set, returning a new `SortedSet`.
106138
*/
107-
def -(a: A): SortedSet[A] = set - a
139+
def -(a: A): SortedSet[A] = toSortedSet - a
108140

109141
/**
110142
* Applies f to all the elements
111143
*/
112144
def map[B: Order](f: A B): NonEmptySet[B] =
113-
new NonEmptySet(SortedSet(set.map(f).to: _*)(Order[B].toOrdering))
145+
NonEmptySetImpl.create(SortedSet(toSortedSet.map(f).to: _*)(Order[B].toOrdering))
114146

115147
/**
116148
* Converts this set to a `NonEmptyList`.
@@ -122,22 +154,22 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
122154
* res0: cats.data.NonEmptyList[Int] = NonEmptyList(1, 2, 3, 4, 5)
123155
* }}}
124156
*/
125-
def toNonEmptyList: NonEmptyList[A] = NonEmptyList.fromListUnsafe(set.toList)
157+
def toNonEmptyList: NonEmptyList[A] = NonEmptyList.fromListUnsafe(toSortedSet.toList)
126158

127159
/**
128160
* Returns the first element of this set.
129161
*/
130-
def head: A = set.head
162+
def head: A = toSortedSet.head
131163

132164
/**
133165
* Returns all but the first element of this set.
134166
*/
135-
def tail: SortedSet[A] = set.tail
167+
def tail: SortedSet[A] = toSortedSet.tail
136168

137169
/**
138170
* Returns the last element of this set.
139171
*/
140-
def last: A = set.last
172+
def last: A = toSortedSet.last
141173

142174
/**
143175
* Alias for [[contains]]
@@ -156,55 +188,50 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
156188
/**
157189
* Tests if some element is contained in this set.
158190
*/
159-
def contains(a: A): Boolean = set(a)
191+
def contains(a: A): Boolean = toSortedSet(a)
160192

161193
/**
162194
* Computes the difference of this set and another set.
163195
*/
164-
def diff(as: NonEmptySet[A]): SortedSet[A] = set -- as.set
196+
def diff(as: NonEmptySet[A]): SortedSet[A] = toSortedSet -- as.toSortedSet
165197

166198
/**
167199
* Computes the union between this NES and another NES.
168200
*/
169-
def union(as: NonEmptySet[A]): NonEmptySet[A] = new NonEmptySet(set ++ as.set)
201+
def union(as: NonEmptySet[A]): NonEmptySet[A] = NonEmptySetImpl.create(toSortedSet ++ as.toSortedSet)
170202

171203
/**
172204
* Computes the intersection between this set and another set.
173205
*/
174-
def intersect(as: NonEmptySet[A]): SortedSet[A] = set.filter(as.apply)
175-
176-
/**
177-
* Returns the number of elements in this set.
178-
*/
179-
def size: Int = set.size
206+
def intersect(as: NonEmptySet[A]): SortedSet[A] = toSortedSet.filter(as.apply)
180207

181208
/**
182209
* Tests whether a predicate holds for all elements of this set.
183210
*/
184-
def forall(p: A Boolean): Boolean = set.forall(p)
211+
def forall(p: A Boolean): Boolean = toSortedSet.forall(p)
185212

186213
/**
187214
* Tests whether a predicate holds for at least one element of this set.
188215
*/
189-
def exists(f: A Boolean): Boolean = set.exists(f)
216+
def exists(f: A Boolean): Boolean = toSortedSet.exists(f)
190217

191218
/**
192219
* Returns the first value that matches the given predicate.
193220
*/
194-
def find(f: A Boolean): Option[A] = set.find(f)
221+
def find(f: A Boolean): Option[A] = toSortedSet.find(f)
195222

196223
/**
197224
* Returns a new `SortedSet` containing all elements where the result of `pf` is defined.
198225
*/
199226
def collect[B: Order](pf: PartialFunction[A, B]): SortedSet[B] = {
200227
implicit val ordering = Order[B].toOrdering
201-
set.collect(pf)
228+
toSortedSet.collect(pf)
202229
}
203230

204231
/**
205232
* Filters all elements of this set that do not satisfy the given predicate.
206233
*/
207-
def filter(p: A Boolean): SortedSet[A] = set.filter(p)
234+
def filter(p: A Boolean): SortedSet[A] = toSortedSet.filter(p)
208235

209236
/**
210237
* Filters all elements of this set that satisfy the given predicate.
@@ -216,19 +243,19 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
216243
* Left-associative fold using f.
217244
*/
218245
def foldLeft[B](b: B)(f: (B, A) => B): B =
219-
set.foldLeft(b)(f)
246+
toSortedSet.foldLeft(b)(f)
220247

221248
/**
222249
* Right-associative fold using f.
223250
*/
224251
def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
225-
Foldable[SortedSet].foldRight(set, lb)(f)
252+
Foldable[SortedSet].foldRight(toSortedSet, lb)(f)
226253

227254
/**
228255
* Left-associative reduce using f.
229256
*/
230257
def reduceLeft(f: (A, A) => A): A =
231-
set.reduceLeft(f)
258+
toSortedSet.reduceLeft(f)
232259

233260
/**
234261
* 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]) {
260287
* Reduce using the Semigroup of A
261288
*/
262289
def reduce[AA >: A](implicit S: Semigroup[AA]): AA =
263-
S.combineAllOption(set).get
290+
S.combineAllOption(toSortedSet).get
264291

265292
/**
266293
* 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]) {
269296
* scala> import cats.implicits._
270297
* scala> val nes = NonEmptySet.of(1, 2, 3)
271298
* scala> nes.concatMap(n => NonEmptySet.of(n, n * 4, n * 5))
272-
* res0: cats.data.NonEmptySet[Int] = NonEmptyTreeSet(1, 2, 3, 4, 5, 8, 10, 12, 15)
299+
* res0: cats.data.NonEmptySet[Int] = TreeSet(1, 2, 3, 4, 5, 8, 10, 12, 15)
273300
* }}}
274301
*/
275302
def concatMap[B: Order](f: A => NonEmptySet[B]): NonEmptySet[B] = {
276303
implicit val ordering = Order[B].toOrdering
277-
new NonEmptySet(set.flatMap(f andThen (_.set)))
304+
NonEmptySetImpl.create(toSortedSet.flatMap(f andThen (_.toSortedSet)))
278305
}
279306

280307

281-
/**
282-
* Converts this set to a `SortedSet`
283-
*/
284-
def toSortedSet: SortedSet[A] = set
285-
286308
/**
287309
* Typesafe stringification method.
288310
*
@@ -291,7 +313,7 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
291313
* universal .toString method.
292314
*/
293315
def show(implicit A: Show[A]): String =
294-
s"NonEmpty${Show[SortedSet[A]].show(set)}"
316+
s"NonEmpty${Show[SortedSet[A]].show(toSortedSet)}"
295317

296318
/**
297319
* Typesafe equality operator.
@@ -302,14 +324,12 @@ final class NonEmptySet[A] private (val set: SortedSet[A]) {
302324
* universal equality provided by .equals.
303325
*/
304326
def ===(that: NonEmptySet[A]): Boolean =
305-
Eq[SortedSet[A]].eqv(set, that.toSortedSet)
327+
Eq[SortedSet[A]].eqv(toSortedSet, that.toSortedSet)
306328

307329
/**
308-
* Alias for [[size]]
330+
* Returns the number of elements in this set.
309331
*/
310-
def length: Int = size
311-
312-
override def toString: String = s"NonEmpty${set.toString}"
332+
def length: Int = toSortedSet.size
313333

314334
/**
315335
* 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]) {
320340
* scala> val as = NonEmptySet.of(1, 2, 3)
321341
* scala> val bs = NonEmptySet.of("A", "B", "C")
322342
* scala> as.zipWith(bs)(_ + _)
323-
* res0: cats.data.NonEmptySet[String] = NonEmptyTreeSet(1A, 2B, 3C)
343+
* res0: cats.data.NonEmptySet[String] = TreeSet(1A, 2B, 3C)
324344
* }}}
325345
*/
326346
def zipWith[B, C: Order](b: NonEmptySet[B])(f: (A, B) => C): NonEmptySet[C] =
327-
new NonEmptySet(SortedSet((set, b.toSortedSet).zipped.map(f).to: _*)(Order[C].toOrdering))
347+
NonEmptySetImpl.create(SortedSet((toSortedSet, b.toSortedSet).zipped.map(f).to: _*)(Order[C].toOrdering))
328348

329349
/**
330350
* Zips this `NonEmptySet` with its index.
331351
*/
332352
def zipWithIndex: NonEmptySet[(A, Int)] =
333-
new NonEmptySet(set.zipWithIndex)
353+
NonEmptySetImpl.create(toSortedSet.zipWithIndex)
334354
}
335355

336356
private[data] sealed abstract class NonEmptySetInstances {
@@ -393,17 +413,3 @@ private[data] sealed abstract class NonEmptySetInstances {
393413
}
394414
}
395415

396-
object NonEmptySet extends NonEmptySetInstances {
397-
def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] =
398-
if (as.nonEmpty) Option(new NonEmptySet(as)) else None
399-
400-
def fromSetUnsafe[A: Order](set: SortedSet[A]): NonEmptySet[A] =
401-
if (set.nonEmpty) new NonEmptySet(set)
402-
else throw new IllegalArgumentException("Cannot create NonEmptySet from empty set")
403-
404-
405-
def of[A: Order](a: A, as: A*): NonEmptySet[A] =
406-
new NonEmptySet(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a)
407-
def apply[A: Order](head: A, tail: SortedSet[A]): NonEmptySet[A] = new NonEmptySet(SortedSet(head)(Order[A].toOrdering) ++ tail)
408-
def one[A: Order](a: A): NonEmptySet[A] = new NonEmptySet(SortedSet(a)(Order[A].toOrdering))
409-
}

core/src/main/scala/cats/data/package.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ package object data {
1111
def NonEmptyStream[A](head: A, tail: A*): NonEmptyStream[A] =
1212
OneAnd(head, tail.toStream)
1313

14+
type NonEmptySet[A] = NonEmptySetImpl.Type[A]
15+
val NonEmptySet = NonEmptySetImpl
16+
1417
type ReaderT[F[_], A, B] = Kleisli[F, A, B]
1518
val ReaderT = Kleisli
1619

tests/src/test/scala/cats/tests/NonEmptySetSuite.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ class NonEmptySetSuite extends CatsSuite {
199199

200200
test("+ consistent with Set") {
201201
forAll { (nes: NonEmptySet[Int], i: Int) =>
202-
(nes + i).toSortedSet should === (nes.toSortedSet + i)
202+
(nes add i).toSortedSet should === (nes.toSortedSet + i)
203203
}
204204
}
205205

@@ -209,9 +209,8 @@ class NonEmptySetSuite extends CatsSuite {
209209
}
210210
}
211211

212-
test("NonEmptySet#size and length is consistent with Set#size") {
212+
test("NonEmptySet#length is consistent with Set#size") {
213213
forAll { nes: NonEmptySet[Int] =>
214-
nes.size should === (nes.toSortedSet.size)
215214
nes.length should === (nes.toSortedSet.size)
216215
}
217216
}

0 commit comments

Comments
 (0)