From c0a3cc270cca74326de621415d6caee582e6c6d7 Mon Sep 17 00:00:00 2001 From: Kailuo Wang Date: Fri, 20 Oct 2017 16:42:04 -0400 Subject: [PATCH 1/2] moved alleycats in --- .../src/main/scala/alleycats/ConsK.scala | 20 +++++ .../src/main/scala/alleycats/Empty.scala | 36 ++++++++ .../src/main/scala/alleycats/EmptyK.scala | 22 +++++ .../src/main/scala/alleycats/Extract.scala | 28 ++++++ .../src/main/scala/alleycats/One.scala | 24 +++++ .../src/main/scala/alleycats/Pure.scala | 29 +++++++ .../src/main/scala/alleycats/Zero.scala | 24 +++++ .../src/main/scala/alleycats/std/all.scala | 13 +++ .../main/scala/alleycats/std/iterable.scala | 38 ++++++++ .../src/main/scala/alleycats/std/list.scala | 22 +++++ .../src/main/scala/alleycats/std/option.scala | 16 ++++ .../src/main/scala/alleycats/std/set.scala | 87 +++++++++++++++++++ .../src/main/scala/alleycats/std/try.scala | 52 +++++++++++ .../src/main/scala/alleycats/syntax/all.scala | 5 ++ .../main/scala/alleycats/syntax/empty.scala | 13 +++ .../scala/alleycats/syntax/foldable.scala | 14 +++ .../laws/discipline/FlatMapRecTests.scala | 29 +++++++ .../alleycats/tests/AlleycatsSuite.scala | 55 ++++++++++++ .../scala/alleycats/tests/CatsEquality.scala | 30 +++++++ .../scala/alleycats/tests/IterableTests.scala | 27 ++++++ .../test/scala/alleycats/tests/SetTests.scala | 14 +++ build.sbt | 62 ++++++++++++- 22 files changed, 656 insertions(+), 4 deletions(-) create mode 100644 alleycats-core/src/main/scala/alleycats/ConsK.scala create mode 100644 alleycats-core/src/main/scala/alleycats/Empty.scala create mode 100644 alleycats-core/src/main/scala/alleycats/EmptyK.scala create mode 100644 alleycats-core/src/main/scala/alleycats/Extract.scala create mode 100644 alleycats-core/src/main/scala/alleycats/One.scala create mode 100644 alleycats-core/src/main/scala/alleycats/Pure.scala create mode 100644 alleycats-core/src/main/scala/alleycats/Zero.scala create mode 100644 alleycats-core/src/main/scala/alleycats/std/all.scala create mode 100644 alleycats-core/src/main/scala/alleycats/std/iterable.scala create mode 100644 alleycats-core/src/main/scala/alleycats/std/list.scala create mode 100644 alleycats-core/src/main/scala/alleycats/std/option.scala create mode 100644 alleycats-core/src/main/scala/alleycats/std/set.scala create mode 100644 alleycats-core/src/main/scala/alleycats/std/try.scala create mode 100644 alleycats-core/src/main/scala/alleycats/syntax/all.scala create mode 100644 alleycats-core/src/main/scala/alleycats/syntax/empty.scala create mode 100644 alleycats-core/src/main/scala/alleycats/syntax/foldable.scala create mode 100644 alleycats-laws/src/main/scala/alleycats/laws/discipline/FlatMapRecTests.scala create mode 100644 alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala create mode 100644 alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala create mode 100644 alleycats-tests/src/test/scala/alleycats/tests/IterableTests.scala create mode 100644 alleycats-tests/src/test/scala/alleycats/tests/SetTests.scala diff --git a/alleycats-core/src/main/scala/alleycats/ConsK.scala b/alleycats-core/src/main/scala/alleycats/ConsK.scala new file mode 100644 index 0000000000..021fddbb29 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/ConsK.scala @@ -0,0 +1,20 @@ +package alleycats + +import cats.SemigroupK +import export.imports +import simulacrum.typeclass + +@typeclass trait ConsK[F[_]] { + def cons[A](hd: A, tl: F[A]): F[A] +} + +object ConsK extends ConsK0 { + implicit def pureSemigroupKIsConsK[F[_]](implicit p: Pure[F], s: SemigroupK[F]): ConsK[F] = + new ConsK[F] { + def cons[A](hd: A, tl: F[A]): F[A] = s.combineK(p.pure(hd), tl) + } +} + +@imports[ConsK] +trait ConsK0 + diff --git a/alleycats-core/src/main/scala/alleycats/Empty.scala b/alleycats-core/src/main/scala/alleycats/Empty.scala new file mode 100644 index 0000000000..b74ea2b052 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/Empty.scala @@ -0,0 +1,36 @@ +package alleycats + +import cats.{Eq, Monoid} +import cats.syntax.eq._ +import export.imports +import simulacrum.typeclass +import scala.collection.generic.CanBuildFrom + +@typeclass trait Empty[A] { + def empty: A + + def isEmpty(a: A)(implicit ev: Eq[A]): Boolean = + empty === a + + def nonEmpty(a: A)(implicit ev: Eq[A]): Boolean = + empty =!= a +} + +object Empty extends EmptyInstances0 { + def apply[A](a: => A): Empty[A] = + new Empty[A] { lazy val empty: A = a } +} + +trait EmptyInstances0 extends EmptyInstances1 { + implicit def iterableIsEmpty[CC[X] <: Iterable[X], A](implicit cbf: CanBuildFrom[CC[A], A, CC[A]]): Empty[CC[A]] = + Empty(cbf().result) +} + +trait EmptyInstances1 extends EmptyInstances2 { + // If Monoid extended Empty then this could be an exported subclass instance provided by Monoid + implicit def monoidIsEmpty[A: Monoid]: Empty[A] = + Empty(Monoid[A].empty) +} + +@imports[Empty] +trait EmptyInstances2 diff --git a/alleycats-core/src/main/scala/alleycats/EmptyK.scala b/alleycats-core/src/main/scala/alleycats/EmptyK.scala new file mode 100644 index 0000000000..85596d3f7a --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/EmptyK.scala @@ -0,0 +1,22 @@ +package alleycats + +import export._ +import simulacrum.typeclass + +@typeclass trait EmptyK[F[_]] { self => + def empty[A]: F[A] + + def synthesize[A]: Empty[F[A]] = + new Empty[F[A]] { + def empty: F[A] = self.empty[A] + } +} + +@imports[EmptyK] +object EmptyK + +@exports +object EmptyKInstances { + @export(Instantiated) + implicit def instantiate[F[_], T](implicit ekf: EmptyK[F]): Empty[F[T]] = ekf.synthesize[T] +} diff --git a/alleycats-core/src/main/scala/alleycats/Extract.scala b/alleycats-core/src/main/scala/alleycats/Extract.scala new file mode 100644 index 0000000000..d58097ddda --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/Extract.scala @@ -0,0 +1,28 @@ +package alleycats + +import cats.{CoflatMap, Comonad} +import export.imports +import simulacrum.typeclass + +@typeclass trait Extract[F[_]] { + def extract[A](fa: F[A]): A +} + +object Extract extends Extract0 { + // Ideally this would be an exported subclass instance provided by Comonad + implicit def comonadIsExtract[F[_]](implicit ev: Comonad[F]): Extract[F] = + new Extract[F] { + def extract[A](fa: F[A]): A = ev.extract(fa) + } + + // Ideally this would be an instance exported to Comonad + implicit def extractCoflatMapIsComonad[F[_]](implicit e: Extract[F], cf: CoflatMap[F]): Comonad[F] = + new Comonad[F] { + def extract[A](fa: F[A]): A = e.extract(fa) + override def map[A, B](fa: F[A])(f: A => B): F[B] = cf.map(fa)(f) + def coflatMap[A, B](fa: F[A])(f: F[A] => B): F[B] = cf.coflatMap(fa)(f) + } +} + +@imports[Extract] +trait Extract0 diff --git a/alleycats-core/src/main/scala/alleycats/One.scala b/alleycats-core/src/main/scala/alleycats/One.scala new file mode 100644 index 0000000000..ec213489b2 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/One.scala @@ -0,0 +1,24 @@ +package alleycats + +import cats.{Eq, Monoid} +import cats.syntax.eq._ +import export.imports +import simulacrum.typeclass + +@typeclass trait One[A] { + def one: A + + def isOne(a: A)(implicit ev: Eq[A]): Boolean = + one === a + + def nonOne(a: A)(implicit ev: Eq[A]): Boolean = + one =!= a +} + +object One extends One0 { + def apply[A](a: => A): One[A] = + new One[A] { lazy val one: A = a } +} + +@imports[One] +trait One0 diff --git a/alleycats-core/src/main/scala/alleycats/Pure.scala b/alleycats-core/src/main/scala/alleycats/Pure.scala new file mode 100644 index 0000000000..95d02c7fc1 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/Pure.scala @@ -0,0 +1,29 @@ +package alleycats + +import cats.{Applicative, FlatMap, Monad} +import export.imports +import simulacrum.typeclass + +@typeclass trait Pure[F[_]] { + def pure[A](a: A): F[A] +} + +object Pure extends Pure0 { + // Ideally this would be an exported subclass instance provided by Applicative + implicit def applicativeIsPure[F[_]](implicit ev: Applicative[F]): Pure[F] = + new Pure[F] { + def pure[A](a: A): F[A] = ev.pure(a) + } + + // Ideally this would be an instance exported to Monad + implicit def pureFlatMapIsMonad[F[_]](implicit p: Pure[F], fm: FlatMap[F]): Monad[F] = + new Monad[F] { + def pure[A](a: A): F[A] = p.pure(a) + override def map[A, B](fa: F[A])(f: A => B): F[B] = fm.map(fa)(f) + def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = fm.flatMap(fa)(f) + def tailRecM[A, B](a: A)(f: (A) => F[Either[A, B]]): F[B] = fm.tailRecM(a)(f) + } +} + +@imports[Pure] +trait Pure0 diff --git a/alleycats-core/src/main/scala/alleycats/Zero.scala b/alleycats-core/src/main/scala/alleycats/Zero.scala new file mode 100644 index 0000000000..e64f28e5a5 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/Zero.scala @@ -0,0 +1,24 @@ +package alleycats + +import cats.Eq +import cats.syntax.eq._ +import export.imports +import simulacrum.typeclass + +@typeclass trait Zero[A] { + def zero: A + + def isZero(a: A)(implicit ev: Eq[A]): Boolean = + zero === a + + def nonZero(a: A)(implicit ev: Eq[A]): Boolean = + zero =!= a +} + +object Zero extends Zero0 { + def apply[A](a: => A): Zero[A] = + new Zero[A] { lazy val zero: A = a } +} + +@imports[Zero] +trait Zero0 diff --git a/alleycats-core/src/main/scala/alleycats/std/all.scala b/alleycats-core/src/main/scala/alleycats/std/all.scala new file mode 100644 index 0000000000..ad3f85d0cb --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/std/all.scala @@ -0,0 +1,13 @@ +package alleycats +package std + +import export._ + +@reexports( + EmptyKInstances, + ListInstances, + OptionInstances, + SetInstances, + TryInstances, + IterableInstances +) object all extends LegacySetInstances with LegacyTryInstances with LegacyIterableInstances diff --git a/alleycats-core/src/main/scala/alleycats/std/iterable.scala b/alleycats-core/src/main/scala/alleycats/std/iterable.scala new file mode 100644 index 0000000000..c58e551d22 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/std/iterable.scala @@ -0,0 +1,38 @@ +package alleycats +package std + +import cats.{Eval, Foldable} +import export._ + +@reexports(IterableInstances) +object iterable extends LegacyIterableInstances + +@exports +object IterableInstances { + @export(Orphan) + implicit val exportIterableFoldable: Foldable[Iterable] = + new Foldable[Iterable] { + override def foldLeft[A, B](fa: Iterable[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) + + // based upon foldRight of List in Cats + override def foldRight[A, B](fa: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + def loop(as: Iterable[A]): Eval[B] = + if (as.isEmpty) + lb + else { + val h = as.head + val t = as.tail + f(h, Eval.defer(loop(t))) + } + Eval.defer(loop(fa)) + } + + } + +} + +// TODO: remove when cats.Foldable support export-hook +trait LegacyIterableInstances { + implicit def legacyIterableFoldable(implicit e: ExportOrphan[Foldable[Iterable]]): Foldable[Iterable] = e.instance + +} diff --git a/alleycats-core/src/main/scala/alleycats/std/list.scala b/alleycats-core/src/main/scala/alleycats/std/list.scala new file mode 100644 index 0000000000..740278c7a0 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/std/list.scala @@ -0,0 +1,22 @@ +package alleycats +package std + +import export._ + +@reexports(ListInstances) +object list + +@exports +object ListInstances { + @export(Orphan) + implicit val exportListEmptyK: EmptyK[List] = + new EmptyK[List] { + def empty[A]: List[A] = Nil + } + + @export(Orphan) + implicit val exportListConsK: ConsK[List] = + new ConsK[List] { + def cons[A](hd: A, tl: List[A]): List[A] = hd :: tl + } +} diff --git a/alleycats-core/src/main/scala/alleycats/std/option.scala b/alleycats-core/src/main/scala/alleycats/std/option.scala new file mode 100644 index 0000000000..25a1cfeb33 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/std/option.scala @@ -0,0 +1,16 @@ +package alleycats +package std + +import export._ + +@reexports(OptionInstances) +object option + +@exports +object OptionInstances { + @export(Orphan) + implicit val exportOptionEmptyK: EmptyK[Option] = + new EmptyK[Option] { + def empty[A]: Option[A] = None + } +} diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala new file mode 100644 index 0000000000..8af2432628 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -0,0 +1,87 @@ +package alleycats.std + +import cats.{Applicative, Eval, Foldable, Monad, Traverse} +import export._ + +import scala.annotation.tailrec + +@exports +object SetInstances { + // This method advertises parametricity, but relies on using + // universal hash codes and equality, which hurts our ability to + // rely on free theorems. + // + // Another problem some people have pointed out is the + // non-associativity of map when using impure functions. For + // example, consider the following expressions: + // + // import scala.util.Random + // + // val f = (_: Int) => 1 + // val g = (_: Int) => Random.nextInt + // + // Set(1, 2, 3).map(f).map(g) + // Set(1, 2, 3).map(f andThen g) + // + // The first Set will contain one random number, and the second will + // contain three. Since `g` is not a function (speaking strictly) + // this would not be considered a law violation, but it still makes + // people uncomfortable. + @export(Orphan) + implicit val setMonad: Monad[Set] = + new Monad[Set] { + def pure[A](a: A): Set[A] = Set(a) + override def map[A, B](fa: Set[A])(f: A => B): Set[B] = fa.map(f) + def flatMap[A, B](fa: Set[A])(f: A => Set[B]): Set[B] = fa.flatMap(f) + + def tailRecM[A, B](a: A)(f: (A) => Set[Either[A, B]]): Set[B] = { + val bldr = Set.newBuilder[B] + + @tailrec def go(set: Set[Either[A, B]]): Unit = { + val lefts = set.foldLeft(Set[A]()) { (memo, either) => + either.fold( + memo + _, + b => { + bldr += b + memo + } + ) + } + if (lefts.isEmpty) + () + else + go(lefts.flatMap(f)) + } + go(f(a)) + bldr.result() + } + } + + // Since iteration order is not guaranteed for sets, folds and other + // traversals may produce different results for input sets which + // appear to be the same. + @export(Orphan) + implicit val setTraverse: Traverse[Set] = + new Traverse[Set] { + def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable.iterateRight(fa.iterator, lb)(f) + def traverse[G[_]: Applicative, A, B](sa: Set[A])(f: A => G[B]): G[Set[B]] = { + val G = Applicative[G] + sa.foldLeft(G.pure(Set.empty[B])) { (buf, a) => + G.map2(buf, f(a))(_ + _) + } + } + } +} + +@reexports(SetInstances) +object set extends LegacySetInstances + +// TODO: remove when cats.{ Set, Traverse } support export-hook +trait LegacySetInstances { + implicit def legacySetMonad(implicit e: ExportOrphan[Monad[Set]]): Monad[Set] = e.instance + + implicit def legacySetTraverse(implicit e: ExportOrphan[Traverse[Set]]): Traverse[Set] = e.instance +} diff --git a/alleycats-core/src/main/scala/alleycats/std/try.scala b/alleycats-core/src/main/scala/alleycats/std/try.scala new file mode 100644 index 0000000000..1594c5c9cb --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/std/try.scala @@ -0,0 +1,52 @@ +package alleycats +package std + +import cats.Bimonad +import export._ +import scala.util.Try + +@reexports(TryInstances) +object try_ extends LegacyTryInstances + +@exports +object TryInstances { + // There are various concerns people have over Try's ability to + // satisfy the monad laws. For example, consider the following code: + // + // import scala.util.{Try, Success} + // + // def verify(n: Int): Try[Int] = + // if (n == 0) sys.error("nope") else Success(n) + // + // val x = Try(0).flatMap(verify) + // val y = verify(0) + // + // The monad laws require that `x` and `y` produce the same value, + // but in this case `x` is a `Failure(_)` and `y` is undefined (due + // an error being thrown). + // + // Since `verify` is not a total function, it is arguable whether + // this constitutes a law violation, but there is enough concern + // that the Monad[Try] instance has ended up here in Alleycats. + // + // Furthermore, since Cats has introduced a Bimonad[A], the Monad[Try] + // and Comanad[Try] instances have been replaced by a single Bimonad[Try] + // instance. + // + @export(Orphan) + implicit val tryBimonad: Bimonad[Try] = + new Bimonad[Try] { + def pure[A](a: A): Try[A] = Try(a) + override def map[A, B](fa: Try[A])(f: A => B): Try[B] = fa.map(f) + def flatMap[A, B](fa: Try[A])(f: A => Try[B]): Try[B] = fa.flatMap(f) + def coflatMap[A, B](fa: Try[A])(f: Try[A] => B): Try[B] = Try(f(fa)) + def extract[A](p: Try[A]): A = p.get + + def tailRecM[A, B](a: A)(f: (A) => Try[Either[A, B]]): Try[B] = cats.instances.try_.catsStdInstancesForTry.tailRecM(a)(f) + } +} + +// TODO: remove when cats.{ Monad, Comonad, Bimonad } support export-hook +trait LegacyTryInstances { + implicit def legacyTryBimonad(implicit e: ExportOrphan[Bimonad[Try]]): Bimonad[Try] = e.instance +} diff --git a/alleycats-core/src/main/scala/alleycats/syntax/all.scala b/alleycats-core/src/main/scala/alleycats/syntax/all.scala new file mode 100644 index 0000000000..cb1e438c68 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/syntax/all.scala @@ -0,0 +1,5 @@ +package alleycats.syntax + +object all + extends EmptySyntax + with FoldableSyntax diff --git a/alleycats-core/src/main/scala/alleycats/syntax/empty.scala b/alleycats-core/src/main/scala/alleycats/syntax/empty.scala new file mode 100644 index 0000000000..c1e3cfd41e --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/syntax/empty.scala @@ -0,0 +1,13 @@ +package alleycats +package syntax + +import cats.Eq + +object empty extends EmptySyntax + +trait EmptySyntax { + implicit class EmptyOps[A](a: A)(implicit ev: Empty[A]) { + def isEmpty(implicit ev1: Eq[A]): Boolean = ev.isEmpty(a) + def nonEmpty(implicit ev1: Eq[A]): Boolean = ev.nonEmpty(a) + } +} diff --git a/alleycats-core/src/main/scala/alleycats/syntax/foldable.scala b/alleycats-core/src/main/scala/alleycats/syntax/foldable.scala new file mode 100644 index 0000000000..687209e8a4 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/syntax/foldable.scala @@ -0,0 +1,14 @@ +package alleycats +package syntax + +import cats.Foldable +import cats.syntax.foldable._ + +object foldable extends FoldableSyntax + +trait FoldableSyntax { + implicit class ExtraFoldableOps[F[_]: Foldable, A](fa: F[A]) { + def foreach(f: A => Unit): Unit = + fa.foldLeft(()) { (unit, a) => f(a); unit } + } +} diff --git a/alleycats-laws/src/main/scala/alleycats/laws/discipline/FlatMapRecTests.scala b/alleycats-laws/src/main/scala/alleycats/laws/discipline/FlatMapRecTests.scala new file mode 100644 index 0000000000..1bfc8333b8 --- /dev/null +++ b/alleycats-laws/src/main/scala/alleycats/laws/discipline/FlatMapRecTests.scala @@ -0,0 +1,29 @@ +package alleycats.laws.discipline + +import cats._ +import cats.laws.FlatMapLaws +import cats.laws.discipline._ +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ +import org.typelevel.discipline.Laws + + +trait FlatMapRecTests[F[_]] extends Laws { + def laws: FlatMapLaws[F] + + def tailRecM[A: Arbitrary](implicit + ArbFA: Arbitrary[F[A]], + ArbAFA: Arbitrary[A => F[A]], + EqFA: Eq[F[A]] + ): RuleSet = { + new DefaultRuleSet( + name = "flatMapTailRec", + parent = None, + "tailRecM consistent flatMap" -> forAll(laws.tailRecMConsistentFlatMap[A] _)) + } +} + +object FlatMapRecTests { + def apply[F[_]: FlatMap]: FlatMapRecTests[F] = + new FlatMapRecTests[F] { def laws: FlatMapLaws[F] = FlatMapLaws[F] } +} diff --git a/alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala b/alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala new file mode 100644 index 0000000000..a558397a7a --- /dev/null +++ b/alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala @@ -0,0 +1,55 @@ +package alleycats +package tests + + +import catalysts.Platform + +import cats._ +import cats.instances.AllInstances +import cats.syntax.{AllSyntax, EqOps} + +import org.scalactic.anyvals.{PosZDouble, PosInt, PosZInt} +import org.scalatest.{FunSuite, Matchers} +import org.scalatest.prop.{Configuration, GeneratorDrivenPropertyChecks} +import org.typelevel.discipline.scalatest.Discipline + +import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary.arbitrary + +import scala.util.{Failure, Success, Try} + +trait TestSettings extends Configuration with Matchers { + + lazy val checkConfiguration: PropertyCheckConfiguration = + PropertyCheckConfiguration( + minSuccessful = if (Platform.isJvm) PosInt(50) else PosInt(5), + maxDiscardedFactor = if (Platform.isJvm) PosZDouble(5.0) else PosZDouble(50.0), + minSize = PosZInt(0), + sizeRange = if (Platform.isJvm) PosZInt(10) else PosZInt(5), + workers = PosInt(1)) + + lazy val slowCheckConfiguration: PropertyCheckConfiguration = + if (Platform.isJvm) checkConfiguration + else PropertyCheckConfiguration(sizeRange = 1, minSuccessful = 1) +} + +/** + * An opinionated stack of traits to improve consistency and reduce + * boilerplate in Alleycats tests. Derived from Cats. + */ +trait AlleycatsSuite extends FunSuite with Matchers with GeneratorDrivenPropertyChecks with Discipline with TestSettings with AllInstances with AllSyntax with TestInstances with StrictCatsEquality { + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + checkConfiguration + + // disable Eq syntax (by making `catsSyntaxEq` not implicit), since it collides + // with scalactic's equality + override def catsSyntaxEq[A: Eq](a: A): EqOps[A] = new EqOps[A](a) +} + +sealed trait TestInstances { + // To be replaced by https://github.com/rickynils/scalacheck/pull/170 + implicit def arbitraryTry[A: Arbitrary]: Arbitrary[Try[A]] = + Arbitrary(Gen.oneOf( + arbitrary[A].map(Success(_)), + arbitrary[Throwable].map(Failure(_)))) +} diff --git a/alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala b/alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala new file mode 100644 index 0000000000..f4a9abb2cc --- /dev/null +++ b/alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala @@ -0,0 +1,30 @@ +package alleycats.tests + +import cats._ + +import org.scalactic._ +import TripleEqualsSupport.AToBEquivalenceConstraint +import TripleEqualsSupport.BToAEquivalenceConstraint + +// The code in this file was taken and only slightly modified from +// https://github.com/bvenners/equality-integration-demo +// Thanks for the great examples, Bill! + +final class CatsEquivalence[T](T: Eq[T]) extends Equivalence[T] { + def areEquivalent(a: T, b: T): Boolean = T.eqv(a, b) +} + +trait LowPriorityStrictCatsConstraints extends TripleEquals { + implicit def lowPriorityCatsCanEqual[A, B](implicit B: Eq[B], ev: A <:< B): CanEqual[A, B] = + new AToBEquivalenceConstraint[A, B](new CatsEquivalence(B), ev) +} + +trait StrictCatsEquality extends LowPriorityStrictCatsConstraints { + override def convertToEqualizer[T](left: T): Equalizer[T] = super.convertToEqualizer[T](left) + implicit override def convertToCheckingEqualizer[T](left: T): CheckingEqualizer[T] = new CheckingEqualizer(left) + override def unconstrainedEquality[A, B](implicit equalityOfA: Equality[A]): CanEqual[A, B] = super.unconstrainedEquality[A, B] + implicit def catsCanEqual[A, B](implicit A: Eq[A], ev: B <:< A): CanEqual[A, B] = + new BToAEquivalenceConstraint[A, B](new CatsEquivalence(A), ev) +} + +object StrictCatsEquality extends StrictCatsEquality diff --git a/alleycats-tests/src/test/scala/alleycats/tests/IterableTests.scala b/alleycats-tests/src/test/scala/alleycats/tests/IterableTests.scala new file mode 100644 index 0000000000..a6ad6a416b --- /dev/null +++ b/alleycats-tests/src/test/scala/alleycats/tests/IterableTests.scala @@ -0,0 +1,27 @@ +package alleycats +package tests + +import cats.{Eval, Foldable} +import cats.laws.discipline._ + +import alleycats.std.all._ + +class IterableTests extends AlleycatsSuite { + + checkAll("Foldable[Iterable]", FoldableTests[Iterable].foldable[Int, Int]) + + test("foldLeft sum == sum"){ + val it = Iterable(1, 2, 3) + Foldable[Iterable].foldLeft(it, 0){ + case (b, a) => a + b + } shouldEqual(it.sum) + } + + test("foldRight early termination"){ + Foldable[Iterable].foldRight(Iterable(1, 2, 3), Eval.now("KO")){ + case (2, _) => Eval.now("OK") + case (a, b) => b + }.value shouldEqual(Eval.now("OK").value) + } + +} diff --git a/alleycats-tests/src/test/scala/alleycats/tests/SetTests.scala b/alleycats-tests/src/test/scala/alleycats/tests/SetTests.scala new file mode 100644 index 0000000000..fd4c7382fb --- /dev/null +++ b/alleycats-tests/src/test/scala/alleycats/tests/SetTests.scala @@ -0,0 +1,14 @@ +package alleycats.tests + +import alleycats.laws.discipline._ + +import alleycats.std.all._ + +class SetsTests extends AlleycatsSuite { + + checkAll("FlatMapRec[Set]", FlatMapRecTests[Set].tailRecM[Int]) + +} + + + diff --git a/build.sbt b/build.sbt index f67843fdf6..e941115b79 100644 --- a/build.sbt +++ b/build.sbt @@ -207,16 +207,16 @@ lazy val catsJVM = project.in(file(".catsJVM")) .settings(noPublishSettings) .settings(catsSettings) .settings(commonJvmSettings) - .aggregate(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testkitJVM, testsJVM, jvm, docs, bench) - .dependsOn(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testkitJVM, testsJVM % "test-internal -> test", jvm, bench % "compile-internal;test-internal -> test") + .aggregate(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testkitJVM, testsJVM, alleycatsCoreJVM, alleycatsLawsJVM, alleycatsTestsJVM, jvm, docs, bench) + .dependsOn(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testkitJVM, testsJVM % "test-internal -> test", alleycatsCoreJVM, alleycatsLawsJVM, alleycatsTestsJVM % "test-internal -> test", jvm, bench % "compile-internal;test-internal -> test") lazy val catsJS = project.in(file(".catsJS")) .settings(moduleName := "cats") .settings(noPublishSettings) .settings(catsSettings) .settings(commonJsSettings) - .aggregate(macrosJS, kernelJS, kernelLawsJS, coreJS, lawsJS, freeJS, testkitJS, testsJS, js) - .dependsOn(macrosJS, kernelJS, kernelLawsJS, coreJS, lawsJS, freeJS, testkitJS, testsJS % "test-internal -> test", js) + .aggregate(macrosJS, kernelJS, kernelLawsJS, coreJS, lawsJS, freeJS, testkitJS, testsJS, alleycatsCoreJS, alleycatsLawsJS, alleycatsTestsJS, js) + .dependsOn(macrosJS, kernelJS, kernelLawsJS, coreJS, lawsJS, freeJS, testkitJS, testsJS % "test-internal -> test", alleycatsCoreJS, alleycatsLawsJS, alleycatsTestsJS % "test-internal -> test", js) .enablePlugins(ScalaJSPlugin) @@ -708,6 +708,55 @@ lazy val testkit = crossProject.crossType(CrossType.Pure) lazy val testkitJVM = testkit.jvm lazy val testkitJS = testkit.js +lazy val alleycatsCore = crossProject.crossType(CrossType.Pure) + .in(file("alleycats-core")) + .dependsOn(core) + .settings(moduleName := "alleycats-core", name := "Alleycats core") + .settings(libraryDependencies ++= Seq( + "org.typelevel" %% "export-hook" % "1.2.0" + )) + .settings(catsSettings) + .settings(publishSettings) + .settings(scoverageSettings) + .settings(includeGeneratedSrc) + .jsSettings(commonJsSettings) + .jvmSettings(commonJvmSettings) + .settings(scalacOptions ~= {_.filterNot("-Ywarn-unused-import" == _)}) //export-hook triggers unused import + + +lazy val alleycatsCoreJVM = alleycatsCore.jvm +lazy val alleycatsCoreJS = alleycatsCore.js + +lazy val alleycatsLaws = crossProject.crossType(CrossType.Pure) + .in(file("alleycats-laws")) + .dependsOn(alleycatsCore, laws) + .settings(moduleName := "alleycats-laws", name := "Alleycats laws") + .settings(catsSettings) + .settings(publishSettings) + .settings(scoverageSettings) + .settings(disciplineDependencies) + .settings(testingDependencies) + .jsSettings(commonJsSettings) + .jvmSettings(commonJvmSettings) + .jsSettings(coverageEnabled := false) + .dependsOn(alleycatsCore) + +lazy val alleycatsLawsJVM = alleycatsLaws.jvm +lazy val alleycatsLawsJS = alleycatsLaws.js + +lazy val alleycatsTests = crossProject.crossType(CrossType.Pure) + .in(file("alleycats-tests")) + .dependsOn(alleycatsLaws, testkit % "test") + .settings(moduleName := "alleycats-tests") + .settings(catsSettings) + .settings(noPublishSettings) + .jsSettings(commonJsSettings) + .jvmSettings(commonJvmSettings) + +lazy val alleycatsTestsJVM = alleycatsTests.jvm +lazy val alleycatsTestsJS = alleycatsTests.js + + // bench is currently JVM-only lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM) @@ -790,6 +839,11 @@ lazy val publishSettings = Seq( Erik Osheim https://github.com/non/ + + LukaJCB + LukaJCB + https://github.com/LukaJCB/ + mpilquist Michael Pilquist From a6e85dd543cc21cc36323b25371237eab825751e Mon Sep 17 00:00:00 2001 From: Kailuo Wang Date: Mon, 23 Oct 2017 13:21:51 -0400 Subject: [PATCH 2/2] address feedback / removed duplicated class --- .../main/scala/alleycats/std/iterable.scala | 15 ++-------- .../src/main/scala/alleycats/std/set.scala | 2 +- .../scala/alleycats/syntax/foldable.scala | 2 +- .../alleycats/tests/AlleycatsSuite.scala | 2 +- .../scala/alleycats/tests/CatsEquality.scala | 30 ------------------- 5 files changed, 5 insertions(+), 46 deletions(-) delete mode 100644 alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala diff --git a/alleycats-core/src/main/scala/alleycats/std/iterable.scala b/alleycats-core/src/main/scala/alleycats/std/iterable.scala index c58e551d22..7ccde80e75 100644 --- a/alleycats-core/src/main/scala/alleycats/std/iterable.scala +++ b/alleycats-core/src/main/scala/alleycats/std/iterable.scala @@ -14,19 +14,8 @@ object IterableInstances { new Foldable[Iterable] { override def foldLeft[A, B](fa: Iterable[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - // based upon foldRight of List in Cats - override def foldRight[A, B](fa: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { - def loop(as: Iterable[A]): Eval[B] = - if (as.isEmpty) - lb - else { - val h = as.head - val t = as.tail - f(h, Eval.defer(loop(t))) - } - Eval.defer(loop(fa)) - } - + override def foldRight[A, B](fa: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable.iterateRight(fa, lb)(f) } } diff --git a/alleycats-core/src/main/scala/alleycats/std/set.scala b/alleycats-core/src/main/scala/alleycats/std/set.scala index 8af2432628..da851068e4 100644 --- a/alleycats-core/src/main/scala/alleycats/std/set.scala +++ b/alleycats-core/src/main/scala/alleycats/std/set.scala @@ -66,7 +66,7 @@ object SetInstances { def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Foldable.iterateRight(fa.iterator, lb)(f) + Foldable.iterateRight(fa, lb)(f) def traverse[G[_]: Applicative, A, B](sa: Set[A])(f: A => G[B]): G[Set[B]] = { val G = Applicative[G] sa.foldLeft(G.pure(Set.empty[B])) { (buf, a) => diff --git a/alleycats-core/src/main/scala/alleycats/syntax/foldable.scala b/alleycats-core/src/main/scala/alleycats/syntax/foldable.scala index 687209e8a4..ddcbce70b2 100644 --- a/alleycats-core/src/main/scala/alleycats/syntax/foldable.scala +++ b/alleycats-core/src/main/scala/alleycats/syntax/foldable.scala @@ -9,6 +9,6 @@ object foldable extends FoldableSyntax trait FoldableSyntax { implicit class ExtraFoldableOps[F[_]: Foldable, A](fa: F[A]) { def foreach(f: A => Unit): Unit = - fa.foldLeft(()) { (unit, a) => f(a); unit } + fa.foldLeft(()) { (_, a) => f(a) } } } diff --git a/alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala b/alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala index a558397a7a..9ced1461bc 100644 --- a/alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala +++ b/alleycats-tests/src/test/scala/alleycats/tests/AlleycatsSuite.scala @@ -7,7 +7,7 @@ import catalysts.Platform import cats._ import cats.instances.AllInstances import cats.syntax.{AllSyntax, EqOps} - +import cats.tests.StrictCatsEquality import org.scalactic.anyvals.{PosZDouble, PosInt, PosZInt} import org.scalatest.{FunSuite, Matchers} import org.scalatest.prop.{Configuration, GeneratorDrivenPropertyChecks} diff --git a/alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala b/alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala deleted file mode 100644 index f4a9abb2cc..0000000000 --- a/alleycats-tests/src/test/scala/alleycats/tests/CatsEquality.scala +++ /dev/null @@ -1,30 +0,0 @@ -package alleycats.tests - -import cats._ - -import org.scalactic._ -import TripleEqualsSupport.AToBEquivalenceConstraint -import TripleEqualsSupport.BToAEquivalenceConstraint - -// The code in this file was taken and only slightly modified from -// https://github.com/bvenners/equality-integration-demo -// Thanks for the great examples, Bill! - -final class CatsEquivalence[T](T: Eq[T]) extends Equivalence[T] { - def areEquivalent(a: T, b: T): Boolean = T.eqv(a, b) -} - -trait LowPriorityStrictCatsConstraints extends TripleEquals { - implicit def lowPriorityCatsCanEqual[A, B](implicit B: Eq[B], ev: A <:< B): CanEqual[A, B] = - new AToBEquivalenceConstraint[A, B](new CatsEquivalence(B), ev) -} - -trait StrictCatsEquality extends LowPriorityStrictCatsConstraints { - override def convertToEqualizer[T](left: T): Equalizer[T] = super.convertToEqualizer[T](left) - implicit override def convertToCheckingEqualizer[T](left: T): CheckingEqualizer[T] = new CheckingEqualizer(left) - override def unconstrainedEquality[A, B](implicit equalityOfA: Equality[A]): CanEqual[A, B] = super.unconstrainedEquality[A, B] - implicit def catsCanEqual[A, B](implicit A: Eq[A], ev: B <:< A): CanEqual[A, B] = - new BToAEquivalenceConstraint[A, B](new CatsEquivalence(A), ev) -} - -object StrictCatsEquality extends StrictCatsEquality