-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add NonEmptySet #2143
Add NonEmptySet #2143
Conversation
4f961a8
to
61f13d0
Compare
|
||
import scala.collection.immutable._ | ||
|
||
final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AnyVal
boxing can lead to a lot of reboxing when this is used in a generic context, which it would be in cats code since any of the typeclasses use it in generic context. I think we don't actually want AnyVal
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Man, we really need opaque types :(
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can also add concatMap[B](A => SortedSet[B]): SortedSet[B]
and I think it would be useful. We can also do concatMap[B](A => NonEmptySet[B]): NonEmptySet[B]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's no doubt useful.
|
||
import scala.collection.immutable._ | ||
|
||
final class NonEmptySet[A] private (val set: SortedSet[A]) extends AnyVal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should this be NonEmptySortedSet
? In the future we might want a Hash-based Set so just using Set
can be ambiguous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yeah in theory this is true, but that's quite a long name, we could just have NonEmptySet
and NonEmptyHashSet
maybe?
def reduce[AA >: A](implicit S: Semigroup[AA]): AA = | ||
S.combineAllOption(set).get | ||
|
||
def toSet: SortedSet[A] = set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about toSortedSet
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need this one vs of
? I also like singleton
or something better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took the name from NonEmptyList.one
, but I have no preference wrt naming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I vote for consistent API
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]] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a semilattice isn't it (it is commutative, no?) Am I missing something?
checkAll("NonEmptySet[String]", BandTests[NonEmptySet[String]].band) | ||
checkAll("NonEmptySet[String]", EqTests[NonEmptySet[String]].eqv) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we test nes.forall { v => Order[Int].lteq(nes.head, v) }
Codecov Report
@@ Coverage Diff @@
## master #2143 +/- ##
=========================================
+ Coverage 94.66% 94.76% +0.1%
=========================================
Files 328 331 +3
Lines 5548 5659 +111
Branches 213 208 -5
=========================================
+ Hits 5252 5363 +111
Misses 296 296
Continue to review full report at Codecov.
|
97f53df
to
34aa71d
Compare
Maybe we should implement these with newtypes similar to what Alex did here: https://github.com/typelevel/cats-effect/pull/115/files#diff-c01f002766a28f49dfe20d6b1f0afd50R300 Thoughts? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
I am also fine with renaming it to |
|
||
final class NonEmptySet[A] private (val set: SortedSet[A]) { | ||
|
||
private implicit def ordering: Ordering[A] = set.ordering |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be val
s
57b2792
to
bba1a5d
Compare
private[cats] type Type[A] <: Base with Tag | ||
} | ||
|
||
private[data] object NonEmptySetImpl extends NonEmptySetInstances with Newtype { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shall we override the toString as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't really do that, since we can't actually add methods on the type, but only through implicit extension methods. This means that it will always call the toString
of the underlying type, we can of course use show
though.
|
||
import scala.collection.immutable._ | ||
|
||
trait Newtype { self => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's staying public (or even package private) then let's move NewType
to a new file.
aa29d79
to
b2109ee
Compare
@johnynek Care to re-review? :) |
e197ab3
to
86d394a
Compare
86d394a
to
65fe31b
Compare
|
||
|
||
def of[A: Order](a: A, as: A*): NonEmptySet[A] = | ||
create(SortedSet(a)(Order[A].toOrdering) ++ SortedSet(as: _*)(Order[A].toOrdering) + a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this adding a
to the set twice? (Initializing with SortedSet(a)
and then calling + a
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is making two separate calls to toOrdering
. If the ordering is needed twice, a slight optimization would be to store the result of toOrdering
. Also, on a more minor note, the Order[A]
instance is being summoned twice, so if the implicit instance is defined by a def
this will result in unnecessary allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're absolutely right, this is a really stupid mistake, not sure how it slipped through, probably refactored it one to many times 🙈
package cats | ||
package data | ||
|
||
trait Newtype { self => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some documentation that motivates this trait
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good idea :)
@@ -0,0 +1,410 @@ | |||
/* | |||
* Copyright (c) 2018 Luka Jacobowitz |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason that this file has this header but Newtype.scala
doesn't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, just copy and paste, Kai suggested we try to standardize on something in a future PR, possibly with sbt-header or something like that :)
s.asInstanceOf[SortedSet[A]] | ||
|
||
|
||
def fromSet[A: Order](as: SortedSet[A]): Option[NonEmptySet[A]] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This takes an implicit Order[A]
but then doesn't actually use it, does it? That seems misleading to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's true, it assumes there's an Ordering
for A
, maybe best to get rid of it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the last bit that prevents this PR being merged?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's done :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on build green
@ceedubs any other issues? |
@johnynek were all your feedback addressed? |
* 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as https://github.com/typelevel/cats/pull/2141/files#r173359573
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are some other places that this applies to this PR. I don't necessarily consider this a blocker, but I'd like us to at least decide what we want to do about this before we publish a release that has the name of the implicit parameter fixed (I think that the auto-generated one will be something like ev1
).
I removed the context bounds @ceedubs :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a bunch @LukaJCB. Just one remaining small question and then this LGTM.
* 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 => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense for this to be a public trait if Base
and Tag
are both private[data]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debatable IMO, but to me those two are implementation details, you're never actually going to want to access these, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right I guess that in order to create your own type that uses Newtype
you don't actually have to set Base
and Tag
, right?
I guess I'm partially hesitant to expose Newtype
publicly because there are a lot of approaches to newtypes in the scala world and I'm not sure whether we want to take on the burden of keeping this one around and stable in cats. Especially since it's an internal implementation detail that isn't really used as an abstraction and is trivial enough that people could easily copy/paste it if they wanted it. I'd be inclined to make it private for now and if it works out well and people want it to be exposed publicly it's easier to open it up later than to make it private later. Admittedly I'm sometimes overly paranoid about this sort of thing...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be okay with making them private :)
Pretty much the same situation as #2141