-
Notifications
You must be signed in to change notification settings - Fork 346
Lots of documentation + test fixes, updates #579
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
Conversation
This fails binary compatibility... but my preference is to merge anyway and do the appropriate version bump, because the methods that this removes were broken. |
cc @johnynek |
83de5ec
to
848a78f
Compare
* } | ||
* | ||
*/ | ||
def predessibleLaws[T: Predecessible: Arbitrary]: Prop = forAll { (t: T, size: Short) => |
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 annoying - this is the ONLY reason this change is binary-incompatible. @isnotinvain, can you check if Twitter is using this anywhere internally?
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.
Let's just call the new one.
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.
💥 done
Okay, I broke out the real binary incompatibility, the future changes, into #580. This change is still binary-incompatible, but only because of the super-annoying typo, |
|
||
// Zero should have the property that it <= all T | ||
def monoid[T](zero: => T)(implicit ord: Ordering[T]): Monoid[Max[T]] = | ||
Monoid.from(Max(zero)) { (l, r) => if (ord.gteq(l.get, r.get)) l else r } |
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.
Would be nice to override sumOption while we are at it can avoid a lot of reboxing.
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.
done
implicit def equiv[T](implicit eq: Equiv[T]): Equiv[Max[T]] = Equiv.by(_.get) | ||
|
||
implicit def semigroup[T](implicit ord: Ordering[T]): Semigroup[Max[T]] = | ||
Semigroup.from[Max[T]] { (l, r) => if (ord.gteq(l.get, r.get)) l else r } |
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.
Let's do sumOption on this semigroup.
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.
Done.
implicit def equiv[T](implicit eq: Equiv[T]): Equiv[Min[T]] = Equiv.by(_.get) | ||
|
||
// Zero should have the property that it >= all T | ||
def monoid[T](zero: => T)(implicit ord: Ordering[T]): Monoid[Min[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.
Add sumOption
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.
done
Monoid.from(Min(zero)) { (l, r) => if (ord.lteq(l.get, r.get)) l else r } | ||
|
||
implicit def semigroup[T](implicit ord: Ordering[T]): Semigroup[Min[T]] = | ||
Semigroup.from[Min[T]] { (l, r) => if (ord.lteq(l.get, r.get)) l else r } |
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.
Add sumOption.
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.
done
* property("blah is predecessible") { predecessibleLaws[MyType] } | ||
* }}} | ||
*/ | ||
def predecessibleLaws[T: Predecessible: Arbitrary]: Prop = |
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 the old one with the broken name, mark it deprecated and just call this one? Let's not break binary compat if we don't have to.
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.
done
* } | ||
* | ||
*/ | ||
def predessibleLaws[T: Predecessible: Arbitrary]: Prop = forAll { (t: T, size: Short) => |
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.
Let's just call the new one.
import org.scalacheck.Prop._ | ||
|
||
class DecayedValueLaws extends CheckProperties { | ||
import org.scalacheck.Gen.choose | ||
class DecayedValueSpec extends CheckProperties { |
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.
Why change the name of the test?
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 was trying to make them all consistent - we have Laws
, Spec
, Specification
, Tests
... what do you think of Spec
for everything? Laws
is fine too, but I'd love them to match.
I don't like Spec personally, but also don't want to open a lot of naming
debates on things that exist so my bias would be to leave well enough alone.
I particularly hate the WordSpec test style. It is a monstrosity that I
hope I never have to see again.
I like Lawful things, so Laws is generally good if there are indeed Laws. I
think tests and laws are just two different things. It would be nice if
laws totally constrained the thing in question, but it usually is not
enough so we add some rather ad-hoc tests.
We could use laws for code that just encode particular laws and Tests for
he actual test fixtures that run the laws.
But so many people have their own stylistic preferences I am a bit bearish
on such changes because tomorrow someone with a different t style may have
a different opinion. Better to make no change without a documented (and
ideally CI checked) policy around it.
…On Sun, Nov 27, 2016 at 14:09 Sam Ritchie ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In
algebird-test/src/test/scala/com/twitter/algebird/DecayedValueSpec.scala
<#579>:
> import org.scalacheck.Prop._
-class DecayedValueLaws extends CheckProperties {
- import org.scalacheck.Gen.choose
+class DecayedValueSpec extends CheckProperties {
I was trying to make them all consistent - we have Laws, Spec,
Specification, Tests... what do you think of Spec for everything? Laws is
fine too, but I'd love them to match.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#579>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAEJdmEkEhzcmcynqd50BktRJr-cwV4Uks5rChvCgaJpZM4K9R9B>
.
|
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.
Okay @johnynek, I think that covers all changes!
* } | ||
* | ||
*/ | ||
def predessibleLaws[T: Predecessible: Arbitrary]: Prop = forAll { (t: T, size: Short) => |
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.
💥 done
implicit def equiv[T](implicit eq: Equiv[T]): Equiv[Max[T]] = Equiv.by(_.get) | ||
|
||
implicit def semigroup[T](implicit ord: Ordering[T]): Semigroup[Max[T]] = | ||
Semigroup.from[Max[T]] { (l, r) => if (ord.gteq(l.get, r.get)) l else r } |
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.
Done.
|
||
// Zero should have the property that it <= all T | ||
def monoid[T](zero: => T)(implicit ord: Ordering[T]): Monoid[Max[T]] = | ||
Monoid.from(Max(zero)) { (l, r) => if (ord.gteq(l.get, r.get)) l else r } |
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.
done
implicit def equiv[T](implicit eq: Equiv[T]): Equiv[Min[T]] = Equiv.by(_.get) | ||
|
||
// Zero should have the property that it >= all T | ||
def monoid[T](zero: => T)(implicit ord: Ordering[T]): Monoid[Min[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.
done
Monoid.from(Min(zero)) { (l, r) => if (ord.lteq(l.get, r.get)) l else r } | ||
|
||
implicit def semigroup[T](implicit ord: Ordering[T]): Semigroup[Min[T]] = | ||
Semigroup.from[Min[T]] { (l, r) => if (ord.lteq(l.get, r.get)) l else r } |
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.
done
* property("blah is predecessible") { predecessibleLaws[MyType] } | ||
* }}} | ||
*/ | ||
def predecessibleLaws[T: Predecessible: Arbitrary]: Prop = |
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.
done
@johnynek, I changed all the |
c7ae3a5
to
7ee5dc9
Compare
Current coverage is 65.53% (diff: 91.87%)@@ develop #579 diff @@
==========================================
Files 111 121 +10
Lines 4572 4694 +122
Methods 4154 4284 +130
Messages 0 0
Branches 379 371 -8
==========================================
+ Hits 2945 3076 +131
+ Misses 1627 1618 -9
Partials 0 0
|
@@ -88,8 +88,10 @@ object BaseProperties { | |||
isAssociativeEq[T, T](Equiv[T].equiv _) && semigroupSumWorks[T] | |||
|
|||
def commutativeSemigroupLawsEq[T: Semigroup: Arbitrary](eqfn: (T, T) => Boolean) = | |||
isAssociativeEq[T, T](eqfn) && isCommutativeEq[T](eqfn) |
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 was missing semigroupSumWorks[T]
.
|
||
case class FirstAggregator[T]() extends Aggregator[T, T, T] { | ||
def prepare(v: T) = v | ||
val semigroup: Semigroup[T] = Semigroup.from { (l: T, r: T) => l } |
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 almost certainly want the sumOption
on this one since we can skip enumerating the iterator in this case (in fact, sumOption
is just headOption
but for some terrible reason, TraversableOnce[T]
does not have that.
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.
fixed
|
||
case class LastAggregator[T]() extends Aggregator[T, T, T] { | ||
def prepare(v: T) = v | ||
val semigroup: Semigroup[T] = Semigroup.from { (l: T, r: T) => r } |
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 have to enumerate everything for last, so this is fine without sumOption.
} | ||
|
||
private[algebird] sealed abstract class FirstInstances { | ||
implicit def semigroup[T]: Semigroup[First[T]] = new Semigroup[First[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.
we could share this code in object First
:
def firstSemigroup[T]: Semigroup[T] = ...
then implicit def semigroup[T]: Semigroup[First[T]] = firstSemigroup[First[T]]
and below you can share the sumOption
in the aggregator.
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.
nice, done
}) | ||
|
||
// TODO: Replace with | ||
// scala.collection.mutable.MutableMethods.iteratorCompare when we |
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 seems like a busted comment. What does cats have to do with scala.collection? I assume this may be referring to some method in cats? Is it in cats.kernel?
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.
yup, busted.
|
||
case class MaxAggregator[T](implicit ord: Ordering[T]) extends Aggregator[T, T, T] { | ||
def prepare(v: T) = v | ||
val semigroup = Semigroup.from { (l: T, r: T) => ord.max(l, r) } |
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.
should we move this up to being on the companion:
def maxSemigroup[T](implicit ord: Ordering[T]): Semigroup[T] = ...
then call it here? People might want to reuse this.
Also, note this is a Semilattice
when we merge algebra
we can add that.
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.
fixed
} | ||
|
||
property("Last[Int] is a Semigroup") { semigroupLaws[Last[Int]] } | ||
property("Last[String] is a Semigroup") { semigroupLaws[Last[String]] } |
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.
don't think we really need two here, for same reason as I mentioned.
|
||
property("Max.{ +, max } works on ints") { maxTest[Int] } | ||
|
||
property("Max should work on non-monoid types like String") { maxTest[String] } |
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.
String is a monoid. You mean non-commutative?
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 catch - I was using the same tests in MinLaws
and MaxLaws
. I'll change types. I was looking to exercise a semigroup, since it's defined differently from the monoid
} | ||
|
||
property("Max[String] is a commutative semigroup") { | ||
commutativeSemigroupLaws[Max[String]] |
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 is a commutativeMonoid
here too, right? ""
is the zero
?
Min[String]
is the one that is just a semigroup because there is no max string.
} | ||
|
||
property("Max[String] is a commutative monoid") { | ||
commutativeMonoidLaws[Max[String]] |
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 duplicated above, I think the first should be Min[String]
*/ | ||
def getMoments(xs: List[Double]): Moments = | ||
xs.foldLeft(MomentsGroup.zero) { (m, x) => MomentsGroup.plus(m, Moments(x)) } | ||
object MomentsSpec { |
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's going on with this object? Can we have a comment if it is really needed?
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, deleted, that was waste from a shared function I had then killed. good catch.
def isAssociative[T: Semigroup: Arbitrary]: Prop = isAssociativeDifferentTypes[T, T] | ||
|
||
def isAssociativeDifferentTypes[T: Semigroup, U <: T: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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 just Equiv.universal
. Can we use that and deprecate defaultEq
?
|
||
// Commutative | ||
def isCommutative[T: Semigroup: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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.
Equiv.universal
def semigroupSumWorks[T: Semigroup: Arbitrary: Equiv] = 'semigroupSumWorks |: forAll { (head: T, tail: List[T]) => | ||
Equiv[T].equiv(Semigroup.sumOption(head :: tail).get, tail.foldLeft(head)(Semigroup.plus(_, _))) | ||
def semigroupLaws[T: Semigroup: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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.
Equiv.universal
isAssociativeEquiv[T, T] && semigroupSumWorks[T] | ||
|
||
// Commutative Semigroup Laws | ||
def commutativeSemigroupLaws[T: Semigroup: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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.
Equiv.universal
val prodZero = !monT.isNonZero(Ring.times(a, b)) | ||
(Monoid.isNonZero(a) && Monoid.isNonZero(b)) || prodZero | ||
def validZero[T: Monoid: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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.
Equiv.universal
val zero = mon.zero | ||
eqfn(a, mon.plus(a, zero)) && eqfn(mon.plus(zero, a), a) && eqfn(mon.plus(a, zero), mon.plus(zero, a)) | ||
def monoidLaws[T: Monoid: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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.
Equiv.univeral
def commutativeMonoidLawsEq[T: Monoid: Arbitrary](eqfn: (T, T) => Boolean) = | ||
monoidLawsEq[T](eqfn) && isCommutativeEq[T](eqfn) | ||
def commutativeMonoidLaws[T: Monoid: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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.
Equiv.universal
hasAdditiveInversesDifferentTypes[T, T] | ||
|
||
def groupLaws[T: Group: Arbitrary]: Prop = { | ||
implicit val eq: Equiv[T] = Equiv.fromFunction(defaultEq) |
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.
universal
Looks great, @sritchie Thanks for the hard work on this one! |
This PR:
SucPredLaws.scala
out into two filesDecayedValue
,AveragedValue
,Moments
andAdjoinedUnit[T]
toGen
andArbitrary
AggregationMonoids.scala
out into separate filesGen.scala
with generators for Algebird data structuresArbitrary.scala
with arbitrary instances for Algebird data structuresInterval
,First
,Last
,Min
,Max
andExpHist
into these objectsOrderedSemigroup.scala
into their own files+
operator to min, max, first, lastmin
operator toMin
andmax
operator toMax
FirstAggregator
andLastAggregator
Monoid[Max[Vector[T]]]
andMonoid[Max[Stream[T]]]
tut
-based documentation for first, last, min and max.