Skip to content

Commit

Permalink
Merge pull request #960 from adelbertc/reducible-tests
Browse files Browse the repository at this point in the history
More Reducible tests
  • Loading branch information
stew committed Apr 1, 2016
2 parents e49b2be + e80ccd2 commit a31a66f
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 5 deletions.
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/Reducible.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ import simulacrum.typeclass
* that we only need `Apply[G]` here, since we don't need to call
* `Applicative#pure` for a starting value.
*/
def sequence1_[G[_], A, B](fga: F[G[A]])(implicit G: Apply[G]): G[Unit] =
def sequence1_[G[_], A](fga: F[G[A]])(implicit G: Apply[G]): G[Unit] =
G.map(reduceLeft(fga)((x, y) => G.map2(x, y)((_, b) => b)))(_ => ())

/**
Expand Down
6 changes: 6 additions & 0 deletions laws/src/main/scala/cats/laws/ReducibleLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ trait ReducibleLaws[F[_]] extends FoldableLaws[F] {
B: Semigroup[B]
): IsEq[B] =
fa.reduceMap(f) <-> fa.reduceRightTo(f)((a, eb) => eb.map(f(a) |+| _)).value

def traverseConsistent[G[_]: Applicative, A, B](fa: F[A], f: A => G[B]): IsEq[G[Unit]] =
fa.traverse1_(f) <-> fa.traverse_(f)

def sequenceConsistent[G[_]: Applicative, A](fa: F[G[A]]): IsEq[G[Unit]] =
fa.sequence1_ <-> fa.sequence_
}

object ReducibleLaws {
Expand Down
9 changes: 7 additions & 2 deletions laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@ import org.scalacheck.Prop.forAll
trait ReducibleTests[F[_]] extends FoldableTests[F] {
def laws: ReducibleLaws[F]

def reducible[A: Arbitrary, B: Arbitrary](implicit
def reducible[G[_]: Applicative, A: Arbitrary, B: Arbitrary](implicit
ArbFA: Arbitrary[F[A]],
ArbFGA: Arbitrary[F[G[A]]],
ArbGB: Arbitrary[G[B]],
B: Monoid[B],
EqG: Eq[G[Unit]],
EqB: Eq[B]
): RuleSet =
new DefaultRuleSet(
name = "reducible",
parent = Some(foldable[A, B]),
"reduceLeftTo consistent with reduceMap" -> forAll(laws.reduceLeftToConsistentWithReduceMap[A, B] _),
"reduceRightTo consistent with reduceMap" -> forAll(laws.reduceRightToConsistentWithReduceMap[A, B] _)
"reduceRightTo consistent with reduceMap" -> forAll(laws.reduceRightToConsistentWithReduceMap[A, B] _),
"traverse1_ consistent with traverse_" -> forAll(laws.traverseConsistent[G, A, B] _),
"sequence1_ consistent with sequence_" -> forAll(laws.sequenceConsistent[G, A] _)
)
}

Expand Down
50 changes: 48 additions & 2 deletions tests/src/test/scala/cats/tests/OneAndTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import algebra.laws.{GroupLaws, OrderLaws}

import cats.data.{NonEmptyList, OneAnd}
import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests}
import cats.laws.discipline.arbitrary.oneAndArbitrary
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._

class OneAndTests extends CatsSuite {
// Lots of collections here.. telling ScalaCheck to calm down a bit
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
PropertyCheckConfig(maxSize = 5, minSuccessful = 20)

checkAll("OneAnd[List, Int]", OrderLaws[OneAnd[List, Int]].eqv)

checkAll("OneAnd[List, Int] with Option", TraverseTests[OneAnd[List, ?]].traverse[Int, Int, Int, Int, Option, Option])
checkAll("Traverse[OneAnd[List, A]]", SerializableTests.serializable(Traverse[OneAnd[List, ?]]))

checkAll("OneAnd[List, Int]", ReducibleTests[OneAnd[List, ?]].reducible[Int, Int])
checkAll("OneAnd[List, Int]", ReducibleTests[OneAnd[List, ?]].reducible[Option, Int, Int])
checkAll("Reducible[OneAnd[List, ?]]", SerializableTests.serializable(Reducible[OneAnd[List, ?]]))

implicit val iso = CartesianTests.Isomorphisms.invariant[OneAnd[ListWrapper, ?]](OneAnd.oneAndFunctor(ListWrapper.functor))
Expand Down Expand Up @@ -117,4 +121,46 @@ class OneAndTests extends CatsSuite {
nel.map(p).unwrap should === (list.map(p))
}
}

test("reduceLeft consistent with foldLeft") {
forAll { (nel: NonEmptyList[Int], f: (Int, Int) => Int) =>
nel.reduceLeft(f) should === (nel.tail.foldLeft(nel.head)(f))
}
}

test("reduceRight consistent with foldRight") {
forAll { (nel: NonEmptyList[Int], f: (Int, Eval[Int]) => Eval[Int]) =>
nel.reduceRight(f).value should === (nel.tail.foldRight(nel.head)((a, b) => f(a, Now(b)).value))
}
}

test("reduce consistent with fold") {
forAll { (nel: NonEmptyList[Int]) =>
nel.reduce should === (nel.fold)
}
}

test("reduce consistent with reduceK") {
forAll { (nel: NonEmptyList[Option[Int]]) =>
nel.reduce(SemigroupK[Option].algebra[Int]) should === (nel.reduceK)
}
}

test("reduceLeftToOption consistent with foldLeft + Option") {
forAll { (nel: NonEmptyList[Int], f: Int => String, g: (String, Int) => String) =>
val expected = nel.tail.foldLeft(Option(f(nel.head))) { (opt, i) =>
opt.map(s => g(s, i))
}
nel.reduceLeftToOption(f)(g) should === (expected)
}
}

test("reduceRightToOption consistent with foldRight + Option") {
forAll { (nel: NonEmptyList[Int], f: Int => String, g: (Int, Eval[String]) => Eval[String]) =>
val expected = nel.tail.foldRight(Option(f(nel.head))) { (i, opt) =>
opt.map(s => g(i, Now(s)).value)
}
nel.reduceRightToOption(f)(g).value should === (expected)
}
}
}
18 changes: 18 additions & 0 deletions tests/src/test/scala/cats/tests/ReducibleTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cats
package tests

import cats.data.{NonEmptyList, NonEmptyVector}
import cats.laws.discipline.{ReducibleTests, SerializableTests}
import cats.laws.discipline.arbitrary.oneAndArbitrary

class ReducibleTest extends CatsSuite {
// Lots of collections here.. telling ScalaCheck to calm down a bit
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
PropertyCheckConfig(maxSize = 5, minSuccessful = 20)

type NEVL[A] = NonEmptyList[NonEmptyVector[A]]
val nevlReducible: Reducible[NEVL] =
Reducible[NonEmptyList].compose[NonEmptyVector]
checkAll("NonEmptyList compose NonEmptyVector", ReducibleTests(nevlReducible).reducible[Option, Int, String])
checkAll("Reducible[NonEmptyList compose NonEmptyVector]", SerializableTests.serializable(nevlReducible))
}

0 comments on commit a31a66f

Please sign in to comment.