Skip to content

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

Merged
merged 16 commits into from
Nov 29, 2016
Merged

Conversation

sritchie
Copy link
Collaborator

@sritchie sritchie commented Nov 27, 2016

This PR:

  • Breaks SucPredLaws.scala out into two files
  • adds generators and arbitrary instances for DecayedValue, AveragedValue, Moments and AdjoinedUnit[T] to Gen and Arbitrary
  • Breaks AggregationMonoids.scala out into separate files
  • Creates Gen.scala with generators for Algebird data structures
  • Creates Arbitrary.scala with arbitrary instances for Algebird data structures
  • Moves the generators for Interval, First, Last, Min, Max and ExpHist into these objects
  • moves the data structures from OrderedSemigroup.scala into their own files
  • adds full tests for min, max, first, last
  • adds + operator to min, max, first, last
  • adds min operator to Min and max operator to Max
  • adds FirstAggregator and LastAggregator
  • adds Monoid[Max[Vector[T]]] and Monoid[Max[Stream[T]]]
  • adds tut-based documentation for first, last, min and max.

@sritchie sritchie changed the title More documentation fixes (comes after first/last etc) More documentation fixes (comes after 578) Nov 27, 2016
@sritchie
Copy link
Collaborator Author

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.

@sritchie
Copy link
Collaborator Author

cc @johnynek

@sritchie sritchie force-pushed the sritchie/next_doc_round branch from 83de5ec to 848a78f Compare November 27, 2016 23:02
* }
*
*/
def predessibleLaws[T: Predecessible: Arbitrary]: Prop = forAll { (t: T, size: Short) =>
Copy link
Collaborator Author

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?

Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💥 done

@sritchie
Copy link
Collaborator Author

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, predessibleLaws. I'd bet 100 bucks that no one is using that test, but maybe it's still best to have this force our algebra changes and push out a new binary-incompatible version.


// 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 }
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 }
Copy link
Collaborator

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.

Copy link
Collaborator Author

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]] =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add sumOption

Copy link
Collaborator Author

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 }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add sumOption.

Copy link
Collaborator Author

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 =
Copy link
Collaborator

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.

Copy link
Collaborator Author

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) =>
Copy link
Collaborator

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 {
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

@johnynek
Copy link
Collaborator

johnynek commented Nov 28, 2016 via email

Copy link
Collaborator Author

@sritchie sritchie left a 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) =>
Copy link
Collaborator Author

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 }
Copy link
Collaborator Author

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 }
Copy link
Collaborator Author

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]] =
Copy link
Collaborator Author

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 }
Copy link
Collaborator Author

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 =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@sritchie
Copy link
Collaborator Author

@johnynek, I changed all the *Spec files I created to *Laws, since they all contain only scalacheck properties. I'll leave old files unchanged from now on, or change to Laws if they in fact only contain scalacheck.

@sritchie sritchie force-pushed the sritchie/next_doc_round branch from c7ae3a5 to 7ee5dc9 Compare November 28, 2016 01:50
@codecov-io
Copy link

codecov-io commented Nov 28, 2016

Current coverage is 65.53% (diff: 91.87%)

Merging #579 into develop will increase coverage by 1.11%

@@            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          

Powered by Codecov. Last update c91e1fb...0be5b0e

@@ -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)
Copy link
Contributor

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 }
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 }
Copy link
Collaborator

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]] {
Copy link
Collaborator

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.

Copy link
Collaborator Author

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
Copy link
Collaborator

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?

Copy link
Collaborator Author

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) }
Copy link
Collaborator

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.

Copy link
Collaborator Author

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]] }
Copy link
Collaborator

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] }
Copy link
Collaborator

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?

Copy link
Collaborator Author

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]]
Copy link
Collaborator

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]]
Copy link
Collaborator

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 {
Copy link
Collaborator

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?

Copy link
Collaborator Author

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)
Copy link
Collaborator

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)
Copy link
Collaborator

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)
Copy link
Collaborator

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)
Copy link
Collaborator

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)
Copy link
Collaborator

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)
Copy link
Collaborator

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)
Copy link
Collaborator

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

universal

@sritchie sritchie changed the title More documentation fixes (comes after 578) Lots of documentation + test fixes, updates Nov 29, 2016
@sritchie sritchie modified the milestone: 0.13.0 Nov 29, 2016
@johnynek
Copy link
Collaborator

Looks great, @sritchie Thanks for the hard work on this one!

@johnynek johnynek merged commit 2c893e3 into develop Nov 29, 2016
@johnynek johnynek deleted the sritchie/next_doc_round branch November 29, 2016 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants