From 03aad13099ece0a56e7529ff4fded72fe07be7ce Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 10:01:21 -0400 Subject: [PATCH 01/17] Get algebra-core added to Cats. This commit sets up the basic SBT subproject configuration and moves the core algebra type classes into the cats.kernel package. --- build.sbt | 44 +++- kernel/src/main/scala/cats/kernel/Band.scala | 18 ++ .../cats/kernel/BoundedSemilattice.scala | 13 ++ .../scala/cats/kernel/CommutativeGroup.scala | 17 ++ .../scala/cats/kernel/CommutativeMonoid.scala | 17 ++ .../cats/kernel/CommutativeSemigroup.scala | 18 ++ kernel/src/main/scala/cats/kernel/Eq.scala | 138 ++++++++++++ kernel/src/main/scala/cats/kernel/Group.scala | 48 ++++ .../src/main/scala/cats/kernel/Monoid.scala | 52 +++++ kernel/src/main/scala/cats/kernel/Order.scala | 209 ++++++++++++++++++ .../main/scala/cats/kernel/PartialOrder.scala | 169 ++++++++++++++ .../src/main/scala/cats/kernel/Priority.scala | 65 ++++++ .../main/scala/cats/kernel/Semigroup.scala | 79 +++++++ .../main/scala/cats/kernel/Semilattice.scala | 62 ++++++ .../src/main/scala/cats/kernel/package.scala | 3 + 15 files changed, 948 insertions(+), 4 deletions(-) create mode 100644 kernel/src/main/scala/cats/kernel/Band.scala create mode 100644 kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala create mode 100644 kernel/src/main/scala/cats/kernel/CommutativeGroup.scala create mode 100644 kernel/src/main/scala/cats/kernel/CommutativeMonoid.scala create mode 100644 kernel/src/main/scala/cats/kernel/CommutativeSemigroup.scala create mode 100644 kernel/src/main/scala/cats/kernel/Eq.scala create mode 100644 kernel/src/main/scala/cats/kernel/Group.scala create mode 100644 kernel/src/main/scala/cats/kernel/Monoid.scala create mode 100644 kernel/src/main/scala/cats/kernel/Order.scala create mode 100644 kernel/src/main/scala/cats/kernel/PartialOrder.scala create mode 100644 kernel/src/main/scala/cats/kernel/Priority.scala create mode 100644 kernel/src/main/scala/cats/kernel/Semigroup.scala create mode 100644 kernel/src/main/scala/cats/kernel/Semilattice.scala create mode 100644 kernel/src/main/scala/cats/kernel/package.scala diff --git a/build.sbt b/build.sbt index e0f4d915ab..1eccc559a3 100644 --- a/build.sbt +++ b/build.sbt @@ -24,6 +24,17 @@ lazy val catsDoctestSettings = Seq( doctestWithDependencies := false ) ++ doctestSettings +lazy val kernelSettings = Seq( + scalacOptions ++= commonScalacOptions, + resolvers ++= Seq( + "bintray/non" at "http://dl.bintray.com/non/maven", + Resolver.sonatypeRepo("releases"), + Resolver.sonatypeRepo("snapshots") + ), + parallelExecution in Test := false, + scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings") +) ++ warnUnusedImport + lazy val commonSettings = Seq( scalacOptions ++= commonScalacOptions, resolvers ++= Seq( @@ -136,15 +147,15 @@ lazy val catsJVM = project.in(file(".catsJVM")) .settings(moduleName := "cats") .settings(catsSettings) .settings(commonJvmSettings) - .aggregate(macrosJVM, coreJVM, lawsJVM, testsJVM, jvm, docs, bench) - .dependsOn(macrosJVM, coreJVM, lawsJVM, testsJVM % "test-internal -> test", jvm, bench % "compile-internal;test-internal -> test") + .aggregate(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, testsJVM, jvm, docs, bench) + .dependsOn(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, testsJVM % "test-internal -> test", jvm, bench % "compile-internal;test-internal -> test") lazy val catsJS = project.in(file(".catsJS")) .settings(moduleName := "cats") .settings(catsSettings) .settings(commonJsSettings) - .aggregate(macrosJS, coreJS, lawsJS, testsJS, js) - .dependsOn(macrosJS, coreJS, lawsJS, testsJS % "test-internal -> test", js) + .aggregate(macrosJS, kernelJS, kernelLawsJS, coreJS, lawsJS, testsJS, js) + .dependsOn(macrosJS, kernelJS, kernelLawsJS, coreJS, lawsJS, testsJS % "test-internal -> test", js) .enablePlugins(ScalaJSPlugin) @@ -158,6 +169,31 @@ lazy val macros = crossProject.crossType(CrossType.Pure) lazy val macrosJVM = macros.jvm lazy val macrosJS = macros.js +lazy val kernel = crossProject.crossType(CrossType.Pure) + .in(file("kernel")) + .settings(moduleName := "cats-kernel") + .settings(kernelSettings: _*) + .settings(buildSettings: _*) + .settings(publishSettings: _*) + .settings(scoverageSettings: _*) + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val kernelJVM = kernel.jvm +lazy val kernelJS = kernel.js + +lazy val kernelLaws = crossProject.crossType(CrossType.Pure) + .in(file("kernel-laws")) + .settings(moduleName := "cats-kernel-laws") + .settings(kernelSettings: _*) + .settings(buildSettings: _*) + .settings(publishSettings: _*) + .settings(scoverageSettings: _*) + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val kernelLawsJVM = kernelLaws.jvm +lazy val kernelLawsJS = kernelLaws.js lazy val core = crossProject.crossType(CrossType.Pure) .dependsOn(macros) diff --git a/kernel/src/main/scala/cats/kernel/Band.scala b/kernel/src/main/scala/cats/kernel/Band.scala new file mode 100644 index 0000000000..5404005633 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Band.scala @@ -0,0 +1,18 @@ +package cats.kernel + +import scala.{specialized => sp} + + +/** + * Bands are semigroups whose operation + * (i.e. combine) is also idempotent. + */ +trait Band[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] + +object Band { + + /** + * Access an implicit `Band[A]`. + */ + @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: Band[A]): Band[A] = ev +} diff --git a/kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala b/kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala new file mode 100644 index 0000000000..b9497ec63e --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala @@ -0,0 +1,13 @@ +package cats.kernel + +import scala.{specialized => sp} + +trait BoundedSemilattice[@sp(Int, Long, Float, Double) A] extends Any with Semilattice[A] with CommutativeMonoid[A] + +object BoundedSemilattice { + + /** + * Access an implicit `BoundedSemilattice[A]`. + */ + @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: BoundedSemilattice[A]): BoundedSemilattice[A] = ev +} diff --git a/kernel/src/main/scala/cats/kernel/CommutativeGroup.scala b/kernel/src/main/scala/cats/kernel/CommutativeGroup.scala new file mode 100644 index 0000000000..6072cecd96 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/CommutativeGroup.scala @@ -0,0 +1,17 @@ +package cats.kernel + +import scala.{ specialized => sp } + +/** + * An abelian group is a group whose operation is commutative. + */ +trait CommutativeGroup[@sp(Int, Long, Float, Double) A] extends Any with Group[A] with CommutativeMonoid[A] + +object CommutativeGroup extends GroupFunctions[CommutativeGroup] { + + /** + * Access an implicit `CommutativeGroup[A]`. + */ + @inline final def apply[A](implicit ev: CommutativeGroup[A]): CommutativeGroup[A] = ev +} + diff --git a/kernel/src/main/scala/cats/kernel/CommutativeMonoid.scala b/kernel/src/main/scala/cats/kernel/CommutativeMonoid.scala new file mode 100644 index 0000000000..c8bb7a3657 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/CommutativeMonoid.scala @@ -0,0 +1,17 @@ +package cats.kernel + +import scala.{ specialized => sp } + +/** + * CommutativeMonoid represents a commutative monoid. + * + * A monoid is commutative if for all x and y, x |+| y === y |+| x. + */ +trait CommutativeMonoid[@sp(Int, Long, Float, Double) A] extends Any with Monoid[A] with CommutativeSemigroup[A] + +object CommutativeMonoid extends MonoidFunctions[CommutativeMonoid] { + /** + * Access an implicit `CommutativeMonoid[A]`. + */ + @inline final def apply[A](implicit ev: CommutativeMonoid[A]): CommutativeMonoid[A] = ev +} diff --git a/kernel/src/main/scala/cats/kernel/CommutativeSemigroup.scala b/kernel/src/main/scala/cats/kernel/CommutativeSemigroup.scala new file mode 100644 index 0000000000..2874a6b5d8 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/CommutativeSemigroup.scala @@ -0,0 +1,18 @@ +package cats.kernel + +import scala.{ specialized => sp } + +/** + * CommutativeSemigroup represents a commutative semigroup. + * + * A semigroup is commutative if for all x and y, x |+| y === y |+| x. + */ +trait CommutativeSemigroup[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] + +object CommutativeSemigroup extends SemigroupFunctions[CommutativeSemigroup] { + + /** + * Access an implicit `CommutativeSemigroup[A]`. + */ + @inline final def apply[A](implicit ev: CommutativeSemigroup[A]): CommutativeSemigroup[A] = ev +} diff --git a/kernel/src/main/scala/cats/kernel/Eq.scala b/kernel/src/main/scala/cats/kernel/Eq.scala new file mode 100644 index 0000000000..c76f51ad28 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Eq.scala @@ -0,0 +1,138 @@ +package cats.kernel + +import scala.{specialized => sp} + +import scala.math.Equiv + +/** + * A type class used to determine equality between 2 instances of the same + * type. Any 2 instances `x` and `y` are equal if `eqv(x, y)` is `true`. + * Moreover, `eqv` should form an equivalence relation. + */ +trait Eq[@sp A] extends Any with Serializable { self => + + /** + * Returns `true` if `x` and `y` are equivalent, `false` otherwise. + */ + def eqv(x: A, y: A): Boolean + + /** + * Returns `false` if `x` and `y` are equivalent, `true` otherwise. + */ + def neqv(x: A, y: A): Boolean = !eqv(x, y) + + /** + * Constructs a new `Eq` instance for type `B` where 2 elements are + * equivalent iff `eqv(f(x), f(y))`. + */ + def on[@sp B](f: B => A): Eq[B] = + new Eq[B] { + def eqv(x: B, y: B): Boolean = self.eqv(f(x), f(y)) + } + + /** + * Return an Eq that gives the result of the and of this and that + * note this is idempotent + */ + def and(that: Eq[A]): Eq[A] = + new Eq[A] { + def eqv(x: A, y: A) = self.eqv(x, y) && that.eqv(x, y) + } + /** + * Return an Eq that gives the result of the or of this and that + * Note this is idempotent + */ + def or(that: Eq[A]): Eq[A] = + new Eq[A] { + def eqv(x: A, y: A) = self.eqv(x, y) || that.eqv(x, y) + } +} + +trait EqFunctions { + def eqv[@sp A](x: A, y: A)(implicit ev: Eq[A]): Boolean = + ev.eqv(x, y) + + def neqv[@sp A](x: A, y: A)(implicit ev: Eq[A]): Boolean = + ev.neqv(x, y) +} + +object Eq extends EqFunctions { + + /** + * Access an implicit `Eq[A]`. + */ + @inline final def apply[A](implicit ev: Eq[A]): Eq[A] = ev + + /** + * Convert an implicit `Eq[B]` to an `Eq[A]` using the given + * function `f`. + */ + def by[@sp A, @sp B](f: A => B)(implicit ev: Eq[B]): Eq[A] = + ev.on(f) + + /** + * This gives compatibility with scala's Equiv trait + */ + implicit def equiv[A](implicit ev: Eq[A]): Equiv[A] = + new Equiv[A] { + def equiv(a: A, b: A) = ev.eqv(a, b) + } + + /** + * Create an `Eq` instance from an `eqv` implementation. + */ + def instance[A](f: (A, A) => Boolean): Eq[A] = + new Eq[A] { + def eqv(x: A, y: A) = f(x, y) + } + + /** + * An `Eq[A]` that delegates to universal equality (`==`). + * + * This can be useful for case classes, which have reasonable `equals` + * implementations + */ + def fromUniversalEquals[A]: Eq[A] = + new Eq[A] { + def eqv(x: A, y: A) = x == y + } + + /** + * Everything is the same + */ + def allEqual[A]: Eq[A] = new Eq[A] { + def eqv(x: A, y: A) = true + } + + /** + * This is a monoid that creates an Eq that + * checks that all equality checks pass + */ + def allEqualBoundedSemilattice[A]: BoundedSemilattice[Eq[A]] = new BoundedSemilattice[Eq[A]] { + def empty = allEqual[A] + def combine(e1: Eq[A], e2: Eq[A]): Eq[A] = e1.and(e2) + override def combineAllOption(es: TraversableOnce[Eq[A]]): Option[Eq[A]] = + if (es.isEmpty) None + else { + val materialized = es.toVector + Some(new Eq[A] { + def eqv(x: A, y: A) = materialized.forall(_.eqv(x, y)) + }) + } + } + /** + * This is a monoid that creates an Eq that + * checks that at least one equality check passes + */ + def anyEqualSemilattice[A]: Semilattice[Eq[A]] = new Semilattice[Eq[A]] { + def combine(e1: Eq[A], e2: Eq[A]): Eq[A] = e1.or(e2) + override def combineAllOption(es: TraversableOnce[Eq[A]]): Option[Eq[A]] = + if (es.isEmpty) None + else { + val materialized = es.toVector + Some(new Eq[A] { + def eqv(x: A, y: A) = materialized.exists(_.eqv(x, y)) + }) + } + } +} diff --git a/kernel/src/main/scala/cats/kernel/Group.scala b/kernel/src/main/scala/cats/kernel/Group.scala new file mode 100644 index 0000000000..2a3412dbbd --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Group.scala @@ -0,0 +1,48 @@ +package cats.kernel + +import scala.{ specialized => sp } + +/** + * A group is a monoid where each element has an inverse. + */ +trait Group[@sp(Int, Long, Float, Double) A] extends Any with Monoid[A] { + + /** + * Find the inverse of `a`. + * + * `combine(a, inverse(a))` = `combine(inverse(a), a)` = `empty`. + */ + def inverse(a: A): A + + /** + * Remove the element `b` from `a`. + * + * Equivalent to `combine(a, inverse(a))` + */ + def remove(a: A, b: A): A = combine(a, inverse(b)) + + /** + * Return `a` appended to itself `n` times. If `n` is negative, then + * this returns `inverse(a)` appended to itself `n` times. + */ + override def combineN(a: A, n: Int): A = + if (n > 0) repeatedCombineN(a, n) + else if (n == 0) empty + else if (n == Int.MinValue) combineN(inverse(combine(a, a)), 1073741824) + else repeatedCombineN(inverse(a), -n) +} + +trait GroupFunctions[G[T] <: Group[T]] extends MonoidFunctions[Group] { + def inverse[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: G[A]): A = + ev.inverse(a) + def remove[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: G[A]): A = + ev.remove(x, y) +} + +object Group extends GroupFunctions[Group] { + + /** + * Access an implicit `Group[A]`. + */ + @inline final def apply[A](implicit ev: Group[A]): Group[A] = ev +} diff --git a/kernel/src/main/scala/cats/kernel/Monoid.scala b/kernel/src/main/scala/cats/kernel/Monoid.scala new file mode 100644 index 0000000000..aa2cbaf317 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Monoid.scala @@ -0,0 +1,52 @@ +package cats.kernel + +import scala.{ specialized => sp } + +/** + * A monoid is a semigroup with an identity. A monoid is a specialization of a + * semigroup, so its operation must be associative. Additionally, + * `combine(x, empty) == combine(empty, x) == x`. For example, if we have `Monoid[String]`, + * with `combine` as string concatenation, then `empty = ""`. + */ +trait Monoid[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] { + + /** + * Return the identity element for this monoid. + */ + def empty: A + + /** + * Tests if `a` is the identity. + */ + def isEmpty(a: A)(implicit ev: Eq[A]) = ev.eqv(a, empty) + + /** + * Return `a` appended to itself `n` times. + */ + override def combineN(a: A, n: Int): A = + if (n < 0) throw new IllegalArgumentException("Repeated combining for monoids must have n >= 0") + else if (n == 0) empty + else repeatedCombineN(a, n) + + /** + * Given a sequence of `as`, sum them using the monoid and return the total. + */ + def combineAll(as: TraversableOnce[A]): A = + as.foldLeft(empty)(combine) +} + +trait MonoidFunctions[M[T] <: Monoid[T]] extends SemigroupFunctions[M] { + def empty[@sp(Int, Long, Float, Double) A](implicit ev: M[A]): A = + ev.empty + + def combineAll[@sp(Int, Long, Float, Double) A](as: TraversableOnce[A])(implicit ev: M[A]): A = + ev.combineAll(as) +} + +object Monoid extends MonoidFunctions[Monoid] { + + /** + * Access an implicit `Monoid[A]`. + */ + @inline final def apply[A](implicit ev: Monoid[A]): Monoid[A] = ev +} diff --git a/kernel/src/main/scala/cats/kernel/Order.scala b/kernel/src/main/scala/cats/kernel/Order.scala new file mode 100644 index 0000000000..7381684b5c --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Order.scala @@ -0,0 +1,209 @@ +package cats.kernel + +import scala.{specialized => sp} + +/** + * The `Order` type class is used to define a total ordering on some type `A`. + * An order is defined by a relation <=, which obeys the following laws: + * + * - either x <= y or y <= x (totality) + * - if x <= y and y <= x, then x == y (antisymmetry) + * - if x <= y and y <= z, then x <= z (transitivity) + * + * The truth table for compare is defined as follows: + * + * x <= y x >= y Int + * true true = 0 (corresponds to x == y) + * true false < 0 (corresponds to x < y) + * false true > 0 (corresponds to x > y) + * + * By the totality law, x <= y and y <= x cannot be both false. + */ +trait Order[@sp A] extends Any with PartialOrder[A] { self => + + /** + * Result of comparing `x` with `y`. Returns an Int whose sign is: + * - negative iff `x < y` + * - zero iff `x = y` + * - positive iff `x > y` + */ + def compare(x: A, y: A): Int + + def partialCompare(x: A, y: A): Double = compare(x, y).toDouble + + /** + * If x <= y, return x, else return y. + */ + def min(x: A, y: A): A = if (lt(x, y)) x else y + + /** + * If x >= y, return x, else return y. + */ + def max(x: A, y: A): A = if (gt(x, y)) x else y + + /** + * Defines an order on `B` by mapping `B` to `A` using `f` and using `A`s + * order to order `B`. + */ + override def on[@sp B](f: B => A): Order[B] = + new Order[B] { + def compare(x: B, y: B): Int = self.compare(f(x), f(y)) + } + + /** + * Defines an ordering on `A` where all arrows switch direction. + */ + override def reverse: Order[A] = + new Order[A] { + def compare(x: A, y: A): Int = self.compare(y, x) + + override def reverse: Order[A] = self + } + + // The following may be overridden for performance: + + /** + * Returns true if `x` = `y`, false otherwise. + */ + override def eqv(x: A, y: A): Boolean = + compare(x, y) == 0 + + /** + * Returns true if `x` != `y`, false otherwise. + */ + override def neqv(x: A, y: A): Boolean = + compare(x, y) != 0 + + /** + * Returns true if `x` <= `y`, false otherwise. + */ + override def lteqv(x: A, y: A): Boolean = + compare(x, y) <= 0 + + /** + * Returns true if `x` < `y`, false otherwise. + */ + override def lt(x: A, y: A): Boolean = + compare(x, y) < 0 + + /** + * Returns true if `x` >= `y`, false otherwise. + */ + override def gteqv(x: A, y: A): Boolean = + compare(x, y) >= 0 + + /** + * Returns true if `x` > `y`, false otherwise. + */ + override def gt(x: A, y: A): Boolean = + compare(x, y) > 0 + + /** + * Returns a new `Order[A]` instance that first compares by the original + * `Order` instance and uses the provided `Order` instance to "break ties". + * + * That is, `x.whenEqual(y)` creates an `Order` that first orders by `x` and + * then (if two elements are equal) falls back to `y` for the comparison. + */ + def whenEqual(o: Order[A]): Order[A] = new Order[A] { + def compare(x: A, y: A) = { + val c = self.compare(x, y) + if (c == 0) o.compare(x, y) + else c + } + } + + /** + * Convert a `Order[A]` to a `scala.math.Ordering[A]` + * instance. + */ + def toOrdering: Ordering[A] = new Ordering[A] { + def compare(x: A, y: A): Int = self.compare(x, y) + } +} + +trait OrderFunctions { + def compare[@sp A](x: A, y: A)(implicit ev: Order[A]): Int = + ev.compare(x, y) + + def eqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = + ev.eqv(x, y) + def neqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = + ev.neqv(x, y) + def gt[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = + ev.gt(x, y) + def gteqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = + ev.gteqv(x, y) + def lt[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = + ev.lt(x, y) + def lteqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = + ev.lteqv(x, y) + + def min[@sp A](x: A, y: A)(implicit ev: Order[A]): A = + ev.min(x, y) + def max[@sp A](x: A, y: A)(implicit ev: Order[A]): A = + ev.max(x, y) +} + +object Order extends OrderFunctions { + + /** + * Access an implicit `Eq[A]`. + */ + @inline final def apply[A](implicit ev: Order[A]) = ev + + /** + * Convert an implicit `Order[A]` to an `Order[B]` using the given + * function `f`. + */ + def by[@sp A, @sp B](f: A => B)(implicit ev: Order[B]): Order[A] = + ev.on(f) + + /** + * Define an `Order[A]` using the given function `f`. + */ + def from[@sp A](f: (A, A) => Int): Order[A] = + new Order[A] { + def compare(x: A, y: A) = f(x, y) + } + + /** + * Implicitly convert a `Order[A]` to a `scala.math.Ordering[A]` + * instance. + */ + implicit def ordering[A](implicit ev: Order[A]): Ordering[A] = + ev.toOrdering + + /** + * An `Order` instance that considers all `A` instances to be equal. + */ + def allEqual[A]: Order[A] = new Order[A] { + def compare(x: A, y: A): Int = 0 + } + + + /** + * A `Monoid[Order[A]]` can be generated for all `A` with the following + * properties: + * + * `empty` returns a trivial `Order[A]` which considers all `A` instances to + * be equal. + * + * `combine(x: Order[A], y: Order[A])` creates an `Order[A]` that first + * orders by `x` and then (if two elements are equal) falls back to `y`. + * + * @see [[Order.whenEqual]] + */ + def whenEqualMonoid[A]: Monoid[Order[A]] = + new Monoid[Order[A]] { + val empty: Order[A] = allEqual[A] + + def combine(x: Order[A], y: Order[A]): Order[A] = x whenEqual y + } + + def fromOrdering[A](implicit ev: Ordering[A]): Order[A] = new Order[A] { + def compare(x: A, y: A): Int = ev.compare(x, y) + + override def toOrdering: Ordering[A] = ev + } +} diff --git a/kernel/src/main/scala/cats/kernel/PartialOrder.scala b/kernel/src/main/scala/cats/kernel/PartialOrder.scala new file mode 100644 index 0000000000..e3adbe0f9a --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/PartialOrder.scala @@ -0,0 +1,169 @@ +package cats.kernel + +import scala.{specialized => sp} + +/** + * The `PartialOrder` type class is used to define a partial ordering on some type `A`. + * + * A partial order is defined by a relation <=, which obeys the following laws: + * + * - x <= x (reflexivity) + * - if x <= y and y <= x, then x = y (anti-symmetry) + * - if x <= y and y <= z, then x <= z (transitivity) + * + * To compute both <= and >= at the same time, we use a Double number + * to encode the result of the comparisons x <= y and x >= y. + * The truth table is defined as follows: + * + * x <= y x >= y Double + * true true = 0.0 (corresponds to x = y) + * false false = NaN (x and y cannot be compared) + * true false = -1.0 (corresponds to x < y) + * false true = 1.0 (corresponds to x > y) + */ +trait PartialOrder[@sp A] extends Any with Eq[A] { self => + + /** + * Result of comparing `x` with `y`. Returns NaN if operands are not + * comparable. If operands are comparable, returns a Double whose + * sign is: + * - negative iff `x < y` + * - zero iff `x = y` + * - positive iff `x > y` + */ + def partialCompare(x: A, y: A): Double + + /** + * Result of comparing `x` with `y`. Returns None if operands are + * not comparable. If operands are comparable, returns Some[Int] + * where the Int sign is: + * - negative iff `x < y` + * - zero iff `x = y` + * - positive iff `x > y` + */ + def tryCompare(x: A, y: A): Option[Int] = { + val c = partialCompare(x, y) + if (c.isNaN) None else Some(c.signum) + } + + /** + * Returns Some(x) if x <= y, Some(y) if x > y, otherwise None. + */ + def pmin(x: A, y: A): Option[A] = { + val c = partialCompare(x, y) + if (c <= 0) Some(x) + else if (c > 0) Some(y) + else None + } + + /** + * Returns Some(x) if x >= y, Some(y) if x < y, otherwise None. + */ + def pmax(x: A, y: A): Option[A] = { + val c = partialCompare(x, y) + if (c >= 0) Some(x) + else if (c < 0) Some(y) + else None + } + + /** + * Defines a partial order on `B` by mapping `B` to `A` using `f` + * and using `A`s order to order `B`. + */ + override def on[@sp B](f: B => A): PartialOrder[B] = + new PartialOrder[B] { + def partialCompare(x: B, y: B): Double = self.partialCompare(f(x), f(y)) + } + + /** + * Defines a partial order on `A` where all arrows switch direction. + */ + def reverse: PartialOrder[A] = + new PartialOrder[A] { + def partialCompare(x: A, y: A): Double = self.partialCompare(y, x) + } + + // The following may be overridden for performance: + + /** + * Returns true if `x` = `y`, false otherwise. + */ + def eqv(x: A, y: A): Boolean = partialCompare(x, y) == 0 + + /** + * Returns true if `x` <= `y`, false otherwise. + */ + def lteqv(x: A, y: A): Boolean = partialCompare(x, y) <= 0 + + /** + * Returns true if `x` < `y`, false otherwise. + */ + def lt(x: A, y: A): Boolean = partialCompare(x, y) < 0 + + /** + * Returns true if `x` >= `y`, false otherwise. + */ + def gteqv(x: A, y: A): Boolean = partialCompare(x, y) >= 0 + + /** + * Returns true if `x` > `y`, false otherwise. + */ + def gt(x: A, y: A): Boolean = partialCompare(x, y) > 0 +} + +trait PartialOrderFunctions { + + def partialCompare[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Double = + ev.partialCompare(x, y) + def tryCompare[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Option[Int] = + ev.tryCompare(x, y) + + def pmin[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Option[A] = + ev.pmin(x, y) + def pmax[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Option[A] = + ev.pmax(x, y) + + def eqv[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + ev.eqv(x, y) + def lteqv[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + ev.lteqv(x, y) + def lt[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + ev.lt(x, y) + def gteqv[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + ev.gteqv(x, y) + def gt[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + ev.gt(x, y) +} + +object PartialOrder extends PartialOrderFunctions { + + /** + * Access an implicit `Eq[A]`. + */ + @inline final def apply[A](implicit ev: PartialOrder[A]) = ev + + /** + * Convert an implicit `PartialOrder[A]` to an `PartialOrder[B]` + * using the given function `f`. + */ + def by[@sp A, @sp B](f: A => B)(implicit ev: PartialOrder[B]): PartialOrder[A] = + ev.on(f) + + /** + * Define a `PartialOrder[A]` using the given function `f`. + */ + def from[@sp A](f: (A, A) => Double): PartialOrder[A] = + new PartialOrder[A] { + def partialCompare(x: A, y: A) = f(x, y) + } + + /** + * Implicitly convert a `PartialOrder[A]` to a + * `scala.math.PartialOrdering[A]` instance. + */ + implicit def partialOrdering[A](implicit ev: PartialOrder[A]): PartialOrdering[A] = + new PartialOrdering[A] { + def tryCompare(x: A, y: A): Option[Int] = ev.tryCompare(x, y) + def lteq(x: A, y: A): Boolean = ev.lteqv(x, y) + } +} diff --git a/kernel/src/main/scala/cats/kernel/Priority.scala b/kernel/src/main/scala/cats/kernel/Priority.scala new file mode 100644 index 0000000000..a9021c5c83 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Priority.scala @@ -0,0 +1,65 @@ +package cats.kernel + +/** + * Priority is a type class for prioritized implicit search. + * + * This type class will attempt to provide an implicit instance of `P` + * (the preferred type). If that type is not available it will + * fallback to `F` (the fallback type). If neither type is available + * then a `Priority[P, F]` instance will not be available. + * + * This type can be useful for problems where multiple algorithms can + * be used, depending on the type classes available. + */ +sealed trait Priority[+P, +F] { + + import Priority.{Preferred, Fallback} + + def fold[B](f1: P => B)(f2: F => B): B = + this match { + case Preferred(x) => f1(x) + case Fallback(y) => f2(y) + } + + def join[U >: P with F]: U = + fold(_.asInstanceOf[U])(_.asInstanceOf[U]) + + def bimap[P2, F2](f1: P => P2)(f2: F => F2): Priority[P2, F2] = + this match { + case Preferred(x) => Preferred(f1(x)) + case Fallback(y) => Fallback(f2(y)) + } + + def toEither: Either[P, F] = + fold[Either[P, F]](p => Left(p))(f => Right(f)) + + def isPreferred: Boolean = + fold(_ => true)(_ => false) + + def isFallback: Boolean = + fold(_ => false)(_ => true) + + def getPreferred: Option[P] = + fold[Option[P]](p => Some(p))(_ => None) + + def getFallback: Option[F] = + fold[Option[F]](_ => None)(f => Some(f)) +} + +object Priority extends FindPreferred { + + case class Preferred[P](get: P) extends Priority[P, Nothing] + case class Fallback[F](get: F) extends Priority[Nothing, F] + + def apply[P, F](implicit ev: Priority[P, F]): Priority[P, F] = ev +} + +private[kernel] trait FindPreferred extends FindFallback { + implicit def preferred[P](implicit ev: P): Priority[P, Nothing] = + Priority.Preferred(ev) +} + +private[kernel] trait FindFallback { + implicit def fallback[F](implicit ev: F): Priority[Nothing, F] = + Priority.Fallback(ev) +} diff --git a/kernel/src/main/scala/cats/kernel/Semigroup.scala b/kernel/src/main/scala/cats/kernel/Semigroup.scala new file mode 100644 index 0000000000..8e17529793 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Semigroup.scala @@ -0,0 +1,79 @@ +package cats.kernel + +import scala.{ specialized => sp } +import scala.annotation.{ tailrec } + +/** + * A semigroup is any set `A` with an associative operation (`combine`). + */ +trait Semigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable { + + /** + * Associative operation taking which combines two values. + */ + def combine(x: A, y: A): A + + /** + * Return `a` combined with itself `n` times. + */ + def combineN(a: A, n: Int): A = + if (n <= 0) throw new IllegalArgumentException("Repeated combining for semigroups must have n > 0") + else repeatedCombineN(a, n) + + /** + * Return `a` combined with itself more than once. + */ + protected[this] def repeatedCombineN(a: A, n: Int): A = { + @tailrec def loop(b: A, k: Int, extra: A): A = + if (k == 1) combine(b, extra) else { + val x = if ((k & 1) == 1) combine(b, extra) else extra + loop(combine(b, b), k >>> 1, x) + } + if (n == 1) a else loop(a, n - 1, a) + } + + /** + * Given a sequence of `as`, combine them and return the total. + * + * If the sequence is empty, returns None. Otherwise, returns Some(total). + */ + def combineAllOption(as: TraversableOnce[A]): Option[A] = + as.reduceOption(combine) +} + +trait SemigroupFunctions[S[T] <: Semigroup[T]] { + def combine[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: S[A]): A = + ev.combine(x, y) + + def maybeCombine[@sp(Int, Long, Float, Double) A](ox: Option[A], y: A)(implicit ev: S[A]): A = + ox match { + case Some(x) => ev.combine(x, y) + case None => y + } + + def maybeCombine[@sp(Int, Long, Float, Double) A](x: A, oy: Option[A])(implicit ev: S[A]): A = + oy match { + case Some(y) => ev.combine(x, y) + case None => x + } + + def isCommutative[A](implicit ev: S[A]): Boolean = + ev.isInstanceOf[CommutativeSemigroup[_]] + + def isIdempotent[A](implicit ev: S[A]): Boolean = + ev.isInstanceOf[Band[_]] + + def combineN[@sp(Int, Long, Float, Double) A](a: A, n: Int)(implicit ev: S[A]): A = + ev.combineN(a, n) + + def combineAllOption[A](as: TraversableOnce[A])(implicit ev: S[A]): Option[A] = + ev.combineAllOption(as) +} + +object Semigroup extends SemigroupFunctions[Semigroup] { + + /** + * Access an implicit `Semigroup[A]`. + */ + @inline final def apply[A](implicit ev: Semigroup[A]) = ev +} diff --git a/kernel/src/main/scala/cats/kernel/Semilattice.scala b/kernel/src/main/scala/cats/kernel/Semilattice.scala new file mode 100644 index 0000000000..92cc80db86 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Semilattice.scala @@ -0,0 +1,62 @@ +package cats.kernel + +import scala.{specialized => sp} + +/** + * Semilattices are commutative semigroups whose operation + * (i.e. combine) is also idempotent. + */ +trait Semilattice[@sp(Int, Long, Float, Double) A] extends Any + with Band[A] + with CommutativeSemigroup[A] { self => + + /** + * Given Eq[A], return a PartialOrder[A] using the `combine` + * operator to determine the partial ordering. This method assumes + * `combine` functions as `meet` (that is, as a lower bound). + * + * This method returns: + * + * 0.0 if x = y + * -1.0 if x = combine(x, y) + * 1.0 if y = combine(x, y) + * NaN otherwise + */ + def asMeetPartialOrder(implicit ev: Eq[A]): PartialOrder[A] = + new PartialOrder[A] { + def partialCompare(x: A, y: A): Double = + if (ev.eqv(x, y)) 0.0 else { + val z = self.combine(x, y) + if (ev.eqv(x, z)) -1.0 else if (ev.eqv(y, z)) 1.0 else Double.NaN + } + } + + /** + * Given Eq[A], return a PartialOrder[A] using the `combine` + * operator to determine the partial ordering. This method assumes + * `combine` functions as `join` (that is, as an upper bound). + * + * This method returns: + * + * 0.0 if x = y + * -1.0 if y = combine(x, y) + * 1.0 if x = combine(x, y) + * NaN otherwise + */ + def asJoinPartialOrder(implicit ev: Eq[A]): PartialOrder[A] = + new PartialOrder[A] { + def partialCompare(x: A, y: A): Double = + if (ev.eqv(x, y)) 0.0 else { + val z = self.combine(x, y) + if (ev.eqv(y, z)) -1.0 else if (ev.eqv(x, z)) 1.0 else Double.NaN + } + } +} + +object Semilattice { + + /** + * Access an implicit `Semilattice[A]`. + */ + @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: Semilattice[A]): Semilattice[A] = ev +} diff --git a/kernel/src/main/scala/cats/kernel/package.scala b/kernel/src/main/scala/cats/kernel/package.scala new file mode 100644 index 0000000000..80fd3067b8 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/package.scala @@ -0,0 +1,3 @@ +package cats +package object kernel { +} From 38a3dc8e9a1222e26b0d2f208366b1ba851a0d2f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 10:33:14 -0400 Subject: [PATCH 02/17] Port relevant instances from algebra.std to cats.kernel.std. Algebra doesn't currently provide generic semigroups for number types, so this commit doesn't have any of those either. Cats is already working around this so that shouldn't be a big change. --- build.sbt | 5 +- .../src/main/scala/cats/kernel/std/Util.scala | 181 ++++++++++++++++++ .../src/main/scala/cats/kernel/std/all.scala | 23 +++ .../main/scala/cats/kernel/std/array.scala | 70 +++++++ .../main/scala/cats/kernel/std/bigInt.scala | 24 +++ .../main/scala/cats/kernel/std/boolean.scala | 24 +++ .../src/main/scala/cats/kernel/std/byte.scala | 26 +++ .../src/main/scala/cats/kernel/std/char.scala | 19 ++ .../main/scala/cats/kernel/std/double.scala | 26 +++ .../main/scala/cats/kernel/std/float.scala | 32 ++++ .../src/main/scala/cats/kernel/std/int.scala | 26 +++ .../src/main/scala/cats/kernel/std/list.scala | 95 +++++++++ .../src/main/scala/cats/kernel/std/long.scala | 27 +++ .../src/main/scala/cats/kernel/std/map.scala | 43 +++++ .../main/scala/cats/kernel/std/option.scala | 68 +++++++ .../src/main/scala/cats/kernel/std/set.scala | 18 ++ .../main/scala/cats/kernel/std/short.scala | 27 +++ .../main/scala/cats/kernel/std/string.scala | 24 +++ .../main/scala/cats/kernel/std/tuple.scala | 4 + .../src/main/scala/cats/kernel/std/unit.scala | 29 +++ project/KernelBoiler.scala | 172 +++++++++++++++++ 21 files changed, 960 insertions(+), 3 deletions(-) create mode 100644 kernel/src/main/scala/cats/kernel/std/Util.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/all.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/array.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/bigInt.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/boolean.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/byte.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/char.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/double.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/float.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/int.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/list.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/long.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/map.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/option.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/set.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/short.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/string.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/tuple.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/unit.scala create mode 100644 project/KernelBoiler.scala diff --git a/build.sbt b/build.sbt index 1eccc559a3..177acefb6b 100644 --- a/build.sbt +++ b/build.sbt @@ -176,6 +176,7 @@ lazy val kernel = crossProject.crossType(CrossType.Pure) .settings(buildSettings: _*) .settings(publishSettings: _*) .settings(scoverageSettings: _*) + .settings(sourceGenerators in Compile <+= (sourceManaged in Compile).map(KernelBoiler.gen)) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) @@ -199,9 +200,7 @@ lazy val core = crossProject.crossType(CrossType.Pure) .dependsOn(macros) .settings(moduleName := "cats-core") .settings(catsSettings:_*) - .settings( - sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen) - ) + .settings(sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen)) .settings(libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalacheckVersion % "test") .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) diff --git a/kernel/src/main/scala/cats/kernel/std/Util.scala b/kernel/src/main/scala/cats/kernel/std/Util.scala new file mode 100644 index 0000000000..93655d09e0 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/Util.scala @@ -0,0 +1,181 @@ +package cats.kernel.std.util + +import java.lang.Double.{ longBitsToDouble, doubleToLongBits } +import java.lang.Float.{ intBitsToFloat, floatToIntBits } +import java.lang.Long.{ numberOfTrailingZeros, numberOfLeadingZeros } +import java.lang.Math +import scala.annotation.tailrec +import scala.collection.mutable + +object StaticMethods { + + /** + * Implementation of the binary GCD algorithm. + */ + final def gcd(x0: Long, y0: Long): Long = { + // if either argument is 0, just return the other. + if (x0 == 0L) return y0 + if (y0 == 0L) return x0 + + val xz = numberOfTrailingZeros(x0) + val yz = numberOfTrailingZeros(y0) + + // Math.abs is safe because Long.MinValue (0x8000000000000000) + // will be shifted over 63 bits (due to trailing zeros). + var x = Math.abs(x0 >> xz) + var y = Math.abs(y0 >> yz) + + while (x != y) { + if (x > y) { + x -= y + x >>= numberOfTrailingZeros(x) + } else { + y -= x + y >>= numberOfTrailingZeros(y) + } + } + + // trailing zeros mean powers of two -- if both numbers had + // trailing zeros, those are part of the common divsor as well. + if (xz < yz) x << xz else x << yz + } + + /** + * GCD for Float values. + */ + final def gcd(a: Float, b: Float): Float = { + import java.lang.Integer.{ numberOfTrailingZeros, numberOfLeadingZeros } + + def value(bits: Int): Int = bits & 0x007FFFFF | 0x00800000 + + def exp(bits: Int): Int = ((bits >> 23) & 0xFF).toInt + + def gcd0(val0: Int, exp0: Int, val1: Int, exp1: Int): Float = { + val tz0 = numberOfTrailingZeros(val0) + val tz1 = numberOfTrailingZeros(val1) + val tzShared = Math.min(tz0, tz1 + exp1 - exp0) + val n = gcd(val0.toLong >>> tz0, val1.toLong >>> tz1).toInt << tzShared + + val shift = numberOfLeadingZeros(n) - 8 // Number of bits to move 1 to bit 23 + val mantissa = (n << shift) & 0x007FFFFF + val exp = (exp0 - shift) + if (exp < 0) 0F else intBitsToFloat((exp << 23) | mantissa) + } + + if (a == 0F) b + else if (b == 0F) a + else { + val aBits = floatToIntBits(a) + val aVal = value(aBits) + val aExp = exp(aBits) + + val bBits = floatToIntBits(b) + val bVal = value(bBits) + val bExp = exp(bBits) + + if (aExp < bExp) gcd0(aVal, aExp, bVal, bExp) + else gcd0(bVal, bExp, aVal, aExp) + } + } + + /** + * GCD for Double values. + */ + final def gcd(a: Double, b: Double): Double = { + def value(bits: Long): Long = bits & 0x000FFFFFFFFFFFFFL | 0x0010000000000000L + + def exp(bits: Long): Int = ((bits >> 52) & 0x7FF).toInt + + def gcd0(val0: Long, exp0: Int, val1: Long, exp1: Int): Double = { + val tz0 = numberOfTrailingZeros(val0) + val tz1 = numberOfTrailingZeros(val1) + val tzShared = Math.min(tz0, tz1 + exp1 - exp0) + val n = gcd(val0 >>> tz0, val1 >>> tz1) << tzShared + + val shift = numberOfLeadingZeros(n) - 11 // Number of bits to move 1 to bit 52 + val mantissa = (n << shift) & 0x000FFFFFFFFFFFFFL + val exp = (exp0 - shift).toLong + if (exp < 0) 0.0 else longBitsToDouble((exp << 52) | mantissa) + } + + if (a == 0D) b + else if (b == 0D) a + else { + val aBits = doubleToLongBits(a) + val aVal = value(aBits) + val aExp = exp(aBits) + + val bBits = doubleToLongBits(b) + val bVal = value(bBits) + val bExp = exp(bBits) + + if (aExp < bExp) gcd0(aVal, aExp, bVal, bExp) + else gcd0(bVal, bExp, aVal, aExp) + } + } + + /** + * Exponentiation function, e.g. x^y + * + * If base^ex doesn't fit in a Long, the result will overflow (unlike + * Math.pow which will return +/- Infinity). + */ + final def pow(base: Long, exponent: Long): Long = { + @tailrec def loop(t: Long, b: Long, e: Long): Long = + if (e == 0L) t + else if ((e & 1) == 1) loop(t * b, b * b, e >>> 1L) + else loop(t, b * b, e >>> 1L) + + if (exponent >= 0L) loop(1L, base, exponent) else { + if(base == 0L) throw new ArithmeticException("zero can't be raised to negative power") + else if (base == 1L) 1L + else if (base == -1L) if ((exponent & 1L) == 0L) -1L else 1L + else 0L + } + } + + def initMutableMap[K, V](m: Map[K, V]): mutable.Map[K, V] = { + val result = mutable.Map.empty[K, V] + m.foreach { case (k, v) => result(k) = v } + result + } + + def wrapMutableMap[K, V](m: mutable.Map[K, V]): Map[K, V] = + new WrappedMutableMap(m) + + private[kernel] class WrappedMutableMap[K, V](m: mutable.Map[K, V]) extends Map[K, V] { + override def size: Int = m.size + def get(k: K): Option[V] = m.get(k) + def iterator: Iterator[(K, V)] = m.iterator + def +[V2 >: V](kv: (K, V2)): Map[K, V2] = m.toMap + kv + def -(key: K): Map[K, V] = m.toMap - key + } + + def addMap[K, V](x: Map[K, V], y: Map[K, V])(f: (V, V) => V): Map[K, V] = { + val (small, big, g) = + if (x.size <= y.size) (x, y, f) + else (y, x, (v1: V, v2: V) => f(v2, v1)) + + val m = initMutableMap(big) + small.foreach { case (k, v1) => + m(k) = m.get(k) match { + case Some(v2) => g(v1, v2) + case None => v1 + } + } + wrapMutableMap(m) + } + + def subtractMap[K, V](x: Map[K, V], y: Map[K, V])(subtract: (V, V) => V)(negate: V => V): Map[K, V] = { + // even if x is smaller, we'd need to call map/foreach on y to + // negate all its values, so this is just as fast or faster. + val m = initMutableMap(x) + y.foreach { case (k, v2) => + m(k) = m.get(k) match { + case Some(v1) => subtract(v1, v2) + case None => negate(v2) + } + } + wrapMutableMap(m) + } +} diff --git a/kernel/src/main/scala/cats/kernel/std/all.scala b/kernel/src/main/scala/cats/kernel/std/all.scala new file mode 100644 index 0000000000..233e7d6c9c --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/all.scala @@ -0,0 +1,23 @@ +package cats.kernel +package std + +package object all extends AllInstances + +trait AllInstances + extends BigIntInstances + with BooleanInstances + with ByteInstances + with CharInstances + with DoubleInstances + with FloatInstances + with IntInstances + with ListInstances + with LongInstances + with MapInstances + with OptionInstances + with SetInstances + with ShortInstances + with StringInstances + with TupleInstances + with UnitInstances + with ArrayInstances diff --git a/kernel/src/main/scala/cats/kernel/std/array.scala b/kernel/src/main/scala/cats/kernel/std/array.scala new file mode 100644 index 0000000000..1cad7e6185 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/array.scala @@ -0,0 +1,70 @@ +package cats.kernel +package std + +import scala.{ specialized => sp } + +package object array extends ArrayInstances + +trait ArrayInstances { + implicit def arrayEq[@sp A: Eq]: Eq[Array[A]] = + new ArrayEq[A] + implicit def arrayOrder[@sp A: Order]: Order[Array[A]] = + new ArrayOrder[A] + implicit def arrayPartialOrder[@sp A: PartialOrder]: PartialOrder[Array[A]] = + new ArrayPartialOrder[A] +} + +object ArraySupport { + + private def signum(x: Int): Int = + if(x < 0) -1 + else if(x > 0) 1 + else 0 + + def eqv[@sp A: Eq](x: Array[A], y: Array[A]): Boolean = { + var i = 0 + if (x.length != y.length) return false + while (i < x.length && i < y.length && Eq.eqv(x(i), y(i))) i += 1 + i == x.length + } + + def compare[@sp A: Order](x: Array[A], y: Array[A]): Int = { + var i = 0 + while (i < x.length && i < y.length) { + val cmp = Order.compare(x(i), y(i)) + if (cmp != 0) return cmp + i += 1 + } + signum(x.length - y.length) + } + + def partialCompare[@sp A: PartialOrder](x: Array[A], y: Array[A]): Double = { + var i = 0 + while (i < x.length && i < y.length) { + val cmp = PartialOrder.partialCompare(x(i), y(i)) + // Double.NaN is also != 0.0 + if (cmp != 0.0) return cmp + i += 1 + } + signum(x.length - y.length).toDouble + } +} + +final class ArrayEq[@sp A: Eq] extends Eq[Array[A]] { + def eqv(x: Array[A], y: Array[A]): Boolean = + ArraySupport.eqv(x, y) +} + +final class ArrayOrder[@sp A: Order] extends Order[Array[A]] { + override def eqv(x: Array[A], y: Array[A]): Boolean = + ArraySupport.eqv(x, y) + def compare(x: Array[A], y: Array[A]): Int = + ArraySupport.compare(x, y) +} + +final class ArrayPartialOrder[@sp A: PartialOrder] extends PartialOrder[Array[A]] { + override def eqv(x: Array[A], y: Array[A]): Boolean = + ArraySupport.eqv(x, y) + override def partialCompare(x: Array[A], y: Array[A]): Double = + ArraySupport.partialCompare(x, y) +} diff --git a/kernel/src/main/scala/cats/kernel/std/bigInt.scala b/kernel/src/main/scala/cats/kernel/std/bigInt.scala new file mode 100644 index 0000000000..13afc51058 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/bigInt.scala @@ -0,0 +1,24 @@ +package cats.kernel +package std + +package object bigInt extends BigIntInstances + +trait BigIntInstances { + implicit val bigIntOrder: Order[BigInt] = + new BigIntOrder +} + +class BigIntOrder extends Order[BigInt] { + + def compare(x: BigInt, y: BigInt): Int = x compare y + + override def eqv(x:BigInt, y:BigInt): Boolean = x == y + override def neqv(x:BigInt, y:BigInt): Boolean = x != y + override def gt(x: BigInt, y: BigInt): Boolean = x > y + override def gteqv(x: BigInt, y: BigInt): Boolean = x >= y + override def lt(x: BigInt, y: BigInt): Boolean = x < y + override def lteqv(x: BigInt, y: BigInt): Boolean = x <= y + + override def min(x: BigInt, y: BigInt): BigInt = x min y + override def max(x: BigInt, y: BigInt): BigInt = x max y +} diff --git a/kernel/src/main/scala/cats/kernel/std/boolean.scala b/kernel/src/main/scala/cats/kernel/std/boolean.scala new file mode 100644 index 0000000000..8f83996118 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/boolean.scala @@ -0,0 +1,24 @@ +package cats.kernel +package std + +package object boolean extends BooleanInstances + +trait BooleanInstances { + implicit val booleanOrder: BooleanOrder = + new BooleanOrder +} + +class BooleanOrder extends Order[Boolean] { + def compare(x: Boolean, y: Boolean): Int = + if (x == y) 0 else if (x) 1 else -1 + + override def eqv(x:Boolean, y:Boolean): Boolean = x == y + override def neqv(x:Boolean, y:Boolean): Boolean = x != y + override def gt(x: Boolean, y: Boolean): Boolean = x && !y + override def lt(x: Boolean, y: Boolean): Boolean = !x && y + override def gteqv(x: Boolean, y: Boolean): Boolean = x == y || x + override def lteqv(x: Boolean, y: Boolean): Boolean = x == y || y + + override def min(x: Boolean, y: Boolean): Boolean = x && y + override def max(x: Boolean, y: Boolean): Boolean = x || y +} diff --git a/kernel/src/main/scala/cats/kernel/std/byte.scala b/kernel/src/main/scala/cats/kernel/std/byte.scala new file mode 100644 index 0000000000..0b1a948ff8 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/byte.scala @@ -0,0 +1,26 @@ +package cats.kernel +package std + +package object byte extends ByteInstances + +trait ByteInstances { + implicit val byteOrder: Order[Byte] = new ByteOrder +} + +class ByteOrder extends Order[Byte] { + + def compare(x: Byte, y: Byte): Int = + if (x < y) -1 else if (x > y) 1 else 0 + + override def eqv(x: Byte, y: Byte) = x == y + override def neqv(x: Byte, y: Byte) = x != y + override def gt(x: Byte, y: Byte) = x > y + override def gteqv(x: Byte, y: Byte) = x >= y + override def lt(x: Byte, y: Byte) = x < y + override def lteqv(x: Byte, y: Byte) = x <= y + + override def min(x: Byte, y: Byte): Byte = + java.lang.Math.min(x.toInt, y.toInt).toByte + override def max(x: Byte, y: Byte): Byte = + java.lang.Math.max(x.toInt, y.toInt).toByte +} diff --git a/kernel/src/main/scala/cats/kernel/std/char.scala b/kernel/src/main/scala/cats/kernel/std/char.scala new file mode 100644 index 0000000000..7a863a8cab --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/char.scala @@ -0,0 +1,19 @@ +package cats.kernel +package std + +package object char extends CharInstances + +trait CharInstances { + implicit val charOrder = new CharOrder +} + +class CharOrder extends Order[Char] { + def compare(x: Char, y: Char) = + if (x < y) -1 else if (x > y) 1 else 0 + override def eqv(x:Char, y:Char) = x == y + override def neqv(x:Char, y:Char) = x != y + override def gt(x: Char, y: Char) = x > y + override def gteqv(x: Char, y: Char) = x >= y + override def lt(x: Char, y: Char) = x < y + override def lteqv(x: Char, y: Char) = x <= y +} diff --git a/kernel/src/main/scala/cats/kernel/std/double.scala b/kernel/src/main/scala/cats/kernel/std/double.scala new file mode 100644 index 0000000000..32945635c0 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/double.scala @@ -0,0 +1,26 @@ +package cats.kernel +package std + +import java.lang.Math + +trait DoubleInstances { + implicit val doubleOrder: Order[Double] = new DoubleOrder +} + +class DoubleOrder extends Order[Double] { + + def compare(x: Double, y: Double): Int = + java.lang.Double.compare(x, y) + + override def eqv(x:Double, y:Double) = x == y + override def neqv(x:Double, y:Double) = x != y + override def gt(x: Double, y: Double) = x > y + override def gteqv(x: Double, y: Double) = x >= y + override def lt(x: Double, y: Double) = x < y + override def lteqv(x: Double, y: Double) = x <= y + + override def min(x: Double, y: Double): Double = + Math.min(x, y) + override def max(x: Double, y: Double): Double = + Math.max(x, y) +} diff --git a/kernel/src/main/scala/cats/kernel/std/float.scala b/kernel/src/main/scala/cats/kernel/std/float.scala new file mode 100644 index 0000000000..f50e96f1fc --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/float.scala @@ -0,0 +1,32 @@ +package cats.kernel +package std + +trait FloatInstances { + implicit val floatOrder = new FloatOrder +} + +/** + * Due to the way floating-point equality works, this instance is not + * lawful under equality, but is correct when taken as an + * approximation of an exact value. + * + * If you would prefer an absolutely lawful fractional value, you'll + * need to investigate rational numbers or more exotic types. + */ +class FloatOrder extends Order[Float] { + + def compare(x: Float, y: Float) = + java.lang.Float.compare(x, y) + + override def eqv(x:Float, y:Float) = x == y + override def neqv(x:Float, y:Float) = x != y + override def gt(x: Float, y: Float) = x > y + override def gteqv(x: Float, y: Float) = x >= y + override def lt(x: Float, y: Float) = x < y + override def lteqv(x: Float, y: Float) = x <= y + + override def min(x: Float, y: Float): Float = + java.lang.Math.min(x, y) + override def max(x: Float, y: Float): Float = + java.lang.Math.max(x, y) +} diff --git a/kernel/src/main/scala/cats/kernel/std/int.scala b/kernel/src/main/scala/cats/kernel/std/int.scala new file mode 100644 index 0000000000..59fb8474ff --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/int.scala @@ -0,0 +1,26 @@ +package cats.kernel +package std + +package object int extends IntInstances + +trait IntInstances { + implicit val intOrder: Order[Int] = new IntOrder +} + +class IntOrder extends Order[Int] { + + def compare(x: Int, y: Int): Int = + if (x < y) -1 else if (x > y) 1 else 0 + + override def eqv(x: Int, y: Int) = x == y + override def neqv(x: Int, y: Int) = x != y + override def gt(x: Int, y: Int) = x > y + override def gteqv(x: Int, y: Int) = x >= y + override def lt(x: Int, y: Int) = x < y + override def lteqv(x: Int, y: Int) = x <= y + + override def min(x: Int, y: Int): Int = + java.lang.Math.min(x, y) + override def max(x: Int, y: Int): Int = + java.lang.Math.max(x, y) +} diff --git a/kernel/src/main/scala/cats/kernel/std/list.scala b/kernel/src/main/scala/cats/kernel/std/list.scala new file mode 100644 index 0000000000..ee717ac78e --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/list.scala @@ -0,0 +1,95 @@ +package cats.kernel +package std + +import scala.annotation.tailrec +import scala.collection.mutable + +package object list extends ListInstances + +trait ListInstances extends ListInstances1 { + implicit def listOrder[A: Order] = new ListOrder[A] + implicit def listMonoid[A] = new ListMonoid[A] +} + +trait ListInstances1 extends ListInstances2 { + implicit def listPartialOrder[A: PartialOrder] = new ListPartialOrder[A] +} + +trait ListInstances2 { + implicit def listEq[A: Eq] = new ListEq[A] +} + +class ListOrder[A](implicit ev: Order[A]) extends Order[List[A]] { + def compare(xs: List[A], ys: List[A]): Int = { + @tailrec def loop(xs: List[A], ys: List[A]): Int = + xs match { + case Nil => + if (ys.isEmpty) 0 else -1 + case x :: xs => + ys match { + case Nil => 1 + case y :: ys => + val n = ev.compare(x, y) + if (n != 0) n else loop(xs, ys) + } + } + loop(xs, ys) + } +} + +class ListPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[List[A]] { + def partialCompare(xs: List[A], ys: List[A]): Double = { + @tailrec def loop(xs: List[A], ys: List[A]): Double = + xs match { + case Nil => + if (ys.isEmpty) 0.0 else -1.0 + case x :: xs => + ys match { + case Nil => 1.0 + case y :: ys => + val n = ev.partialCompare(x, y) + if (n != 0.0) n else loop(xs, ys) + } + } + loop(xs, ys) + } +} + +class ListEq[A](implicit ev: Eq[A]) extends Eq[List[A]] { + def eqv(x: List[A], y: List[A]): Boolean = { + def loop(xs: List[A], ys: List[A]): Boolean = + xs match { + case Nil => + ys.isEmpty + case x :: xs => + ys match { + case y :: ys => + if (ev.eqv(x, y)) loop(xs, ys) else false + case Nil => + false + } + } + loop(x, y) + } +} + +class ListMonoid[A] extends Monoid[List[A]] { + def empty: List[A] = Nil + def combine(x: List[A], y: List[A]): List[A] = x ::: y + + override def combineN(x: List[A], n: Int): List[A] = { + val buf = mutable.ListBuffer.empty[A] + var i = n + while (i > 0) { + buf ++= x + i -= 1 + } + buf.toList + } + + override def combineAll(xs: TraversableOnce[List[A]]): List[A] = { + val buf = mutable.ListBuffer.empty[A] + xs.foreach(buf ++= _) + buf.toList + } +} diff --git a/kernel/src/main/scala/cats/kernel/std/long.scala b/kernel/src/main/scala/cats/kernel/std/long.scala new file mode 100644 index 0000000000..8d978062cc --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/long.scala @@ -0,0 +1,27 @@ +package cats.kernel +package std + +package object long extends LongInstances + +trait LongInstances { + implicit val longOrder = new LongOrder +} + +class LongOrder extends Order[Long] { + + // use java.lang.Long.compare if we can rely on java >= 1.7 + def compare(x: Long, y: Long): Int = + if (x < y) -1 else if (x > y) 1 else 0 + + override def eqv(x: Long, y: Long) = x == y + override def neqv(x: Long, y: Long) = x != y + override def gt(x: Long, y: Long) = x > y + override def gteqv(x: Long, y: Long) = x >= y + override def lt(x: Long, y: Long) = x < y + override def lteqv(x: Long, y: Long) = x <= y + + override def min(x: Long, y: Long): Long = + java.lang.Math.min(x, y) + override def max(x: Long, y: Long): Long = + java.lang.Math.max(x, y) +} diff --git a/kernel/src/main/scala/cats/kernel/std/map.scala b/kernel/src/main/scala/cats/kernel/std/map.scala new file mode 100644 index 0000000000..912b7bab48 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/map.scala @@ -0,0 +1,43 @@ +package cats.kernel +package std + +import cats.kernel.std.util.StaticMethods.{ addMap, subtractMap } + +package object map extends MapInstances + +trait MapInstances extends MapInstances0 { + implicit def mapGroup[K, V: Group]: MapGroup[K, V] = + new MapGroup[K, V] +} + +trait MapInstances0 { + implicit def mapEq[K, V: Eq]: Eq[Map[K, V]] = + new MapEq[K, V] + implicit def mapMonoid[K, V: Semigroup]: MapMonoid[K, V] = + new MapMonoid[K, V] +} + +class MapEq[K, V](implicit V: Eq[V]) extends Eq[Map[K, V]] { + def eqv(x: Map[K, V], y: Map[K, V]): Boolean = + x.size == y.size && x.forall { case (k, v1) => + y.get(k) match { + case Some(v2) => V.eqv(v1, v2) + case None => false + } + } +} + +class MapMonoid[K, V](implicit V: Semigroup[V]) extends Monoid[Map[K, V]] { + def empty: Map[K, V] = Map.empty + + def combine(x: Map[K, V], y: Map[K, V]): Map[K, V] = + addMap(x, y)(V.combine) +} + +class MapGroup[K, V](implicit V: Group[V]) extends MapMonoid[K, V] with Group[Map[K, V]] { + def inverse(x: Map[K, V]): Map[K, V] = + x.map { case (k, v) => (k, V.inverse(v)) } + + override def remove(x: Map[K, V], y: Map[K, V]): Map[K, V] = + subtractMap(x, y)(V.remove)(V.inverse) +} diff --git a/kernel/src/main/scala/cats/kernel/std/option.scala b/kernel/src/main/scala/cats/kernel/std/option.scala new file mode 100644 index 0000000000..db0c8a7126 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/option.scala @@ -0,0 +1,68 @@ +package cats.kernel +package std + +package object option extends OptionInstances + +trait OptionInstances extends OptionInstances1 { + implicit def optionOrder[A: Order] = new OptionOrder[A] + implicit def optionMonoid[A: Semigroup] = new OptionMonoid[A] +} + +trait OptionInstances1 extends OptionInstances0 { + implicit def optionPartialOrder[A: PartialOrder] = new OptionPartialOrder[A] +} + +trait OptionInstances0 { + implicit def optionEq[A: Eq] = new OptionEq[A] +} + +class OptionOrder[A](implicit A: Order[A]) extends Order[Option[A]] { + def compare(x: Option[A], y: Option[A]): Int = + x match { + case None => + if (y.isEmpty) 0 else -1 + case Some(a) => + y match { + case None => 1 + case Some(b) => A.compare(a, b) + } + } +} + +class OptionPartialOrder[A](implicit A: PartialOrder[A]) extends PartialOrder[Option[A]] { + def partialCompare(x: Option[A], y: Option[A]): Double = + x match { + case None => + if (y.isEmpty) 0.0 else -1.0 + case Some(a) => + y match { + case None => 1.0 + case Some(b) => A.partialCompare(a, b) + } + } +} + +class OptionEq[A](implicit A: Eq[A]) extends Eq[Option[A]] { + def eqv(x: Option[A], y: Option[A]): Boolean = + x match { + case None => y.isEmpty + case Some(a) => + y match { + case None => false + case Some(b) => A.eqv(a, b) + } + } +} + +class OptionMonoid[A](implicit A: Semigroup[A]) extends Monoid[Option[A]] { + def empty: Option[A] = None + def combine(x: Option[A], y: Option[A]): Option[A] = + x match { + case None => y + case Some(a) => + y match { + case None => x + case Some(b) => Some(A.combine(a, b)) + } + } +} diff --git a/kernel/src/main/scala/cats/kernel/std/set.scala b/kernel/src/main/scala/cats/kernel/std/set.scala new file mode 100644 index 0000000000..ab5e178d3c --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/set.scala @@ -0,0 +1,18 @@ +package cats.kernel +package std + +package object set extends SetInstances + +trait SetInstances { + implicit def setPartialOrder[A]: PartialOrder[Set[A]] = new SetPartialOrder[A] +} + +class SetPartialOrder[A] extends PartialOrder[Set[A]] { + def partialCompare(x: Set[A], y: Set[A]): Double = + if (x.size < y.size) if (x.subsetOf(y)) -1.0 else Double.NaN + else if (y.size < x.size) -partialCompare(y, x) + else if (x == y) 0.0 + else Double.NaN + + override def eqv(x: Set[A], y: Set[A]): Boolean = x == y +} diff --git a/kernel/src/main/scala/cats/kernel/std/short.scala b/kernel/src/main/scala/cats/kernel/std/short.scala new file mode 100644 index 0000000000..ba3582205c --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/short.scala @@ -0,0 +1,27 @@ +package cats.kernel +package std + +package object short extends ShortInstances + +trait ShortInstances { + implicit val shortOrder: Order[Short] = new ShortOrder +} + +class ShortOrder extends Order[Short] { + + // use java.lang.Short.compare if we can rely on java >= 1.7 + def compare(x: Short, y: Short): Int = + if (x < y) -1 else if (x > y) 1 else 0 + + override def eqv(x: Short, y: Short) = x == y + override def neqv(x: Short, y: Short) = x != y + override def gt(x: Short, y: Short) = x > y + override def gteqv(x: Short, y: Short) = x >= y + override def lt(x: Short, y: Short) = x < y + override def lteqv(x: Short, y: Short) = x <= y + + override def min(x: Short, y: Short): Short = + java.lang.Math.min(x.toInt, y.toInt).toShort + override def max(x: Short, y: Short): Short = + java.lang.Math.max(x.toInt, y.toInt).toShort +} diff --git a/kernel/src/main/scala/cats/kernel/std/string.scala b/kernel/src/main/scala/cats/kernel/std/string.scala new file mode 100644 index 0000000000..52ac8de129 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/string.scala @@ -0,0 +1,24 @@ +package cats.kernel +package std + +package object string extends StringInstances + +trait StringInstances { + implicit val stringOrder: Order[String] = new StringOrder + implicit val stringMonoid = new StringMonoid +} + +class StringOrder extends Order[String] { + def compare(x: String, y: String): Int = x compare y +} + +class StringMonoid extends Monoid[String] { + def empty: String = "" + def combine(x: String, y: String): String = x + y + + override def combineAll(xs: TraversableOnce[String]): String = { + val sb = new StringBuilder + xs.foreach(sb.append) + sb.toString + } +} diff --git a/kernel/src/main/scala/cats/kernel/std/tuple.scala b/kernel/src/main/scala/cats/kernel/std/tuple.scala new file mode 100644 index 0000000000..ce1cb89643 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/tuple.scala @@ -0,0 +1,4 @@ +package cats.kernel +package std + +package object tuple extends TupleInstances diff --git a/kernel/src/main/scala/cats/kernel/std/unit.scala b/kernel/src/main/scala/cats/kernel/std/unit.scala new file mode 100644 index 0000000000..8068b246de --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/unit.scala @@ -0,0 +1,29 @@ +package cats.kernel +package std + +package object unit extends UnitInstances + +trait UnitInstances { + implicit val unitAlgebra: Order[Unit] with BoundedSemilattice[Unit] = + new UnitAlgebra +} + +class UnitAlgebra extends Order[Unit] with BoundedSemilattice[Unit] { + def compare(x: Unit, y: Unit): Int = 0 + + override def eqv(x: Unit, y: Unit): Boolean = true + override def neqv(x: Unit, y: Unit): Boolean = false + override def gt(x: Unit, y: Unit): Boolean = false + override def lt(x: Unit, y: Unit): Boolean = false + override def gteqv(x: Unit, y: Unit): Boolean = true + override def lteqv(x: Unit, y: Unit): Boolean = true + + override def min(x: Unit, y: Unit): Unit = () + override def max(x: Unit, y: Unit): Unit = () + + def empty: Unit = () + def combine(x: Unit, y: Unit): Unit = () + override protected[this] def repeatedCombineN(a: Unit, n: Int): Unit = () + override def combineAllOption(as: TraversableOnce[Unit]): Option[Unit] = + if (as.isEmpty) None else Some(()) +} diff --git a/project/KernelBoiler.scala b/project/KernelBoiler.scala new file mode 100644 index 0000000000..69a46537fe --- /dev/null +++ b/project/KernelBoiler.scala @@ -0,0 +1,172 @@ +import sbt._ + +/** + * Generate a range of boilerplate classes that would be tedious to write and maintain by hand. + * + * Copied, with some modifications, from + * [[https://github.com/milessabin/shapeless/blob/master/project/Boilerplate.scala Shapeless]]. + * + * @author Miles Sabin + * @author Kevin Wright + */ +object KernelBoiler { + import scala.StringContext._ + + implicit class BlockHelper(val sc: StringContext) extends AnyVal { + def block(args: Any*): String = { + val interpolated = sc.standardInterpolator(treatEscapes, args) + val rawLines = interpolated.split('\n') + val trimmedLines = rawLines.map(_.dropWhile(_.isWhitespace)) + trimmedLines.mkString("\n") + } + } + + val templates: Seq[Template] = Seq( + GenTupleInstances + ) + + val header = "// auto-generated boilerplate" + val maxArity = 22 + + /** + * Return a sequence of the generated files. + * + * As a side-effect, it actually generates them... + */ + def gen(dir: File): Seq[File] = templates.map { template => + val tgtFile = template.filename(dir) + IO.write(tgtFile, template.body) + tgtFile + } + + class TemplateVals(val arity: Int) { + val synTypes = (0 until arity).map(n => s"A$n") + val synVals = (0 until arity).map(n => s"a$n") + val `A..N` = synTypes.mkString(", ") + val `a..n` = synVals.mkString(", ") + val `_.._` = Seq.fill(arity)("_").mkString(", ") + val `(A..N)` = if (arity == 1) "Tuple1[A0]" else synTypes.mkString("(", ", ", ")") + val `(_.._)` = if (arity == 1) "Tuple1[_]" else Seq.fill(arity)("_").mkString("(", ", ", ")") + val `(a..n)` = if (arity == 1) "Tuple1(a)" else synVals.mkString("(", ", ", ")") + } + + /** + * Blocks in the templates below use a custom interpolator, combined with post-processing to + * produce the body. + * + * - The contents of the `header` val is output first + * - Then the first block of lines beginning with '|' + * - Then the block of lines beginning with '-' is replicated once for each arity, + * with the `templateVals` already pre-populated with relevant relevant vals for that arity + * - Then the last block of lines prefixed with '|' + * + * The block otherwise behaves as a standard interpolated string with regards to variable + * substitution. + */ + trait Template { + def filename(root: File): File + def content(tv: TemplateVals): String + def range: IndexedSeq[Int] = 1 to maxArity + def body: String = { + val headerLines = header.split('\n') + val raw = range.map(n => content(new TemplateVals(n)).split('\n').filterNot(_.isEmpty)) + val preBody = raw.head.takeWhile(_.startsWith("|")).map(_.tail) + val instances = raw.flatMap(_.filter(_.startsWith("-")).map(_.tail)) + val postBody = raw.head.dropWhile(_.startsWith("|")).dropWhile(_.startsWith("-")).map(_.tail) + (headerLines ++ preBody ++ instances ++ postBody).mkString("\n") + } + } + + object GenTupleInstances extends Template { + override def range: IndexedSeq[Int] = 1 to maxArity + + def filename(root: File): File = root / "cats" / "kernel" / "std" / "TupleAlgebra.scala" + + def content(tv: TemplateVals): String = { + import tv._ + + def constraints(constraint: String) = + synTypes.map(tpe => s"${tpe}: ${constraint}[${tpe}]").mkString(", ") + + def tuple(results: TraversableOnce[String]) = { + val resultsVec = results.toVector + val a = synTypes.size + val r = s"${0.until(a).map(i => resultsVec(i)).mkString(", ")}" + if (a == 1) "Tuple1(" ++ r ++ ")" + else s"(${r})" + } + + def binMethod(name: String) = + synTypes.zipWithIndex.iterator.map { + case (tpe, i) => + val j = i + 1 + s"${tpe}.${name}(x._${j}, y._${j})" + } + + def binTuple(name: String) = + tuple(binMethod(name)) + + def unaryTuple(name: String) = { + val m = synTypes.zipWithIndex.map { case (tpe, i) => s"${tpe}.${name}(x._${i + 1})" } + tuple(m) + } + + def nullaryTuple(name: String) = { + val m = synTypes.map(tpe => s"${tpe}.${name}") + tuple(m) + } + + block""" + |package cats.kernel + |package std + | + |trait TupleInstances { + - implicit def tuple${arity}Band[${`A..N`}](implicit ${constraints("Band")}): Band[${`(A..N)`}] = + - new Band[${`(A..N)`}] { + - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} + - } + - + - implicit def tuple${arity}Group[${`A..N`}](implicit ${constraints("Group")}): Group[${`(A..N)`}] = + - new Group[${`(A..N)`}] { + - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} + - def empty: ${`(A..N)`} = ${nullaryTuple("empty")} + - def inverse(x: ${`(A..N)`}): ${`(A..N)`} = ${unaryTuple("inverse")} + - } + - + - implicit def tuple${arity}Eq[${`A..N`}](implicit ${constraints("Eq")}): Eq[${`(A..N)`}] = + - new Eq[${`(A..N)`}] { + - def eqv(x: ${`(A..N)`}, y: ${`(A..N)`}): Boolean = ${binMethod("eqv").mkString(" && ")} + - } + - + - implicit def tuple${arity}Monoid[${`A..N`}](implicit ${constraints("Monoid")}): Monoid[${`(A..N)`}] = + - new Monoid[${`(A..N)`}] { + - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} + - def empty: ${`(A..N)`} = ${nullaryTuple("empty")} + - } + - + - implicit def tuple${arity}Order[${`A..N`}](implicit ${constraints("Order")}): Order[${`(A..N)`}] = + - new Order[${`(A..N)`}] { + - def compare(x: ${`(A..N)`}, y: ${`(A..N)`}): Int = + - ${binMethod("compare").find(_ != 0).getOrElse(0)} + - } + - + - implicit def tuple${arity}PartialOrder[${`A..N`}](implicit ${constraints("PartialOrder")}): PartialOrder[${`(A..N)`}] = + - new PartialOrder[${`(A..N)`}] { + - def partialCompare(x: ${`(A..N)`}, y: ${`(A..N)`}): Double = + - ${binMethod("partialCompare").find(_ != 0.0).getOrElse(0.0)} + - } + - + - implicit def tuple${arity}Semigroup[${`A..N`}](implicit ${constraints("Semigroup")}): Semigroup[${`(A..N)`}] = + - new Semigroup[${`(A..N)`}] { + - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} + - } + - + - implicit def tuple${arity}Semilattice[${`A..N`}](implicit ${constraints("Semilattice")}): Semilattice[${`(A..N)`}] = + - new Semilattice[${`(A..N)`}] { + - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} + - } + |} + """ + } + } +} From 157710f9f64308247b9f6d4e373a22127d0d235b Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 11:00:56 -0400 Subject: [PATCH 03/17] Get the relevant bits of algebra.laws ported over. There are a few things here: 1. Many of algebra's interesting laws apply to rings/lattices. 2. We introduce a (laws-only) dependency on catalysts-platform. 3. Unlike Cats, we'll run the tests in kernel-laws' tests. --- build.sbt | 14 +- .../scala/cats/kernel/laws/BaseLaws.scala | 27 ++++ .../scala/cats/kernel/laws/CheckSupport.scala | 12 ++ .../scala/cats/kernel/laws/GroupLaws.scala | 142 ++++++++++++++++++ .../cats/kernel/laws/IsSerializable.scala | 14 ++ .../scala/cats/kernel/laws/OrderLaws.scala | 79 ++++++++++ .../main/scala/cats/kernel/laws/Rules.scala | 128 ++++++++++++++++ .../main/scala/cats/kernel/laws/package.scala | 45 ++++++ 8 files changed, 456 insertions(+), 5 deletions(-) create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/BaseLaws.scala create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/OrderLaws.scala create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/package.scala diff --git a/build.sbt b/build.sbt index 177acefb6b..76c13a1792 100644 --- a/build.sbt +++ b/build.sbt @@ -91,8 +91,11 @@ lazy val scalacheckVersion = "1.12.5" lazy val disciplineDependencies = Seq( libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalacheckVersion, - libraryDependencies += "org.typelevel" %%% "discipline" % "0.4" -) + libraryDependencies += "org.typelevel" %%% "discipline" % "0.4") + +lazy val testingDependencies = Seq( + libraryDependencies += "org.typelevel" %%% "catalysts-platform" % "0.0.2", + libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test") /** * Remove 2.10 projects from doc generation, as the macros used in the projects @@ -190,8 +193,11 @@ lazy val kernelLaws = crossProject.crossType(CrossType.Pure) .settings(buildSettings: _*) .settings(publishSettings: _*) .settings(scoverageSettings: _*) + .settings(disciplineDependencies: _*) + .settings(testingDependencies: _*) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) + .dependsOn(kernel) lazy val kernelLawsJVM = kernelLaws.jvm lazy val kernelLawsJS = kernelLaws.js @@ -228,9 +234,7 @@ lazy val tests = crossProject.crossType(CrossType.Pure) .settings(catsSettings:_*) .settings(disciplineDependencies:_*) .settings(noPublishSettings:_*) - .settings(libraryDependencies ++= Seq( - "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test", - "org.typelevel" %%% "catalysts-platform" % "0.0.2" % "test")) + .settings(testingDependencies: _*) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/BaseLaws.scala b/kernel-laws/src/main/scala/cats/kernel/laws/BaseLaws.scala new file mode 100644 index 0000000000..54bda4b136 --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/BaseLaws.scala @@ -0,0 +1,27 @@ +package cats.kernel.laws + +import cats.kernel._ + +import org.typelevel.discipline.Laws + +import org.scalacheck.{Arbitrary, Prop} + +object BaseLaws { + def apply[A : Eq : Arbitrary] = new BaseLaws[A] { + def Equ = Eq[A] + def Arb = implicitly[Arbitrary[A]] + } +} + +trait BaseLaws[A] extends Laws { + + implicit def Equ: Eq[A] + implicit def Arb: Arbitrary[A] + + class BaseRuleSet( + val name: String, + val parent: Option[RuleSet], + val bases: Seq[(String, Laws#RuleSet)], + val props: (String, Prop)* + ) extends RuleSet with HasOneParent +} diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala b/kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala new file mode 100644 index 0000000000..ad80b28a19 --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala @@ -0,0 +1,12 @@ +// package cats.kernel.laws +// +// /** +// * This object contains Arbitrary instances for types defined in +// * cats.kernel.std, as well as anything else we'd like to import to assist +// * in running ScalaCheck tests. +// * +// * (Since cats.kernel-std has no dependencies, its types can't define +// * Arbitrary instances in companions.) +// */ +// object CheckSupport { +// } diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala b/kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala new file mode 100644 index 0000000000..f12ed7d2e8 --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala @@ -0,0 +1,142 @@ +package cats.kernel.laws + +import cats.kernel._ + +import org.typelevel.discipline.Laws + +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop._ + +object GroupLaws { + def apply[A : Eq : Arbitrary] = new GroupLaws[A] { + def Equ = Eq[A] + def Arb = implicitly[Arbitrary[A]] + } +} + +trait GroupLaws[A] extends Laws { + + implicit def Equ: Eq[A] + implicit def Arb: Arbitrary[A] + + // groups + + def semigroup(implicit A: Semigroup[A]) = new GroupProperties( + name = "semigroup", + parents = Nil, + Rules.serializable(A), + Rules.associativity(A.combine), + Rules.repeat1("combineN")(A.combineN), + Rules.repeat2("combineN", "|+|")(A.combineN)(A.combine) + ) + + def band(implicit A: Band[A]) = new GroupProperties( + name = "band", + parents = List(semigroup), + Rules.idempotence(A.combine), + "isIdempotent" -> Semigroup.isIdempotent[A] + ) + + def commutativeSemigroup(implicit A: CommutativeSemigroup[A]) = new GroupProperties( + name = "commutative semigroup", + parents = List(semigroup), + Rules.commutative(A.combine) + ) + + def semilattice(implicit A: Semilattice[A]) = new GroupProperties( + name = "semilattice", + parents = List(band, commutativeSemigroup) + ) + + def monoid(implicit A: Monoid[A]) = new GroupProperties( + name = "monoid", + parents = List(semigroup), + Rules.leftIdentity(A.empty)(A.combine), + Rules.rightIdentity(A.empty)(A.combine), + Rules.repeat0("combineN", "id", A.empty)(A.combineN), + Rules.collect0("combineAll", "id", A.empty)(A.combineAll), + Rules.isId("isEmpty", A.empty)(A.isEmpty) + ) + + def commutativeMonoid(implicit A: CommutativeMonoid[A]) = new GroupProperties( + name = "commutative monoid", + parents = List(monoid, commutativeSemigroup) + ) + + def boundedSemilattice(implicit A: BoundedSemilattice[A]) = new GroupProperties( + name = "boundedSemilattice", + parents = List(commutativeMonoid, semilattice) + ) + + def group(implicit A: Group[A]) = new GroupProperties( + name = "group", + parents = List(monoid), + Rules.leftInverse(A.empty)(A.combine)(A.inverse), + Rules.rightInverse(A.empty)(A.combine)(A.inverse), + Rules.consistentInverse("remove")(A.remove)(A.combine)(A.inverse) + ) + + def commutativeGroup(implicit A: CommutativeGroup[A]) = new GroupProperties( + name = "commutative group", + parents = List(group, commutativeMonoid) + ) + + // // additive groups + // + // def additiveSemigroup(implicit A: AdditiveSemigroup[A]) = new AdditiveProperties( + // base = semigroup(A.additive), + // parents = Nil, + // Rules.serializable(A), + // Rules.repeat1("sumN")(A.sumN), + // Rules.repeat2("sumN", "+")(A.sumN)(A.plus) + // ) + // + // def additiveCommutativeSemigroup(implicit A: AdditiveCommutativeSemigroup[A]) = new AdditiveProperties( + // base = commutativeSemigroup(A.additive), + // parents = List(additiveSemigroup) + // ) + // + // def additiveMonoid(implicit A: AdditiveMonoid[A]) = new AdditiveProperties( + // base = monoid(A.additive), + // parents = List(additiveSemigroup), + // Rules.repeat0("sumN", "zero", A.zero)(A.sumN), + // Rules.collect0("sum", "zero", A.zero)(A.sum) + // ) + // + // def additiveCommutativeMonoid(implicit A: AdditiveCommutativeMonoid[A]) = new AdditiveProperties( + // base = commutativeMonoid(A.additive), + // parents = List(additiveMonoid) + // ) + // + // def additiveGroup(implicit A: AdditiveGroup[A]) = new AdditiveProperties( + // base = group(A.additive), + // parents = List(additiveMonoid), + // Rules.consistentInverse("subtract")(A.minus)(A.plus)(A.negate) + // ) + // + // def additiveCommutativeGroup(implicit A: AdditiveCommutativeGroup[A]) = new AdditiveProperties( + // base = commutativeGroup(A.additive), + // parents = List(additiveGroup) + // ) + + + // property classes + + class GroupProperties( + val name: String, + val parents: Seq[GroupProperties], + val props: (String, Prop)* + ) extends RuleSet { + val bases = Nil + } + + // class AdditiveProperties( + // val base: GroupProperties, + // val parents: Seq[AdditiveProperties], + // val props: (String, Prop)* + // ) extends RuleSet { + // val name = base.name + // val bases = List("base" -> base) + // } + +} diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala b/kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala new file mode 100644 index 0000000000..ac64a97407 --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala @@ -0,0 +1,14 @@ +package cats.kernel.laws + +import catalysts.Platform + +import scala.util.DynamicVariable + +/** + * Object with a dynamic variable that allows users to skip the + * serialization tests for certain instances. + */ +private[laws] object IsSerializable { + val runTests = new DynamicVariable[Boolean](true) + def apply(): Boolean = (!Platform.isJs) && runTests.value +} diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/OrderLaws.scala b/kernel-laws/src/main/scala/cats/kernel/laws/OrderLaws.scala new file mode 100644 index 0000000000..fcb2bde554 --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/OrderLaws.scala @@ -0,0 +1,79 @@ +package cats.kernel +package laws + +import org.typelevel.discipline.Laws + +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop._ + +import cats.kernel.std.boolean._ + +object OrderLaws { + def apply[A: Eq: Arbitrary] = new OrderLaws[A] { + def Equ = Eq[A] + def Arb = implicitly[Arbitrary[A]] + } +} + +trait OrderLaws[A] extends Laws { + + implicit def Equ: Eq[A] + implicit def Arb: Arbitrary[A] + + def eqv = new OrderProperties( + name = "eq", + parent = None, + Rules.serializable(Equ), + "reflexitivity-eq" -> forAll { (x: A) => + x ?== x + }, + "symmetry-eq" -> forAll { (x: A, y: A) => + Equ.eqv(x, y) ?== Equ.eqv(y, x) + }, + "antisymmetry-eq" -> forAll { (x: A, y: A, f: A => A) => + !Equ.eqv(x, y) ?|| Equ.eqv(f(x), f(y)) + }, + "transitivity-eq" -> forAll { (x: A, y: A, z: A) => + !(Equ.eqv(x, y) && Equ.eqv(y, z)) ?|| Equ.eqv(x, z) + } + ) + + def partialOrder(implicit A: PartialOrder[A]) = new OrderProperties( + name = "partialOrder", + parent = Some(eqv), + Rules.serializable(A), + "reflexitivity" -> forAll { (x: A) => + x ?<= x + }, + "antisymmetry" -> forAll { (x: A, y: A) => + !(A.lteqv(x, y) && A.lteqv(y, x)) ?|| A.eqv(x, y) + }, + "transitivity" -> forAll { (x: A, y: A, z: A) => + !(A.lteqv(x, y) && A.lteqv(y, z)) ?|| A.lteqv(x, z) + }, + "gteqv" -> forAll { (x: A, y: A) => + A.lteqv(x, y) ?== A.gteqv(y, x) + }, + "lt" -> forAll { (x: A, y: A) => + A.lt(x, y) ?== (A.lteqv(x, y) && A.neqv(x, y)) + }, + "gt" -> forAll { (x: A, y: A) => + A.lt(x, y) ?== A.gt(y, x) + } + ) + + def order(implicit A: Order[A]) = new OrderProperties( + name = "order", + parent = Some(partialOrder), + "totality" -> forAll { (x: A, y: A) => + A.lteqv(x, y) ?|| A.lteqv(y, x) + } + ) + + class OrderProperties( + name: String, + parent: Option[RuleSet], + props: (String, Prop)* + ) extends DefaultRuleSet(name, parent, props: _*) + +} diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala b/kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala new file mode 100644 index 0000000000..156a39a774 --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala @@ -0,0 +1,128 @@ +package cats.kernel +package laws + +import org.scalacheck.Prop +import org.scalacheck.Prop._ +import scala.util.control.NonFatal + +import org.scalacheck.{Arbitrary, Prop} + +import cats.kernel.std.boolean._ + +object Rules { + + // Comparison operators for testing are supplied by CheckEqOps and + // CheckOrderOps in package.scala. They are: + // + // ?== Ensure that x equals y + // ?!= Ensure that x does not equal y + // ?< Ensure that x < y + // ?<= Ensure that x <= y + // ?> Ensure that x > y + // ?>= Ensure that x >= y + // + // The reason to prefer these operators is that when tests fail, we + // will get more detaild output about what the failing values were + // (in addition to the input values generated by ScalaCheck). + + def associativity[A: Arbitrary: Eq](f: (A, A) => A): (String, Prop) = + "associativity" -> forAll { (x: A, y: A, z: A) => + f(f(x, y), z) ?== f(x, f(y, z)) + } + + def leftIdentity[A: Arbitrary: Eq](id: A)(f: (A, A) => A): (String, Prop) = + "leftIdentity" -> forAll { (x: A) => + f(id, x) ?== x + } + + def rightIdentity[A: Arbitrary: Eq](id: A)(f: (A, A) => A): (String, Prop) = + "rightIdentity" -> forAll { (x: A) => + f(x, id) ?== x + } + + def leftInverse[A: Arbitrary: Eq](id: A)(f: (A, A) => A)(inv: A => A): (String, Prop) = + "left inverse" -> forAll { (x: A) => + id ?== f(inv(x), x) + } + + def rightInverse[A: Arbitrary: Eq](id: A)(f: (A, A) => A)(inv: A => A): (String, Prop) = + "right inverse" -> forAll { (x: A) => + id ?== f(x, inv(x)) + } + + def commutative[A: Arbitrary: Eq](f: (A, A) => A): (String, Prop) = + "commutative" -> forAll { (x: A, y: A) => + f(x, y) ?== f(y, x) + } + + def idempotence[A: Arbitrary: Eq](f: (A, A) => A): (String, Prop) = + "idempotence" -> forAll { (x: A) => + f(x, x) ?== x + } + + def consistentInverse[A: Arbitrary: Eq](name: String)(m: (A, A) => A)(f: (A, A) => A)(inv: A => A): (String, Prop) = + s"consistent $name" -> forAll { (x: A, y: A) => + m(x, y) ?== f(x, inv(y)) + } + + def repeat0[A: Arbitrary: Eq](name: String, sym: String, id: A)(r: (A, Int) => A): (String, Prop) = + s"$name(a, 0) == $sym" -> forAll { (a: A) => + r(a, 0) ?== id + } + + def repeat1[A: Arbitrary: Eq](name: String)(r: (A, Int) => A): (String, Prop) = + s"$name(a, 1) == a" -> forAll { (a: A) => + r(a, 1) ?== a + } + + def repeat2[A: Arbitrary: Eq](name: String, sym: String)(r: (A, Int) => A)(f: (A, A) => A): (String, Prop) = + s"$name(a, 2) == a $sym a" -> forAll { (a: A) => + r(a, 2) ?== f(a, a) + } + + def collect0[A: Arbitrary: Eq](name: String, sym: String, id: A)(c: Seq[A] => A): (String, Prop) = + s"$name(Nil) == $sym" -> forAll { (a: A) => + c(Nil) ?== id + } + + def isId[A: Arbitrary: Eq](name: String, id: A)(p: A => Boolean): (String, Prop) = + name -> forAll { (x: A) => + Eq.eqv(x, id) ?== p(x) + } + + def distributive[A: Arbitrary: Eq](a: (A, A) => A)(m: (A, A) => A): (String, Prop) = + "distributive" → forAll { (x: A, y: A, z: A) => + (m(x, a(y, z)) ?== a(m(x, y), m(x, z))) && + (m(a(x, y), z) ?== a(m(x, z), m(y, z))) + } + + // ugly platform-specific code follows + + def serializable[M](m: M): (String, Prop) = + "serializable" -> (IsSerializable() match { + case false => + Prop(_ => Result(status = Proof)) + case true => + Prop { _ => + import java.io._ + val baos = new ByteArrayOutputStream() + val oos = new ObjectOutputStream(baos) + var ois: ObjectInputStream = null + try { + oos.writeObject(m) + oos.close() + val bais = new ByteArrayInputStream(baos.toByteArray()) + ois = new ObjectInputStream(bais) + val m2 = ois.readObject() // just ensure we can read it back + ois.close() + Result(status = Proof) + } catch { case NonFatal(t) => + Result(status = Exception(t)) + } finally { + oos.close() + if (ois != null) ois.close() + } + } + }) + +} diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/package.scala b/kernel-laws/src/main/scala/cats/kernel/laws/package.scala new file mode 100644 index 0000000000..4e576499df --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/package.scala @@ -0,0 +1,45 @@ +package cats.kernel + +import org.typelevel.discipline.Predicate + +import org.scalacheck._ +import org.scalacheck.util.Pretty +import Prop.{False, Proof, Result} + +package object laws { + + implicit def PredicateFromMonoid[A](implicit ev: Eq[A], A: Monoid[A]): Predicate[A] = + new Predicate[A] { + def apply(a: A) = ev.neqv(a, A.empty) + } + + lazy val proved = Prop(Result(status = Proof)) + + lazy val falsified = Prop(Result(status = False)) + + object Ops { + def run[A](sym: String)(lhs: A, rhs: A)(f: (A, A) => Boolean): Prop = + if (f(lhs, rhs)) proved else falsified :| { + val exp = Pretty.pretty(lhs, Pretty.Params(0)) + val got = Pretty.pretty(rhs, Pretty.Params(0)) + s"($exp $sym $got) failed" + } + } + + implicit class CheckEqOps[A](lhs: A)(implicit ev: Eq[A], pp: A => Pretty) { + def ?==(rhs: A): Prop = Ops.run("?==")(lhs, rhs)(ev.eqv) + def ?!=(rhs: A): Prop = Ops.run("?!=")(lhs, rhs)(ev.neqv) + } + + implicit class CheckOrderOps[A](lhs: A)(implicit ev: PartialOrder[A], pp: A => Pretty) { + def ?<(rhs: A): Prop = Ops.run("?<")(lhs, rhs)(ev.lt) + def ?<=(rhs: A): Prop = Ops.run("?<=")(lhs, rhs)(ev.lteqv) + def ?>(rhs: A): Prop = Ops.run("?>")(lhs, rhs)(ev.gt) + def ?>=(rhs: A): Prop = Ops.run("?>=")(lhs, rhs)(ev.gteqv) + } + + implicit class BooleanOps[A](lhs: Boolean)(implicit pp: Boolean => Pretty) { + def ?&&(rhs: Boolean): Prop = Ops.run("?&&")(lhs, rhs)(_ && _) + def ?||(rhs: Boolean): Prop = Ops.run("?||")(lhs, rhs)(_ || _) + } +} From ceb94cc088138c0c7e927a34ab4cb1a425960fbd Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 11:56:12 -0400 Subject: [PATCH 04/17] Get law tests compiling but not running. This commit ports over the law tests which make sense in the context of cats-kernel. For some reason the tests aren't being run right now, but at least they compile. --- build.sbt | 1 + .../scala/cats/kernel/laws/LawTests.scala | 155 ++++++++++++++++++ .../src/main/scala/cats/kernel/package.scala | 3 - 3 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala delete mode 100644 kernel/src/main/scala/cats/kernel/package.scala diff --git a/build.sbt b/build.sbt index 76c13a1792..51e176f642 100644 --- a/build.sbt +++ b/build.sbt @@ -95,6 +95,7 @@ lazy val disciplineDependencies = Seq( lazy val testingDependencies = Seq( libraryDependencies += "org.typelevel" %%% "catalysts-platform" % "0.0.2", + libraryDependencies += "org.typelevel" %%% "catalysts-macros" % "0.0.2" % "test", libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test") /** diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala new file mode 100644 index 0000000000..2380a4e09f --- /dev/null +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -0,0 +1,155 @@ +package cats.kernel +package laws + +import catalysts.Platform +import catalysts.macros.TypeTagM + +import cats.kernel.std.all._ + +import org.typelevel.discipline.{ Laws } +import org.typelevel.discipline.scalatest.Discipline +import org.scalacheck.{ Arbitrary } +import Arbitrary.arbitrary +import org.scalatest.FunSuite +import scala.util.Random + +trait LawTests extends FunSuite with Discipline { + + implicit def orderLaws[A: Eq: Arbitrary] = OrderLaws[A] + implicit def groupLaws[A: Eq: Arbitrary] = GroupLaws[A] + + laws[OrderLaws, List[HasEq[Int]]].check(_.eqv) + laws[OrderLaws, List[HasEq[String]]].check(_.eqv) + laws[OrderLaws, Option[HasEq[Int]]].check(_.eqv) + laws[OrderLaws, Option[HasEq[String]]].check(_.eqv) + laws[OrderLaws, Map[Char, Int]].check(_.eqv) + laws[OrderLaws, Map[Int, BigInt]].check(_.eqv) + + laws[OrderLaws, Option[HasPartialOrder[Int]]].check(_.partialOrder) + laws[OrderLaws, Option[HasPartialOrder[String]]].check(_.partialOrder) + laws[OrderLaws, List[HasPartialOrder[Int]]].check(_.partialOrder) + laws[OrderLaws, List[HasPartialOrder[String]]].check(_.partialOrder) + laws[OrderLaws, Set[Int]].check(_.partialOrder) + laws[OrderLaws, Array[Int]].check(_.partialOrder) + + laws[OrderLaws, Unit].check(_.order) + laws[OrderLaws, Boolean].check(_.order) + laws[OrderLaws, String].check(_.order) + laws[OrderLaws, Byte].check(_.order) + laws[OrderLaws, Short].check(_.order) + laws[OrderLaws, Char].check(_.order) + laws[OrderLaws, Int].check(_.order) + laws[OrderLaws, Long].check(_.order) + laws[OrderLaws, List[Int]].check(_.order) + laws[OrderLaws, Option[String]].check(_.order) + laws[OrderLaws, List[String]].check(_.order) + laws[OrderLaws, Array[Int]].check(_.order) + laws[OrderLaws, Int]("fromOrdering").check(_.order(Order.fromOrdering[Int])) + + laws[GroupLaws, Int].check(_.monoid) + laws[GroupLaws, String].check(_.monoid) + laws[GroupLaws, Option[Int]].check(_.monoid) + laws[GroupLaws, Option[String]].check(_.monoid) + laws[GroupLaws, List[Int]].check(_.monoid) + laws[GroupLaws, List[String]].check(_.monoid) + + laws[GroupLaws, (Int, Int)].check(_.band) + + implicit val intGroup: Group[Int] = + new Group[Int] { + def empty: Int = 0 + def combine(x: Int, y: Int): Int = x + y + def inverse(x: Int): Int = -x + } + + implicit val band: Band[(Int, Int)] = + new Band[(Int, Int)] { + def combine(a: (Int, Int), b: (Int, Int)) = (a._1, b._2) + } + + { + // In order to check the monoid laws for `Order[N]`, we need + // `Arbitrary[Order[N]]` and `Eq[Order[N]]` instances. + // Here we have a bit of a hack to create these instances. + val nMax: Int = 13 + final case class N(n: Int) { require(n >= 0 && n < nMax) } + // The arbitrary `Order[N]` values are created by mapping N values to random + // integers. + implicit val arbNOrder: Arbitrary[Order[N]] = Arbitrary(arbitrary[Int].map { seed => + val order = new Random(seed).shuffle(Vector.range(0, nMax)) + Order.by { (n: N) => order(n.n) } + }) + // The arbitrary `Eq[N]` values are created by mapping N values to random + // integers. + implicit val arbNEq: Arbitrary[Eq[N]] = Arbitrary(arbitrary[Int].map { seed => + val mapping = new Random(seed).shuffle(Vector.range(0, nMax)) + Eq.by { (n: N) => mapping(n.n) } + }) + // needed because currently we don't have Vector instances + implicit val vectorNEq: Eq[Vector[N]] = Eq.fromUniversalEquals + // The `Eq[Order[N]]` instance enumerates all possible `N` values in a + // `Vector` and considers two `Order[N]` instances to be equal if they + // result in the same sorting of that vector. + implicit val NOrderEq: Eq[Order[N]] = Eq.by { order: Order[N] => + Vector.tabulate(nMax)(N).sorted(order.toOrdering) + } + implicit val NEqEq: Eq[Eq[N]] = new Eq[Eq[N]] { + def eqv(a: Eq[N], b: Eq[N]) = + Iterator.tabulate(nMax)(N) + .flatMap { x => Iterator.tabulate(nMax)(N).map((x, _)) } + .forall { case (x, y) => a.eqv(x, y) == b.eqv(x, y) } + } + + implicit val monoidOrderN: Monoid[Order[N]] = Order.whenEqualMonoid[N] + laws[GroupLaws, Order[N]].check(_.monoid) + + { + implicit val bsEqN: BoundedSemilattice[Eq[N]] = Eq.allEqualBoundedSemilattice[N] + laws[GroupLaws, Eq[N]].check(_.boundedSemilattice) + } + { + implicit val sEqN: Semilattice[Eq[N]] = Eq.anyEqualSemilattice[N] + laws[GroupLaws, Eq[N]].check(_.semilattice) + } + } + + + // esoteric machinery follows... + + + // The scalacheck defaults (100,100) are too high for scala-js. + val PropMaxSize = if (Platform.isJs) 10 else 100 + val PropMinSuccessful = if (Platform.isJs) 10 else 100 + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfig(maxSize = PropMaxSize, minSuccessful = PropMinSuccessful) + + case class HasEq[A](a: A) + + object HasEq { + implicit def hasEq[A: Eq]: Eq[HasEq[A]] = + Eq[A].on(_.a) + implicit def hasEqArbitrary[A: Arbitrary]: Arbitrary[HasEq[A]] = + Arbitrary(arbitrary[A].map(HasEq(_))) + } + + case class HasPartialOrder[A](a: A) + + object HasPartialOrder { + implicit def hasPartialOrder[A: PartialOrder]: PartialOrder[HasPartialOrder[A]] = + PartialOrder[A].on(_.a) + implicit def hasPartialOrderArbitrary[A: Arbitrary]: Arbitrary[HasPartialOrder[A]] = + Arbitrary(arbitrary[A].map(HasPartialOrder(_))) + } + + case class LawChecker[L <: Laws](name: String, laws: L) { + def check(f: L => L#RuleSet): Unit = checkAll(name, f(laws)) + } + + private[laws] def laws[L[_] <: Laws, A](implicit lws: L[A], tag: TypeTagM[A]): LawChecker[L[A]] = + laws[L, A]("") + + private[laws] def laws[L[_] <: Laws, A](extraTag: String)(implicit laws: L[A], tag: TypeTagM[A]): LawChecker[L[A]] = + LawChecker("[" + tag.name.toString + (if(extraTag != "") "@@" + extraTag else "") + "]", laws) + +} diff --git a/kernel/src/main/scala/cats/kernel/package.scala b/kernel/src/main/scala/cats/kernel/package.scala deleted file mode 100644 index 80fd3067b8..0000000000 --- a/kernel/src/main/scala/cats/kernel/package.scala +++ /dev/null @@ -1,3 +0,0 @@ -package cats -package object kernel { -} From 673d0454bf4fbbba369dbf9b6dbdbc95a1195755 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 12:03:50 -0400 Subject: [PATCH 05/17] Get tests running and passing. --- .../scala/cats/kernel/laws/LawTests.scala | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index 2380a4e09f..9064c9b1ef 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -13,7 +13,14 @@ import Arbitrary.arbitrary import org.scalatest.FunSuite import scala.util.Random -trait LawTests extends FunSuite with Discipline { +class LawTests extends FunSuite with Discipline { + + // The scalacheck defaults (100,100) are too high for scala-js. + final val PropMaxSize = if (Platform.isJs) 10 else 100 + final val PropMinSuccessful = if (Platform.isJs) 10 else 100 + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfig(maxSize = PropMaxSize, minSuccessful = PropMinSuccessful) implicit def orderLaws[A: Eq: Arbitrary] = OrderLaws[A] implicit def groupLaws[A: Eq: Arbitrary] = GroupLaws[A] @@ -55,14 +62,16 @@ trait LawTests extends FunSuite with Discipline { laws[GroupLaws, (Int, Int)].check(_.band) - implicit val intGroup: Group[Int] = + // esoteric machinery follows... + + implicit lazy val intGroup: Group[Int] = new Group[Int] { def empty: Int = 0 def combine(x: Int, y: Int): Int = x + y def inverse(x: Int): Int = -x } - implicit val band: Band[(Int, Int)] = + implicit lazy val band: Band[(Int, Int)] = new Band[(Int, Int)] { def combine(a: (Int, Int), b: (Int, Int)) = (a._1, b._2) } @@ -113,17 +122,6 @@ trait LawTests extends FunSuite with Discipline { } } - - // esoteric machinery follows... - - - // The scalacheck defaults (100,100) are too high for scala-js. - val PropMaxSize = if (Platform.isJs) 10 else 100 - val PropMinSuccessful = if (Platform.isJs) 10 else 100 - - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfig(maxSize = PropMaxSize, minSuccessful = PropMinSuccessful) - case class HasEq[A](a: A) object HasEq { From d19a098e9a1c4626bd015859967f0bad4a24ea74 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 12:13:18 -0400 Subject: [PATCH 06/17] Fix tests in JS. --- .../cats/kernel/laws/IsSerializable.scala | 26 ++++++++++++++- .../main/scala/cats/kernel/laws/Rules.scala | 32 +++---------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala b/kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala index ac64a97407..2ed2e92739 100644 --- a/kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala +++ b/kernel-laws/src/main/scala/cats/kernel/laws/IsSerializable.scala @@ -1,7 +1,9 @@ package cats.kernel.laws import catalysts.Platform - +import org.scalacheck.Prop +import org.scalacheck.Prop._ +import scala.util.control.NonFatal import scala.util.DynamicVariable /** @@ -11,4 +13,26 @@ import scala.util.DynamicVariable private[laws] object IsSerializable { val runTests = new DynamicVariable[Boolean](true) def apply(): Boolean = (!Platform.isJs) && runTests.value + + def testSerialization[M](m: M): Prop.Result = + if (Platform.isJs) Result(status = Proof) else { + import java.io._ + val baos = new ByteArrayOutputStream() + val oos = new ObjectOutputStream(baos) + var ois: ObjectInputStream = null + try { + oos.writeObject(m) + oos.close() + val bais = new ByteArrayInputStream(baos.toByteArray()) + ois = new ObjectInputStream(bais) + val m2 = ois.readObject() // just ensure we can read it back + ois.close() + Result(status = Proof) + } catch { case NonFatal(t) => + Result(status = Exception(t)) + } finally { + oos.close() + if (ois != null) ois.close() + } + } } diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala b/kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala index 156a39a774..abeb08d0a5 100644 --- a/kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala +++ b/kernel-laws/src/main/scala/cats/kernel/laws/Rules.scala @@ -3,10 +3,7 @@ package laws import org.scalacheck.Prop import org.scalacheck.Prop._ -import scala.util.control.NonFatal - import org.scalacheck.{Arbitrary, Prop} - import cats.kernel.std.boolean._ object Rules { @@ -99,30 +96,9 @@ object Rules { // ugly platform-specific code follows def serializable[M](m: M): (String, Prop) = - "serializable" -> (IsSerializable() match { - case false => - Prop(_ => Result(status = Proof)) - case true => - Prop { _ => - import java.io._ - val baos = new ByteArrayOutputStream() - val oos = new ObjectOutputStream(baos) - var ois: ObjectInputStream = null - try { - oos.writeObject(m) - oos.close() - val bais = new ByteArrayInputStream(baos.toByteArray()) - ois = new ObjectInputStream(bais) - val m2 = ois.readObject() // just ensure we can read it back - ois.close() - Result(status = Proof) - } catch { case NonFatal(t) => - Result(status = Exception(t)) - } finally { - oos.close() - if (ois != null) ois.close() - } - } + "serializable" -> (if (IsSerializable()) { + Prop(_ => Result(status = Proof)) + } else { + Prop(_ => IsSerializable.testSerialization(m)) }) - } From 3378af34e2a31feb461c3a8f0fca54ad72044004 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 13:15:21 -0400 Subject: [PATCH 07/17] Add missing instances to cats.kernel.std. --- .../src/main/scala/cats/kernel/std/Util.scala | 184 +++++------------- .../src/main/scala/cats/kernel/std/set.scala | 14 +- .../main/scala/cats/kernel/std/stream.scala | 55 ++++++ .../main/scala/cats/kernel/std/vector.scala | 55 ++++++ 4 files changed, 175 insertions(+), 133 deletions(-) create mode 100644 kernel/src/main/scala/cats/kernel/std/stream.scala create mode 100644 kernel/src/main/scala/cats/kernel/std/vector.scala diff --git a/kernel/src/main/scala/cats/kernel/std/Util.scala b/kernel/src/main/scala/cats/kernel/std/Util.scala index 93655d09e0..5b0859e0da 100644 --- a/kernel/src/main/scala/cats/kernel/std/Util.scala +++ b/kernel/src/main/scala/cats/kernel/std/Util.scala @@ -1,139 +1,10 @@ -package cats.kernel.std.util +package cats.kernel +package std.util -import java.lang.Double.{ longBitsToDouble, doubleToLongBits } -import java.lang.Float.{ intBitsToFloat, floatToIntBits } -import java.lang.Long.{ numberOfTrailingZeros, numberOfLeadingZeros } -import java.lang.Math -import scala.annotation.tailrec import scala.collection.mutable object StaticMethods { - /** - * Implementation of the binary GCD algorithm. - */ - final def gcd(x0: Long, y0: Long): Long = { - // if either argument is 0, just return the other. - if (x0 == 0L) return y0 - if (y0 == 0L) return x0 - - val xz = numberOfTrailingZeros(x0) - val yz = numberOfTrailingZeros(y0) - - // Math.abs is safe because Long.MinValue (0x8000000000000000) - // will be shifted over 63 bits (due to trailing zeros). - var x = Math.abs(x0 >> xz) - var y = Math.abs(y0 >> yz) - - while (x != y) { - if (x > y) { - x -= y - x >>= numberOfTrailingZeros(x) - } else { - y -= x - y >>= numberOfTrailingZeros(y) - } - } - - // trailing zeros mean powers of two -- if both numbers had - // trailing zeros, those are part of the common divsor as well. - if (xz < yz) x << xz else x << yz - } - - /** - * GCD for Float values. - */ - final def gcd(a: Float, b: Float): Float = { - import java.lang.Integer.{ numberOfTrailingZeros, numberOfLeadingZeros } - - def value(bits: Int): Int = bits & 0x007FFFFF | 0x00800000 - - def exp(bits: Int): Int = ((bits >> 23) & 0xFF).toInt - - def gcd0(val0: Int, exp0: Int, val1: Int, exp1: Int): Float = { - val tz0 = numberOfTrailingZeros(val0) - val tz1 = numberOfTrailingZeros(val1) - val tzShared = Math.min(tz0, tz1 + exp1 - exp0) - val n = gcd(val0.toLong >>> tz0, val1.toLong >>> tz1).toInt << tzShared - - val shift = numberOfLeadingZeros(n) - 8 // Number of bits to move 1 to bit 23 - val mantissa = (n << shift) & 0x007FFFFF - val exp = (exp0 - shift) - if (exp < 0) 0F else intBitsToFloat((exp << 23) | mantissa) - } - - if (a == 0F) b - else if (b == 0F) a - else { - val aBits = floatToIntBits(a) - val aVal = value(aBits) - val aExp = exp(aBits) - - val bBits = floatToIntBits(b) - val bVal = value(bBits) - val bExp = exp(bBits) - - if (aExp < bExp) gcd0(aVal, aExp, bVal, bExp) - else gcd0(bVal, bExp, aVal, aExp) - } - } - - /** - * GCD for Double values. - */ - final def gcd(a: Double, b: Double): Double = { - def value(bits: Long): Long = bits & 0x000FFFFFFFFFFFFFL | 0x0010000000000000L - - def exp(bits: Long): Int = ((bits >> 52) & 0x7FF).toInt - - def gcd0(val0: Long, exp0: Int, val1: Long, exp1: Int): Double = { - val tz0 = numberOfTrailingZeros(val0) - val tz1 = numberOfTrailingZeros(val1) - val tzShared = Math.min(tz0, tz1 + exp1 - exp0) - val n = gcd(val0 >>> tz0, val1 >>> tz1) << tzShared - - val shift = numberOfLeadingZeros(n) - 11 // Number of bits to move 1 to bit 52 - val mantissa = (n << shift) & 0x000FFFFFFFFFFFFFL - val exp = (exp0 - shift).toLong - if (exp < 0) 0.0 else longBitsToDouble((exp << 52) | mantissa) - } - - if (a == 0D) b - else if (b == 0D) a - else { - val aBits = doubleToLongBits(a) - val aVal = value(aBits) - val aExp = exp(aBits) - - val bBits = doubleToLongBits(b) - val bVal = value(bBits) - val bExp = exp(bBits) - - if (aExp < bExp) gcd0(aVal, aExp, bVal, bExp) - else gcd0(bVal, bExp, aVal, aExp) - } - } - - /** - * Exponentiation function, e.g. x^y - * - * If base^ex doesn't fit in a Long, the result will overflow (unlike - * Math.pow which will return +/- Infinity). - */ - final def pow(base: Long, exponent: Long): Long = { - @tailrec def loop(t: Long, b: Long, e: Long): Long = - if (e == 0L) t - else if ((e & 1) == 1) loop(t * b, b * b, e >>> 1L) - else loop(t, b * b, e >>> 1L) - - if (exponent >= 0L) loop(1L, base, exponent) else { - if(base == 0L) throw new ArithmeticException("zero can't be raised to negative power") - else if (base == 1L) 1L - else if (base == -1L) if ((exponent & 1L) == 0L) -1L else 1L - else 0L - } - } - def initMutableMap[K, V](m: Map[K, V]): mutable.Map[K, V] = { val result = mutable.Map.empty[K, V] m.foreach { case (k, v) => result(k) = v } @@ -178,4 +49,55 @@ object StaticMethods { } wrapMutableMap(m) } + + def iteratorCompare[A](xs: Iterator[A], ys: Iterator[A])(implicit ev: Order[A]): Int = { + while (true) { + if (xs.hasNext) { + if (ys.hasNext) { + val x = xs.next + val y = ys.next + val cmp = ev.compare(x, y) + if (cmp != 0) return cmp + } else { + return 1 + } + } else { + return if (ys.hasNext) -1 else 0 + } + } + 0 + } + + def iteratorPartialCompare[A](xs: Iterator[A], ys: Iterator[A])(implicit ev: PartialOrder[A]): Double = { + while (true) { + if (xs.hasNext) { + if (ys.hasNext) { + val x = xs.next + val y = ys.next + val cmp = ev.partialCompare(x, y) + if (cmp != 0.0) return cmp + } else { + return 1.0 + } + } else { + return if (ys.hasNext) -1.0 else 0.0 + } + } + 0.0 + } + + def iteratorEq[A](xs: Iterator[A], ys: Iterator[A])(implicit ev: Eq[A]): Boolean = { + while (true) { + if (xs.hasNext) { + if (ys.hasNext) { + if (ev.eqv(xs.next, ys.next)) return true + } else { + return false + } + } else { + return !ys.hasNext + } + } + true + } } diff --git a/kernel/src/main/scala/cats/kernel/std/set.scala b/kernel/src/main/scala/cats/kernel/std/set.scala index ab5e178d3c..844fab2fe5 100644 --- a/kernel/src/main/scala/cats/kernel/std/set.scala +++ b/kernel/src/main/scala/cats/kernel/std/set.scala @@ -4,7 +4,11 @@ package std package object set extends SetInstances trait SetInstances { - implicit def setPartialOrder[A]: PartialOrder[Set[A]] = new SetPartialOrder[A] + implicit def setPartialOrder[A]: PartialOrder[Set[A]] = + new SetPartialOrder[A] + + implicit def setMonoid[A]: Monoid[Set[A]] = + new SetMonoid[A] } class SetPartialOrder[A] extends PartialOrder[Set[A]] { @@ -14,5 +18,11 @@ class SetPartialOrder[A] extends PartialOrder[Set[A]] { else if (x == y) 0.0 else Double.NaN - override def eqv(x: Set[A], y: Set[A]): Boolean = x == y + override def eqv(x: Set[A], y: Set[A]): Boolean = + x == y +} + +class SetMonoid[A] extends Monoid[Set[A]] { + def empty: Set[A] = Set.empty + def combine(x: Set[A], y: Set[A]): Set[A] = x | y } diff --git a/kernel/src/main/scala/cats/kernel/std/stream.scala b/kernel/src/main/scala/cats/kernel/std/stream.scala new file mode 100644 index 0000000000..899cc58d72 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/stream.scala @@ -0,0 +1,55 @@ +package cats.kernel +package std + +import cats.kernel.std.util.StaticMethods + +package object stream extends StreamInstances + +trait StreamInstances extends StreamInstances1 { + implicit def streamOrder[A: Order] = new StreamOrder[A] + implicit def streamMonoid[A] = new StreamMonoid[A] +} + +trait StreamInstances1 extends StreamInstances2 { + implicit def streamPartialOrder[A: PartialOrder] = new StreamPartialOrder[A] +} + +trait StreamInstances2 { + implicit def streamEq[A: Eq] = new StreamEq[A] +} + +class StreamOrder[A](implicit ev: Order[A]) extends Order[Stream[A]] { + def compare(xs: Stream[A], ys: Stream[A]): Int = + StaticMethods.iteratorCompare(xs.iterator, ys.iterator) +} + +class StreamPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Stream[A]] { + def partialCompare(xs: Stream[A], ys: Stream[A]): Double = + StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) +} + +class StreamEq[A](implicit ev: Eq[A]) extends Eq[Stream[A]] { + def eqv(xs: Stream[A], ys: Stream[A]): Boolean = + StaticMethods.iteratorEq(xs.iterator, ys.iterator) +} + +class StreamMonoid[A] extends Monoid[Stream[A]] { + def empty: Stream[A] = Stream.empty + def combine(x: Stream[A], y: Stream[A]): Stream[A] = x ++ y + + override def combineN(x: Stream[A], n: Int): Stream[A] = { + val buf = Stream.newBuilder[A] + var i = n + while (i > 0) { + buf ++= x + i -= 1 + } + buf.result + } + + override def combineAll(xs: TraversableOnce[Stream[A]]): Stream[A] = { + val buf = Stream.newBuilder[A] + xs.foreach(buf ++= _) + buf.result + } +} diff --git a/kernel/src/main/scala/cats/kernel/std/vector.scala b/kernel/src/main/scala/cats/kernel/std/vector.scala new file mode 100644 index 0000000000..50c13c5174 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/std/vector.scala @@ -0,0 +1,55 @@ +package cats.kernel +package std + +import cats.kernel.std.util.StaticMethods + +package object vector extends VectorInstances + +trait VectorInstances extends VectorInstances1 { + implicit def vectorOrder[A: Order] = new VectorOrder[A] + implicit def vectorMonoid[A] = new VectorMonoid[A] +} + +trait VectorInstances1 extends VectorInstances2 { + implicit def vectorPartialOrder[A: PartialOrder] = new VectorPartialOrder[A] +} + +trait VectorInstances2 { + implicit def vectorEq[A: Eq] = new VectorEq[A] +} + +class VectorOrder[A](implicit ev: Order[A]) extends Order[Vector[A]] { + def compare(xs: Vector[A], ys: Vector[A]): Int = + StaticMethods.iteratorCompare(xs.iterator, ys.iterator) +} + +class VectorPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Vector[A]] { + def partialCompare(xs: Vector[A], ys: Vector[A]): Double = + StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) +} + +class VectorEq[A](implicit ev: Eq[A]) extends Eq[Vector[A]] { + def eqv(xs: Vector[A], ys: Vector[A]): Boolean = + StaticMethods.iteratorEq(xs.iterator, ys.iterator) +} + +class VectorMonoid[A] extends Monoid[Vector[A]] { + def empty: Vector[A] = Vector.empty + def combine(x: Vector[A], y: Vector[A]): Vector[A] = x ++ y + + override def combineN(x: Vector[A], n: Int): Vector[A] = { + val buf = Vector.newBuilder[A] + var i = n + while (i > 0) { + buf ++= x + i -= 1 + } + buf.result + } + + override def combineAll(xs: TraversableOnce[Vector[A]]): Vector[A] = { + val buf = Vector.newBuilder[A] + xs.foreach(buf ++= _) + buf.result + } +} From 34e206af4b061730587095399ee2d3f0e8e0ed97 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 13:15:37 -0400 Subject: [PATCH 08/17] Remove algebra. After this commit, the intention is that none of the algebra types are used. The tests pass and things seem to work. Next step is to actually remove the algebra dependency. --- .../src/main/scala/cats/bench/FoldBench.scala | 2 +- build.sbt | 4 +- core/src/main/scala/cats/package.scala | 24 ++--- core/src/main/scala/cats/std/anyval.scala | 89 +++++++------------ core/src/main/scala/cats/std/bigInt.scala | 2 +- core/src/main/scala/cats/std/function.scala | 2 +- core/src/main/scala/cats/std/list.scala | 53 +---------- core/src/main/scala/cats/std/map.scala | 18 +--- core/src/main/scala/cats/std/option.scala | 46 +--------- core/src/main/scala/cats/std/set.scala | 5 +- core/src/main/scala/cats/std/stream.scala | 23 +---- core/src/main/scala/cats/std/string.scala | 2 +- core/src/main/scala/cats/std/vector.scala | 16 +--- .../cats/laws/discipline/CartesianTests.scala | 2 +- .../main/scala/cats/laws/discipline/Eq.scala | 5 +- .../scala/cats/laws/discipline/package.scala | 2 +- .../test/scala/cats/tests/CategoryTests.scala | 2 +- .../test/scala/cats/tests/ComposeTest.scala | 2 +- .../test/scala/cats/tests/ConstTests.scala | 2 +- .../scala/cats/tests/CoproductTests.scala | 3 +- .../test/scala/cats/tests/EitherTests.scala | 2 +- .../src/test/scala/cats/tests/EvalTests.scala | 2 +- .../src/test/scala/cats/tests/FreeTests.scala | 1 + .../test/scala/cats/tests/FunctionTests.scala | 2 +- .../test/scala/cats/tests/KleisliTests.scala | 2 +- .../test/scala/cats/tests/OneAndTests.scala | 2 +- .../src/test/scala/cats/tests/SetTests.scala | 2 +- .../scala/cats/tests/ValidatedTests.scala | 2 +- .../test/scala/cats/tests/WriterTTests.scala | 2 +- .../src/test/scala/cats/tests/XorTTests.scala | 2 +- .../src/test/scala/cats/tests/XorTests.scala | 2 +- 31 files changed, 77 insertions(+), 248 deletions(-) diff --git a/bench/src/main/scala/cats/bench/FoldBench.scala b/bench/src/main/scala/cats/bench/FoldBench.scala index d6388e868e..8f1d43bdc8 100644 --- a/bench/src/main/scala/cats/bench/FoldBench.scala +++ b/bench/src/main/scala/cats/bench/FoldBench.scala @@ -1,7 +1,7 @@ package cats.bench -import algebra.std.string._ import cats.data.Const +import cats.std.string._ import cats.std.list._ import cats.{Foldable, Traverse} import org.openjdk.jmh.annotations.{Benchmark, Scope, State} diff --git a/build.sbt b/build.sbt index 51e176f642..0a6820fe72 100644 --- a/build.sbt +++ b/build.sbt @@ -204,7 +204,7 @@ lazy val kernelLawsJVM = kernelLaws.jvm lazy val kernelLawsJS = kernelLaws.js lazy val core = crossProject.crossType(CrossType.Pure) - .dependsOn(macros) + .dependsOn(macros, kernel) .settings(moduleName := "cats-core") .settings(catsSettings:_*) .settings(sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen)) @@ -216,7 +216,7 @@ lazy val coreJVM = core.jvm lazy val coreJS = core.js lazy val laws = crossProject.crossType(CrossType.Pure) - .dependsOn(macros, core) + .dependsOn(macros, kernel, core, kernelLaws) .settings(moduleName := "cats-laws") .settings(catsSettings:_*) .settings(disciplineDependencies:_*) diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 393ecc7004..d1e7ad5730 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -45,17 +45,17 @@ package object cats { f(a) } - type Eq[A] = algebra.Eq[A] - type PartialOrder[A] = algebra.PartialOrder[A] - type Order[A] = algebra.Order[A] - type Semigroup[A] = algebra.Semigroup[A] - type Monoid[A] = algebra.Monoid[A] - type Group[A] = algebra.Group[A] + type Eq[A] = cats.kernel.Eq[A] + type PartialOrder[A] = cats.kernel.PartialOrder[A] + type Order[A] = cats.kernel.Order[A] + type Semigroup[A] = cats.kernel.Semigroup[A] + type Monoid[A] = cats.kernel.Monoid[A] + type Group[A] = cats.kernel.Group[A] - val Eq = algebra.Eq - val PartialOrder = algebra.PartialOrder - val Order = algebra.Order - val Semigroup = algebra.Semigroup - val Monoid = algebra.Monoid - val Group = algebra.Group + val Eq = cats.kernel.Eq + val PartialOrder = cats.kernel.PartialOrder + val Order = cats.kernel.Order + val Semigroup = cats.kernel.Semigroup + val Monoid = cats.kernel.Monoid + val Group = cats.kernel.Group } diff --git a/core/src/main/scala/cats/std/anyval.scala b/core/src/main/scala/cats/std/anyval.scala index 7964aa18e9..41110f5377 100644 --- a/core/src/main/scala/cats/std/anyval.scala +++ b/core/src/main/scala/cats/std/anyval.scala @@ -1,8 +1,7 @@ package cats package std -import algebra.CommutativeGroup -import algebra.ring.AdditiveCommutativeGroup +import cats.kernel.CommutativeGroup trait AnyValInstances extends IntInstances @@ -15,129 +14,103 @@ trait AnyValInstances with BooleanInstances with UnitInstances -trait IntInstances extends algebra.std.IntInstances { +trait IntInstances extends cats.kernel.std.IntInstances { implicit val intShow: Show[Int] = Show.fromToString[Int] implicit val intGroup: CommutativeGroup[Int] = - AdditiveCommutativeGroup[Int].additive - + new CommutativeGroup[Int] { + def combine(x: Int, y: Int): Int = x + y + def empty: Int = 0 + def inverse(x: Int): Int = -x + override def remove(x: Int, y: Int): Int = x - y + } } -trait ByteInstances /* missing algebra type classes */ { +trait ByteInstances extends cats.kernel.std.ByteInstances { implicit val byteShow: Show[Byte] = Show.fromToString[Byte] - // TODO: replace this minimal algebra with one from the algebra project - implicit val byteAlgebra: CommutativeGroup[Byte] with Order[Byte] = - new CommutativeGroup[Byte] with Order[Byte] { + implicit val byteGroup: CommutativeGroup[Byte] = + new CommutativeGroup[Byte] { def combine(x: Byte, y: Byte): Byte = (x + y).toByte def empty: Byte = 0 def inverse(x: Byte): Byte = (-x).toByte - def compare(x: Byte, y: Byte): Int = - if (x < y) -1 else if (y < x) 1 else 0 + override def remove(x: Byte, y: Byte): Byte = (x - y).toByte } } -trait CharInstances /* missing algebra type classes */ { - +trait CharInstances extends cats.kernel.std.CharInstances { implicit val charShow: Show[Char] = Show.fromToString[Char] - - implicit val charOrder: Order[Char] = - new Order[Char] { - def compare(x: Char, y: Char): Int = - if (x < y) -1 else if (y < x) 1 else 0 - } } -trait ShortInstances /* missing algebra type classes */ { +trait ShortInstances extends cats.kernel.std.ShortInstances { implicit val shortShow: Show[Short] = Show.fromToString[Short] - // TODO: replace this minimal algebra with one from the algebra project - implicit val shortAlgebra: CommutativeGroup[Short] with Order[Short] = - new CommutativeGroup[Short] with Order[Short] { + implicit val shortGroup: CommutativeGroup[Short] = + new CommutativeGroup[Short] { def combine(x: Short, y: Short): Short = (x + y).toShort def empty: Short = 0 def inverse(x: Short): Short = (-x).toShort - def compare(x: Short, y: Short): Int = - if (x < y) -1 else if (y < x) 1 else 0 + override def remove(x: Short, y: Short): Short = (x - y).toShort } - } -trait LongInstances /* missing algebra type classes */ { +trait LongInstances extends cats.kernel.std.LongInstances { implicit val longShow: Show[Long] = Show.fromToString[Long] - // TODO: replace this minimal algebra with one from the algebra project - implicit val longAlgebra: CommutativeGroup[Long] with Order[Long] = - new CommutativeGroup[Long] with Order[Long] { + implicit val longGroup: CommutativeGroup[Long] = + new CommutativeGroup[Long] { def combine(x: Long, y: Long): Long = x + y def empty: Long = 0L def inverse(x: Long): Long = -x - def compare(x: Long, y: Long): Int = - if (x < y) -1 else if (y < x) 1 else 0 + override def remove(x: Long, y: Long): Long = x - y } } -trait FloatInstances /* missing algebra type classes */ { +trait FloatInstances extends cats.kernel.std.FloatInstances { implicit val floatShow: Show[Float] = Show.fromToString[Float] - // TODO: replace this minimal algebra with one from the algebra project - implicit val floatAlgebra: CommutativeGroup[Float] with Order[Float] = - new CommutativeGroup[Float] with Order[Float] { + implicit val floatGroup: CommutativeGroup[Float] = + new CommutativeGroup[Float] { def combine(x: Float, y: Float): Float = x + y def empty: Float = 0F def inverse(x: Float): Float = -x - def compare(x: Float, y: Float): Int = - java.lang.Float.compare(x, y) + override def remove(x: Float, y: Float): Float = x - y } - } -trait DoubleInstances /* missing algebra type classes */ { +trait DoubleInstances extends cats.kernel.std.DoubleInstances { implicit val doubleShow: Show[Double] = Show.fromToString[Double] - // TODO: replace this minimal algebra with one from the algebra project - implicit val doubleAlgebra: CommutativeGroup[Double] with Order[Double] = - new CommutativeGroup[Double] with Order[Double] { + implicit val doubleGroup: CommutativeGroup[Double] = + new CommutativeGroup[Double] { def combine(x: Double, y: Double): Double = x + y def empty: Double = 0D def inverse(x: Double): Double = -x - def compare(x: Double, y: Double): Int = - java.lang.Double.compare(x, y) + override def remove(x: Double, y: Double): Double = x - y } - } -trait BooleanInstances extends algebra.std.BooleanInstances { +trait BooleanInstances extends cats.kernel.std.BooleanInstances { implicit val booleanShow: Show[Boolean] = Show.fromToString[Boolean] - } -trait UnitInstances /* missing algebra type classes */ { +trait UnitInstances extends cats.kernel.std.UnitInstances { implicit val unitShow: Show[Unit] = Show.fromToString[Unit] - - implicit val unitAlgebra: CommutativeGroup[Unit] with Order[Unit] = - new CommutativeGroup[Unit] with Order[Unit] { - def combine(x: Unit, y: Unit): Unit = () - def empty: Unit = () - def inverse(x: Unit): Unit = () - def compare(x: Unit, y: Unit): Int = 0 - } - } diff --git a/core/src/main/scala/cats/std/bigInt.scala b/core/src/main/scala/cats/std/bigInt.scala index d075af3826..0c663f734e 100644 --- a/core/src/main/scala/cats/std/bigInt.scala +++ b/core/src/main/scala/cats/std/bigInt.scala @@ -1,7 +1,7 @@ package cats package std -trait BigIntInstances extends algebra.std.BigIntInstances { +trait BigIntInstances extends cats.kernel.std.BigIntInstances { implicit val bigIntShow: Show[BigInt] = Show.fromToString[BigInt] } diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 0215fda5b9..f19d20a816 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -1,12 +1,12 @@ package cats package std -import algebra.Eq import cats.arrow.{Arrow, Choice} import cats.data.Xor import cats.functor.Contravariant private[std] sealed trait Function0Instances { + implicit val function0Instance: Bimonad[Function0] = new Bimonad[Function0] { def extract[A](x: () => A): A = x() diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index c79a3d006b..372e91ab5d 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -1,16 +1,13 @@ package cats package std -import algebra.Eq -import algebra.std.{ListMonoid, ListOrder} - -import cats.syntax.order._ import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.mutable.ListBuffer -trait ListInstances extends ListInstances1 { +trait ListInstances extends cats.kernel.std.ListInstances { + implicit val listInstance: Traverse[List] with MonadCombine[List] with CoflatMap[List] = new Traverse[List] with MonadCombine[List] with CoflatMap[List] { @@ -65,54 +62,8 @@ trait ListInstances extends ListInstances1 { override def isEmpty[A](fa: List[A]): Boolean = fa.isEmpty } - implicit def listAlgebra[A]: Monoid[List[A]] = new ListMonoid[A] - implicit def listOrder[A: Order]: Order[List[A]] = new ListOrder[A] - implicit def listShow[A:Show]: Show[List[A]] = new Show[List[A]] { def show(fa: List[A]): String = fa.map(_.show).mkString("List(", ", ", ")") } } - -private[std] sealed trait ListInstances1 extends ListInstances2 { - implicit def partialOrderList[A: PartialOrder]: PartialOrder[List[A]] = - new PartialOrder[List[A]] { - def partialCompare(x: List[A], y: List[A]): Double = { - def loop(xs: List[A], ys: List[A]): Double = - xs match { - case a :: xs => - ys match { - case b :: ys => - val n = a partialCompare b - if (n != 0.0) n else loop(xs, ys) - case Nil => - 1.0 - } - case Nil => - if (ys.isEmpty) 0.0 else -1.0 - } - loop(x, y) - } - } -} - -private[std] sealed trait ListInstances2 { - implicit def eqList[A: Eq]: Eq[List[A]] = - new Eq[List[A]] { - def eqv(x: List[A], y: List[A]): Boolean = { - def loop(xs: List[A], ys: List[A]): Boolean = - xs match { - case a :: xs => - ys match { - case b :: ys => - if (a =!= b) false else loop(xs, ys) - case Nil => - false - } - case Nil => - ys.isEmpty - } - loop(x, y) - } - } -} diff --git a/core/src/main/scala/cats/std/map.scala b/core/src/main/scala/cats/std/map.scala index ee75ae27ee..9d4dd43b95 100644 --- a/core/src/main/scala/cats/std/map.scala +++ b/core/src/main/scala/cats/std/map.scala @@ -1,23 +1,7 @@ package cats package std -import cats.syntax.eq._ - -trait MapInstances extends algebra.std.MapInstances { - - implicit def MapEq[A, B: Eq]: Eq[Map[A, B]] = - new Eq[Map[A, B]] { - def eqv(lhs: Map[A, B], rhs: Map[A, B]): Boolean = { - def checkKeys: Boolean = - lhs.forall { case (k, v1) => - rhs.get(k) match { - case Some(v2) => v1 === v2 - case None => false - } - } - (lhs eq rhs) || (lhs.size == rhs.size && checkKeys) - } - } +trait MapInstances extends cats.kernel.std.MapInstances { implicit def MapShow[A, B](implicit showA: Show[A], showB: Show[B]): Show[Map[A, B]] = Show.show[Map[A, B]] { m => diff --git a/core/src/main/scala/cats/std/option.scala b/core/src/main/scala/cats/std/option.scala index 05df794fe2..d70e978ea4 100644 --- a/core/src/main/scala/cats/std/option.scala +++ b/core/src/main/scala/cats/std/option.scala @@ -1,9 +1,8 @@ package cats package std -import algebra.Eq +trait OptionInstances extends cats.kernel.std.OptionInstances { -trait OptionInstances extends OptionInstances1 { implicit val optionInstance: Traverse[Option] with MonadCombine[Option] with CoflatMap[Option] with Alternative[Option] = new Traverse[Option] with MonadCombine[Option] with CoflatMap[Option] with Alternative[Option] { @@ -53,33 +52,6 @@ trait OptionInstances extends OptionInstances1 { fa.isEmpty } - implicit def optionMonoid[A](implicit ev: Semigroup[A]): Monoid[Option[A]] = - new Monoid[Option[A]] { - def empty: Option[A] = None - def combine(x: Option[A], y: Option[A]): Option[A] = - x match { - case None => y - case Some(xx) => y match { - case None => x - case Some(yy) => Some(ev.combine(xx,yy)) - } - } - } - - implicit def orderOption[A](implicit ev: Order[A]): Order[Option[A]] = - new Order[Option[A]] { - def compare(x: Option[A], y: Option[A]): Int = - x match { - case Some(a) => - y match { - case Some(b) => ev.compare(a, b) - case None => 1 - } - case None => - if (y.isDefined) -1 else 0 - } - } - implicit def showOption[A](implicit A: Show[A]): Show[Option[A]] = new Show[Option[A]] { def show(fa: Option[A]): String = fa match { @@ -88,19 +60,3 @@ trait OptionInstances extends OptionInstances1 { } } } - -private[std] sealed trait OptionInstances1 extends OptionInstances2 { - implicit def partialOrderOption[A](implicit ev: PartialOrder[A]): PartialOrder[Option[A]] = - new PartialOrder[Option[A]] { - def partialCompare(x: Option[A], y: Option[A]): Double = - x.fold(if (y.isDefined) -1.0 else 0.0)(a => y.fold(1.0)(ev.partialCompare(_, a))) - } -} - -private[std] sealed trait OptionInstances2 { - implicit def eqOption[A](implicit ev: Eq[A]): Eq[Option[A]] = - new Eq[Option[A]] { - def eqv(x: Option[A], y: Option[A]): Boolean = - x.fold(y == None)(a => y.fold(false)(ev.eqv(_, a))) - } -} diff --git a/core/src/main/scala/cats/std/set.scala b/core/src/main/scala/cats/std/set.scala index a4ce9f0bbf..7ac35ef22e 100644 --- a/core/src/main/scala/cats/std/set.scala +++ b/core/src/main/scala/cats/std/set.scala @@ -3,7 +3,8 @@ package std import cats.syntax.show._ -trait SetInstances extends algebra.std.SetInstances { +trait SetInstances extends cats.kernel.std.SetInstances { + implicit val setInstance: Foldable[Set] with MonoidK[Set] = new Foldable[Set] with MonoidK[Set] { @@ -26,8 +27,6 @@ trait SetInstances extends algebra.std.SetInstances { override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty } - implicit def setMonoid[A]: Monoid[Set[A]] = MonoidK[Set].algebra[A] - implicit def setShow[A:Show]: Show[Set[A]] = new Show[Set[A]] { def show(fa: Set[A]): String = fa.toIterator.map(_.show).mkString("Set(", ", ", ")") diff --git a/core/src/main/scala/cats/std/stream.scala b/core/src/main/scala/cats/std/stream.scala index 9406bcec96..d865c37555 100644 --- a/core/src/main/scala/cats/std/stream.scala +++ b/core/src/main/scala/cats/std/stream.scala @@ -1,10 +1,9 @@ package cats package std -import scala.collection.immutable.Stream.Empty import cats.syntax.show._ -trait StreamInstances { +trait StreamInstances extends cats.kernel.std.StreamInstances { implicit val streamInstance: Traverse[Stream] with MonadCombine[Stream] with CoflatMap[Stream] = new Traverse[Stream] with MonadCombine[Stream] with CoflatMap[Stream] { @@ -63,24 +62,4 @@ trait StreamInstances { new Show[Stream[A]] { def show(fa: Stream[A]): String = if(fa.isEmpty) "Stream()" else s"Stream(${fa.head.show}, ?)" } - - // TODO: eventually use algebra's instances (which will deal with - // implicit priority between Eq/PartialOrder/Order). - - implicit def eqStream[A](implicit ev: Eq[A]): Eq[Stream[A]] = - new Eq[Stream[A]] { - def eqv(x: Stream[A], y: Stream[A]): Boolean = { - def loop(xs: Stream[A], ys: Stream[A]): Boolean = - xs match { - case Empty => ys.isEmpty - case a #:: xs => - ys match { - case Empty => false - case b #:: ys => if (ev.neqv(a, b)) false else loop(xs, ys) - } - } - loop(x, y) - } - } - } diff --git a/core/src/main/scala/cats/std/string.scala b/core/src/main/scala/cats/std/string.scala index a79a814734..ca96850460 100644 --- a/core/src/main/scala/cats/std/string.scala +++ b/core/src/main/scala/cats/std/string.scala @@ -1,7 +1,7 @@ package cats package std -trait StringInstances extends algebra.std.StringInstances { +trait StringInstances extends cats.kernel.std.StringInstances { implicit val stringShow: Show[String] = Show.fromToString[String] } diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 4df0044bdb..04d1b71133 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -7,7 +7,7 @@ import scala.annotation.tailrec import scala.collection.+: import scala.collection.immutable.VectorBuilder -trait VectorInstances { +trait VectorInstances extends cats.kernel.std.VectorInstances { implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] = new Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] { @@ -57,18 +57,4 @@ trait VectorInstances { new Show[Vector[A]] { def show(fa: Vector[A]): String = fa.map(_.show).mkString("Vector(", ", ", ")") } - - // TODO: eventually use algebra's instances (which will deal with - // implicit priority between Eq/PartialOrder/Order). - - implicit def eqVector[A](implicit ev: Eq[A]): Eq[Vector[A]] = - new Eq[Vector[A]] { - def eqv(x: Vector[A], y: Vector[A]): Boolean = { - @tailrec def loop(to: Int): Boolean = - if(to == -1) true - else ev.eqv(x(to), y(to)) && loop(to - 1) - - (x.size == y.size) && loop(x.size - 1) - } - } } diff --git a/laws/src/main/scala/cats/laws/discipline/CartesianTests.scala b/laws/src/main/scala/cats/laws/discipline/CartesianTests.scala index 0f83256b41..989baeb7eb 100644 --- a/laws/src/main/scala/cats/laws/discipline/CartesianTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CartesianTests.scala @@ -36,7 +36,7 @@ object CartesianTests { } object Isomorphisms { - import algebra.laws._ + import cats.kernel.laws._ implicit def invariant[F[_]](implicit F: functor.Invariant[F]): Isomorphisms[F] = new Isomorphisms[F] { def associativity[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) = diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala index 730822231d..a7261833ef 100644 --- a/laws/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/src/main/scala/cats/laws/discipline/Eq.scala @@ -3,7 +3,6 @@ package laws package discipline import catalysts.Platform -import algebra.Eq import cats.std.string._ import org.scalacheck.Arbitrary @@ -26,10 +25,12 @@ object eq { } /** Create an approximation of Eq[Show[A]] by using function1Eq[A, String] */ - implicit def showEq[A: Arbitrary]: Eq[Show[A]] = + implicit def showEq[A: Arbitrary]: Eq[Show[A]] = { + val xyz = function1Eq[A, String] Eq.by[Show[A], A => String] { showInstance => (a: A) => showInstance.show(a) } + } // Temporary, see https://github.com/non/algebra/pull/82 implicit def tuple2Eq[A, B](implicit A: Eq[A], B: Eq[B]): Eq[(A, B)] = diff --git a/laws/src/main/scala/cats/laws/discipline/package.scala b/laws/src/main/scala/cats/laws/discipline/package.scala index a015ad9bcd..6862b3036c 100644 --- a/laws/src/main/scala/cats/laws/discipline/package.scala +++ b/laws/src/main/scala/cats/laws/discipline/package.scala @@ -1,7 +1,7 @@ package cats package laws -import algebra.laws._ +import cats.kernel.laws._ import org.scalacheck.Prop diff --git a/tests/src/test/scala/cats/tests/CategoryTests.scala b/tests/src/test/scala/cats/tests/CategoryTests.scala index b93cc732d6..dc0de32190 100644 --- a/tests/src/test/scala/cats/tests/CategoryTests.scala +++ b/tests/src/test/scala/cats/tests/CategoryTests.scala @@ -1,7 +1,7 @@ package cats package tests -import algebra.laws.GroupLaws +import cats.kernel.laws.GroupLaws import cats.arrow.Category import cats.laws.discipline.{MonoidKTests, SerializableTests} diff --git a/tests/src/test/scala/cats/tests/ComposeTest.scala b/tests/src/test/scala/cats/tests/ComposeTest.scala index 74019444bb..d6694e7caf 100644 --- a/tests/src/test/scala/cats/tests/ComposeTest.scala +++ b/tests/src/test/scala/cats/tests/ComposeTest.scala @@ -1,7 +1,7 @@ package cats package tests -import algebra.laws.GroupLaws +import cats.kernel.laws.GroupLaws import cats.arrow.Compose import cats.laws.discipline.{SemigroupKTests, SerializableTests} diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 20023b2735..1b487575ea 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -1,7 +1,7 @@ package cats package tests -import algebra.laws.{GroupLaws, OrderLaws} +import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.data.{Const, NonEmptyList} import cats.functor.Contravariant diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index afdc58a221..6c0279efd2 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -1,8 +1,7 @@ package cats.tests -import algebra.Eq -import algebra.laws.OrderLaws import cats._ +import cats.kernel.laws.OrderLaws import cats.data.Coproduct import cats.functor.Contravariant import cats.laws.discipline._ diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index a104d21cbd..a5ef74d18c 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -3,7 +3,7 @@ package tests import cats.laws.discipline.{BitraverseTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq._ -import algebra.laws.OrderLaws +import cats.kernel.laws.OrderLaws class EitherTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index 46c490898d..ba3ed945ad 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -6,7 +6,7 @@ import cats.laws.ComonadLaws import cats.laws.discipline.{CartesianTests, BimonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ -import algebra.laws.{GroupLaws, OrderLaws} +import cats.kernel.laws.{GroupLaws, OrderLaws} class EvalTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/FreeTests.scala b/tests/src/test/scala/cats/tests/FreeTests.scala index 56fe308ac2..ad84dc6578 100644 --- a/tests/src/test/scala/cats/tests/FreeTests.scala +++ b/tests/src/test/scala/cats/tests/FreeTests.scala @@ -6,6 +6,7 @@ import cats.free.{Free, Trampoline} import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary.function0Arbitrary + import org.scalacheck.{Arbitrary, Gen} import Arbitrary.arbFunction1 diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 7daf31c445..d7ca5cf729 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -6,7 +6,7 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ -import algebra.laws.GroupLaws +import cats.kernel.laws.GroupLaws class FunctionTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 959bba9776..27350621db 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -8,7 +8,7 @@ import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary -import algebra.laws.GroupLaws +import cats.kernel.laws.GroupLaws import cats.laws.discipline.{SemigroupKTests, MonoidKTests} class KleisliTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index ad711ea410..96dec80fbb 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -1,7 +1,7 @@ package cats package tests -import algebra.laws.{GroupLaws, OrderLaws} +import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.data.{NonEmptyList, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests} diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala index 571b238b0b..e4716df529 100644 --- a/tests/src/test/scala/cats/tests/SetTests.scala +++ b/tests/src/test/scala/cats/tests/SetTests.scala @@ -4,7 +4,7 @@ package tests import cats.laws.discipline.{FoldableTests, MonoidKTests, SerializableTests} class SetTests extends CatsSuite { - checkAll("Set[Int]", algebra.laws.GroupLaws[Set[Int]].monoid) + checkAll("Set[Int]", cats.kernel.laws.GroupLaws[Set[Int]].monoid) checkAll("Set[Int]", MonoidKTests[Set].monoidK[Int]) checkAll("MonoidK[Set]", SerializableTests.serializable(MonoidK[Set])) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index e755b1719d..c629cbdca4 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -7,7 +7,7 @@ import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeErrorTest import org.scalacheck.Arbitrary._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq.tuple3Eq -import algebra.laws.{OrderLaws, GroupLaws} +import cats.kernel.laws.{OrderLaws, GroupLaws} import scala.util.Try diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index cd0cc07312..ecc3846a02 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -7,7 +7,7 @@ import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ -import algebra.laws.OrderLaws +import cats.kernel.laws.OrderLaws class WriterTTests extends CatsSuite { type Logged[A] = Writer[ListWrapper[Int], A] diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 7ea4e9b343..a9f2105045 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -6,7 +6,7 @@ import cats.data.{Xor, XorT} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq.tuple3Eq -import algebra.laws.OrderLaws +import cats.kernel.laws.OrderLaws class XorTTests extends CatsSuite { implicit val eq0 = XorT.xorTEq[List, String, String Xor Int] diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 58d0a90de2..cffedddd37 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -6,7 +6,7 @@ import cats.data.Xor._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{BitraverseTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq.tuple3Eq -import algebra.laws.{GroupLaws, OrderLaws} +import cats.kernel.laws.{GroupLaws, OrderLaws} import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary._ From 041a06a876a307c813c7a7adc032ccde983bacbd Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 13:23:36 -0400 Subject: [PATCH 09/17] Remove the algebra dependency. After clean, validate passed. I think this branch is good to go. --- build.sbt | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.sbt b/build.sbt index 0a6820fe72..fbdd6dc3e9 100644 --- a/build.sbt +++ b/build.sbt @@ -44,8 +44,6 @@ lazy val commonSettings = Seq( ), libraryDependencies ++= Seq( "com.github.mpilquist" %%% "simulacrum" % "0.7.0", - "org.spire-math" %%% "algebra" % "0.3.1", - "org.spire-math" %%% "algebra-std" % "0.3.1", "org.typelevel" %%% "machinist" % "0.4.1", compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") From f8e3910b9a4d34e867babfaafc027a53279e6c91 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 15:00:55 -0400 Subject: [PATCH 10/17] Work around 2.10 bug with Unit specialization. --- build.sbt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index fbdd6dc3e9..6189512395 100644 --- a/build.sbt +++ b/build.sbt @@ -25,12 +25,11 @@ lazy val catsDoctestSettings = Seq( ) ++ doctestSettings lazy val kernelSettings = Seq( - scalacOptions ++= commonScalacOptions, + scalacOptions ++= commonScalacOptions.filter(_ != "-Ywarn-value-discard"), resolvers ++= Seq( "bintray/non" at "http://dl.bintray.com/non/maven", Resolver.sonatypeRepo("releases"), - Resolver.sonatypeRepo("snapshots") - ), + Resolver.sonatypeRepo("snapshots")), parallelExecution in Test := false, scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings") ) ++ warnUnusedImport From e6bd8fec9a6118c94058a7756a1d5c319dff86f5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 15:33:40 -0400 Subject: [PATCH 11/17] Respond to Oscar's review comments. --- core/src/main/scala/cats/std/anyval.scala | 85 ++----------------- .../scala/cats/kernel/laws/GroupLaws.scala | 49 ----------- .../main/scala/cats/kernel/laws/package.scala | 2 +- kernel/src/main/scala/cats/kernel/Group.scala | 15 +++- kernel/src/main/scala/cats/kernel/Order.scala | 23 ++--- .../main/scala/cats/kernel/std/bigInt.scala | 9 ++ .../main/scala/cats/kernel/std/boolean.scala | 2 +- .../src/main/scala/cats/kernel/std/byte.scala | 8 ++ .../main/scala/cats/kernel/std/double.scala | 8 ++ .../main/scala/cats/kernel/std/float.scala | 13 ++- .../src/main/scala/cats/kernel/std/int.scala | 8 ++ .../src/main/scala/cats/kernel/std/long.scala | 10 ++- .../src/main/scala/cats/kernel/std/set.scala | 6 +- .../main/scala/cats/kernel/std/short.scala | 8 ++ .../src/main/scala/cats/kernel/std/unit.scala | 11 ++- 15 files changed, 111 insertions(+), 146 deletions(-) diff --git a/core/src/main/scala/cats/std/anyval.scala b/core/src/main/scala/cats/std/anyval.scala index 41110f5377..708e5ee021 100644 --- a/core/src/main/scala/cats/std/anyval.scala +++ b/core/src/main/scala/cats/std/anyval.scala @@ -1,8 +1,6 @@ package cats package std -import cats.kernel.CommutativeGroup - trait AnyValInstances extends IntInstances with ByteInstances @@ -15,102 +13,37 @@ trait AnyValInstances with UnitInstances trait IntInstances extends cats.kernel.std.IntInstances { - - implicit val intShow: Show[Int] = - Show.fromToString[Int] - - implicit val intGroup: CommutativeGroup[Int] = - new CommutativeGroup[Int] { - def combine(x: Int, y: Int): Int = x + y - def empty: Int = 0 - def inverse(x: Int): Int = -x - override def remove(x: Int, y: Int): Int = x - y - } + implicit val intShow: Show[Int] = Show.fromToString[Int] } trait ByteInstances extends cats.kernel.std.ByteInstances { - - implicit val byteShow: Show[Byte] = - Show.fromToString[Byte] - - implicit val byteGroup: CommutativeGroup[Byte] = - new CommutativeGroup[Byte] { - def combine(x: Byte, y: Byte): Byte = (x + y).toByte - def empty: Byte = 0 - def inverse(x: Byte): Byte = (-x).toByte - override def remove(x: Byte, y: Byte): Byte = (x - y).toByte - } + implicit val byteShow: Show[Byte] = Show.fromToString[Byte] } trait CharInstances extends cats.kernel.std.CharInstances { - implicit val charShow: Show[Char] = - Show.fromToString[Char] + implicit val charShow: Show[Char] = Show.fromToString[Char] } trait ShortInstances extends cats.kernel.std.ShortInstances { - - implicit val shortShow: Show[Short] = - Show.fromToString[Short] - - implicit val shortGroup: CommutativeGroup[Short] = - new CommutativeGroup[Short] { - def combine(x: Short, y: Short): Short = (x + y).toShort - def empty: Short = 0 - def inverse(x: Short): Short = (-x).toShort - override def remove(x: Short, y: Short): Short = (x - y).toShort - } + implicit val shortShow: Show[Short] = Show.fromToString[Short] } trait LongInstances extends cats.kernel.std.LongInstances { - - implicit val longShow: Show[Long] = - Show.fromToString[Long] - - implicit val longGroup: CommutativeGroup[Long] = - new CommutativeGroup[Long] { - def combine(x: Long, y: Long): Long = x + y - def empty: Long = 0L - def inverse(x: Long): Long = -x - override def remove(x: Long, y: Long): Long = x - y - } + implicit val longShow: Show[Long] = Show.fromToString[Long] } trait FloatInstances extends cats.kernel.std.FloatInstances { - - implicit val floatShow: Show[Float] = - Show.fromToString[Float] - - implicit val floatGroup: CommutativeGroup[Float] = - new CommutativeGroup[Float] { - def combine(x: Float, y: Float): Float = x + y - def empty: Float = 0F - def inverse(x: Float): Float = -x - override def remove(x: Float, y: Float): Float = x - y - } + implicit val floatShow: Show[Float] = Show.fromToString[Float] } trait DoubleInstances extends cats.kernel.std.DoubleInstances { - - implicit val doubleShow: Show[Double] = - Show.fromToString[Double] - - implicit val doubleGroup: CommutativeGroup[Double] = - new CommutativeGroup[Double] { - def combine(x: Double, y: Double): Double = x + y - def empty: Double = 0D - def inverse(x: Double): Double = -x - override def remove(x: Double, y: Double): Double = x - y - } + implicit val doubleShow: Show[Double] = Show.fromToString[Double] } trait BooleanInstances extends cats.kernel.std.BooleanInstances { - - implicit val booleanShow: Show[Boolean] = - Show.fromToString[Boolean] + implicit val booleanShow: Show[Boolean] = Show.fromToString[Boolean] } trait UnitInstances extends cats.kernel.std.UnitInstances { - - implicit val unitShow: Show[Unit] = - Show.fromToString[Unit] + implicit val unitShow: Show[Unit] = Show.fromToString[Unit] } diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala b/kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala index f12ed7d2e8..281d853f50 100644 --- a/kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala +++ b/kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala @@ -81,45 +81,6 @@ trait GroupLaws[A] extends Laws { parents = List(group, commutativeMonoid) ) - // // additive groups - // - // def additiveSemigroup(implicit A: AdditiveSemigroup[A]) = new AdditiveProperties( - // base = semigroup(A.additive), - // parents = Nil, - // Rules.serializable(A), - // Rules.repeat1("sumN")(A.sumN), - // Rules.repeat2("sumN", "+")(A.sumN)(A.plus) - // ) - // - // def additiveCommutativeSemigroup(implicit A: AdditiveCommutativeSemigroup[A]) = new AdditiveProperties( - // base = commutativeSemigroup(A.additive), - // parents = List(additiveSemigroup) - // ) - // - // def additiveMonoid(implicit A: AdditiveMonoid[A]) = new AdditiveProperties( - // base = monoid(A.additive), - // parents = List(additiveSemigroup), - // Rules.repeat0("sumN", "zero", A.zero)(A.sumN), - // Rules.collect0("sum", "zero", A.zero)(A.sum) - // ) - // - // def additiveCommutativeMonoid(implicit A: AdditiveCommutativeMonoid[A]) = new AdditiveProperties( - // base = commutativeMonoid(A.additive), - // parents = List(additiveMonoid) - // ) - // - // def additiveGroup(implicit A: AdditiveGroup[A]) = new AdditiveProperties( - // base = group(A.additive), - // parents = List(additiveMonoid), - // Rules.consistentInverse("subtract")(A.minus)(A.plus)(A.negate) - // ) - // - // def additiveCommutativeGroup(implicit A: AdditiveCommutativeGroup[A]) = new AdditiveProperties( - // base = commutativeGroup(A.additive), - // parents = List(additiveGroup) - // ) - - // property classes class GroupProperties( @@ -129,14 +90,4 @@ trait GroupLaws[A] extends Laws { ) extends RuleSet { val bases = Nil } - - // class AdditiveProperties( - // val base: GroupProperties, - // val parents: Seq[AdditiveProperties], - // val props: (String, Prop)* - // ) extends RuleSet { - // val name = base.name - // val bases = List("base" -> base) - // } - } diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/package.scala b/kernel-laws/src/main/scala/cats/kernel/laws/package.scala index 4e576499df..f4c7ed5ca0 100644 --- a/kernel-laws/src/main/scala/cats/kernel/laws/package.scala +++ b/kernel-laws/src/main/scala/cats/kernel/laws/package.scala @@ -8,7 +8,7 @@ import Prop.{False, Proof, Result} package object laws { - implicit def PredicateFromMonoid[A](implicit ev: Eq[A], A: Monoid[A]): Predicate[A] = + implicit def nonEmptyPredicate[A](implicit ev: Eq[A], A: Monoid[A]): Predicate[A] = new Predicate[A] { def apply(a: A) = ev.neqv(a, A.empty) } diff --git a/kernel/src/main/scala/cats/kernel/Group.scala b/kernel/src/main/scala/cats/kernel/Group.scala index 2a3412dbbd..11776f69bd 100644 --- a/kernel/src/main/scala/cats/kernel/Group.scala +++ b/kernel/src/main/scala/cats/kernel/Group.scala @@ -25,11 +25,24 @@ trait Group[@sp(Int, Long, Float, Double) A] extends Any with Monoid[A] { * Return `a` appended to itself `n` times. If `n` is negative, then * this returns `inverse(a)` appended to itself `n` times. */ - override def combineN(a: A, n: Int): A = + override def combineN(a: A, n: Int): A = { + // This method is a bit tricky. Normally, to sum x a negative + // number of times (n), we can sum (-x) a positive number of times + // (-n). The issue here is that Int.MinValue cannot be negated; in + // other words (-MinValue) == MinValue. + // + // To work around that, we rely on the fact that we can divide n + // by 2 if we sum 2a (i.e. combine(a, a)) instead. Here is the + // transformation we use: + // + // combineN(x, -2147483648) + // combineN(combine(x, x), -1073741824) + // combineN(inverse(combine(x, x)), 1073741824) if (n > 0) repeatedCombineN(a, n) else if (n == 0) empty else if (n == Int.MinValue) combineN(inverse(combine(a, a)), 1073741824) else repeatedCombineN(inverse(a), -n) + } } trait GroupFunctions[G[T] <: Group[T]] extends MonoidFunctions[Group] { diff --git a/kernel/src/main/scala/cats/kernel/Order.scala b/kernel/src/main/scala/cats/kernel/Order.scala index 7381684b5c..c3e5a102b0 100644 --- a/kernel/src/main/scala/cats/kernel/Order.scala +++ b/kernel/src/main/scala/cats/kernel/Order.scala @@ -177,13 +177,14 @@ object Order extends OrderFunctions { /** * An `Order` instance that considers all `A` instances to be equal. */ - def allEqual[A]: Order[A] = new Order[A] { - def compare(x: A, y: A): Int = 0 - } + def allEqual[A]: Order[A] = + new Order[A] { + def compare(x: A, y: A): Int = 0 + } /** - * A `Monoid[Order[A]]` can be generated for all `A` with the following + * A `Band[Order[A]]` can be generated for all `A` with the following * properties: * * `empty` returns a trivial `Order[A]` which considers all `A` instances to @@ -194,16 +195,16 @@ object Order extends OrderFunctions { * * @see [[Order.whenEqual]] */ - def whenEqualMonoid[A]: Monoid[Order[A]] = - new Monoid[Order[A]] { + def whenEqualMonoid[A]: Band[Order[A]] = + new Band[Order[A]] { val empty: Order[A] = allEqual[A] - def combine(x: Order[A], y: Order[A]): Order[A] = x whenEqual y } - def fromOrdering[A](implicit ev: Ordering[A]): Order[A] = new Order[A] { - def compare(x: A, y: A): Int = ev.compare(x, y) + def fromOrdering[A](implicit ev: Ordering[A]): Order[A] = + new Order[A] { + def compare(x: A, y: A): Int = ev.compare(x, y) - override def toOrdering: Ordering[A] = ev - } + override def toOrdering: Ordering[A] = ev + } } diff --git a/kernel/src/main/scala/cats/kernel/std/bigInt.scala b/kernel/src/main/scala/cats/kernel/std/bigInt.scala index 13afc51058..f02552af20 100644 --- a/kernel/src/main/scala/cats/kernel/std/bigInt.scala +++ b/kernel/src/main/scala/cats/kernel/std/bigInt.scala @@ -6,6 +6,15 @@ package object bigInt extends BigIntInstances trait BigIntInstances { implicit val bigIntOrder: Order[BigInt] = new BigIntOrder + implicit val bigIntGroup: CommutativeGroup[BigInt] = + new BigIntGroup +} + +class BigIntGroup extends CommutativeGroup[BigInt] { + val empty: BigInt = BigInt(0) + def combine(x: BigInt, y: BigInt): BigInt = x + y + def inverse(x: BigInt): BigInt = -x + override def remove(x: BigInt, y: BigInt): BigInt = x - y } class BigIntOrder extends Order[BigInt] { diff --git a/kernel/src/main/scala/cats/kernel/std/boolean.scala b/kernel/src/main/scala/cats/kernel/std/boolean.scala index 8f83996118..6d3214efc5 100644 --- a/kernel/src/main/scala/cats/kernel/std/boolean.scala +++ b/kernel/src/main/scala/cats/kernel/std/boolean.scala @@ -4,7 +4,7 @@ package std package object boolean extends BooleanInstances trait BooleanInstances { - implicit val booleanOrder: BooleanOrder = + implicit val booleanOrder: Order[Boolean] = new BooleanOrder } diff --git a/kernel/src/main/scala/cats/kernel/std/byte.scala b/kernel/src/main/scala/cats/kernel/std/byte.scala index 0b1a948ff8..d635597c1f 100644 --- a/kernel/src/main/scala/cats/kernel/std/byte.scala +++ b/kernel/src/main/scala/cats/kernel/std/byte.scala @@ -5,6 +5,14 @@ package object byte extends ByteInstances trait ByteInstances { implicit val byteOrder: Order[Byte] = new ByteOrder + implicit val byteGroup: CommutativeGroup[Byte] = new ByteGroup +} + +class ByteGroup extends CommutativeGroup[Byte] { + def combine(x: Byte, y: Byte): Byte = (x + y).toByte + def empty: Byte = 0 + def inverse(x: Byte): Byte = (-x).toByte + override def remove(x: Byte, y: Byte): Byte = (x - y).toByte } class ByteOrder extends Order[Byte] { diff --git a/kernel/src/main/scala/cats/kernel/std/double.scala b/kernel/src/main/scala/cats/kernel/std/double.scala index 32945635c0..215fa96564 100644 --- a/kernel/src/main/scala/cats/kernel/std/double.scala +++ b/kernel/src/main/scala/cats/kernel/std/double.scala @@ -5,6 +5,14 @@ import java.lang.Math trait DoubleInstances { implicit val doubleOrder: Order[Double] = new DoubleOrder + implicit val doubleGroup: CommutativeGroup[Double] = new DoubleGroup +} + +class DoubleGroup extends CommutativeGroup[Double] { + def combine(x: Double, y: Double): Double = x + y + def empty: Double = 0D + def inverse(x: Double): Double = -x + override def remove(x: Double, y: Double): Double = x - y } class DoubleOrder extends Order[Double] { diff --git a/kernel/src/main/scala/cats/kernel/std/float.scala b/kernel/src/main/scala/cats/kernel/std/float.scala index f50e96f1fc..81c72ed5d5 100644 --- a/kernel/src/main/scala/cats/kernel/std/float.scala +++ b/kernel/src/main/scala/cats/kernel/std/float.scala @@ -2,7 +2,18 @@ package cats.kernel package std trait FloatInstances { - implicit val floatOrder = new FloatOrder + implicit val floatOrder: Order[Float] = new FloatOrder + implicit val floatGroup: CommutativeGroup[Float] = new FloatGroup +} + +/** + * This is only approximately associative. + */ +class FloatGroup extends CommutativeGroup[Float] { + def combine(x: Float, y: Float): Float = x + y + def empty: Float = 0F + def inverse(x: Float): Float = -x + override def remove(x: Float, y: Float): Float = x - y } /** diff --git a/kernel/src/main/scala/cats/kernel/std/int.scala b/kernel/src/main/scala/cats/kernel/std/int.scala index 59fb8474ff..7224bb059e 100644 --- a/kernel/src/main/scala/cats/kernel/std/int.scala +++ b/kernel/src/main/scala/cats/kernel/std/int.scala @@ -5,6 +5,14 @@ package object int extends IntInstances trait IntInstances { implicit val intOrder: Order[Int] = new IntOrder + implicit val intGroup: CommutativeGroup[Int] = new IntGroup +} + +class IntGroup extends CommutativeGroup[Int] { + def combine(x: Int, y: Int): Int = x + y + def empty: Int = 0 + def inverse(x: Int): Int = -x + override def remove(x: Int, y: Int): Int = x - y } class IntOrder extends Order[Int] { diff --git a/kernel/src/main/scala/cats/kernel/std/long.scala b/kernel/src/main/scala/cats/kernel/std/long.scala index 8d978062cc..21552b6a6f 100644 --- a/kernel/src/main/scala/cats/kernel/std/long.scala +++ b/kernel/src/main/scala/cats/kernel/std/long.scala @@ -4,7 +4,15 @@ package std package object long extends LongInstances trait LongInstances { - implicit val longOrder = new LongOrder + implicit val longOrder: Order[Long] = new LongOrder + implicit val longGroup: CommutativeGroup[Long] = new LongGroup +} + +class LongGroup extends CommutativeGroup[Long] { + def combine(x: Long, y: Long): Long = x + y + def empty: Long = 0L + def inverse(x: Long): Long = -x + override def remove(x: Long, y: Long): Long = x - y } class LongOrder extends Order[Long] { diff --git a/kernel/src/main/scala/cats/kernel/std/set.scala b/kernel/src/main/scala/cats/kernel/std/set.scala index 844fab2fe5..6f91f97107 100644 --- a/kernel/src/main/scala/cats/kernel/std/set.scala +++ b/kernel/src/main/scala/cats/kernel/std/set.scala @@ -7,8 +7,8 @@ trait SetInstances { implicit def setPartialOrder[A]: PartialOrder[Set[A]] = new SetPartialOrder[A] - implicit def setMonoid[A]: Monoid[Set[A]] = - new SetMonoid[A] + implicit def setSemilattice[A]: BoundedSemilattice[Set[A]] = + new SetSemilattice[A] } class SetPartialOrder[A] extends PartialOrder[Set[A]] { @@ -22,7 +22,7 @@ class SetPartialOrder[A] extends PartialOrder[Set[A]] { x == y } -class SetMonoid[A] extends Monoid[Set[A]] { +class SetSemilattice[A] extends BoundedSemilattice[Set[A]] { def empty: Set[A] = Set.empty def combine(x: Set[A], y: Set[A]): Set[A] = x | y } diff --git a/kernel/src/main/scala/cats/kernel/std/short.scala b/kernel/src/main/scala/cats/kernel/std/short.scala index ba3582205c..531dfae07d 100644 --- a/kernel/src/main/scala/cats/kernel/std/short.scala +++ b/kernel/src/main/scala/cats/kernel/std/short.scala @@ -5,6 +5,14 @@ package object short extends ShortInstances trait ShortInstances { implicit val shortOrder: Order[Short] = new ShortOrder + implicit val shortGroup: CommutativeGroup[Short] = new ShortGroup +} + +class ShortGroup extends CommutativeGroup[Short] { + def combine(x: Short, y: Short): Short = (x + y).toShort + def empty: Short = 0 + def inverse(x: Short): Short = (-x).toShort + override def remove(x: Short, y: Short): Short = (x - y).toShort } class ShortOrder extends Order[Short] { diff --git a/kernel/src/main/scala/cats/kernel/std/unit.scala b/kernel/src/main/scala/cats/kernel/std/unit.scala index 8068b246de..fd0b8b8b1a 100644 --- a/kernel/src/main/scala/cats/kernel/std/unit.scala +++ b/kernel/src/main/scala/cats/kernel/std/unit.scala @@ -4,11 +4,14 @@ package std package object unit extends UnitInstances trait UnitInstances { - implicit val unitAlgebra: Order[Unit] with BoundedSemilattice[Unit] = + implicit val unitOrder: Order[Unit] = + new UnitOrder + + implicit val unitAlgebra: BoundedSemilattice[Unit] with CommutativeGroup[Unit] = new UnitAlgebra } -class UnitAlgebra extends Order[Unit] with BoundedSemilattice[Unit] { +class UnitOrder extends Order[Unit] { def compare(x: Unit, y: Unit): Int = 0 override def eqv(x: Unit, y: Unit): Boolean = true @@ -20,9 +23,13 @@ class UnitAlgebra extends Order[Unit] with BoundedSemilattice[Unit] { override def min(x: Unit, y: Unit): Unit = () override def max(x: Unit, y: Unit): Unit = () +} +class UnitAlgebra extends BoundedSemilattice[Unit] with CommutativeGroup[Unit] { def empty: Unit = () def combine(x: Unit, y: Unit): Unit = () + override def remove(x: Unit, y: Unit): Unit = () + def inverse(x: Unit): Unit = () override protected[this] def repeatedCombineN(a: Unit, n: Int): Unit = () override def combineAllOption(as: TraversableOnce[Unit]): Option[Unit] = if (as.isEmpty) None else Some(()) From 4b8b7895429f9af30c58dd87d08311c0f542a434 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 16:12:27 -0400 Subject: [PATCH 12/17] Remove totally-commented file. --- .../main/scala/cats/kernel/laws/CheckSupport.scala | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala b/kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala deleted file mode 100644 index ad80b28a19..0000000000 --- a/kernel-laws/src/main/scala/cats/kernel/laws/CheckSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -// package cats.kernel.laws -// -// /** -// * This object contains Arbitrary instances for types defined in -// * cats.kernel.std, as well as anything else we'd like to import to assist -// * in running ScalaCheck tests. -// * -// * (Since cats.kernel-std has no dependencies, its types can't define -// * Arbitrary instances in companions.) -// */ -// object CheckSupport { -// } From ada6b75dfb01d118abb2daacb1ae8dede43c6d98 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 17:30:19 -0400 Subject: [PATCH 13/17] Remove bintray resolver from cats-kernel's SBT config. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6189512395..7bd28f36ce 100644 --- a/build.sbt +++ b/build.sbt @@ -25,9 +25,9 @@ lazy val catsDoctestSettings = Seq( ) ++ doctestSettings lazy val kernelSettings = Seq( + // don't warn on value discarding because it's broken on 2.10 with @sp(Unit) scalacOptions ++= commonScalacOptions.filter(_ != "-Ywarn-value-discard"), resolvers ++= Seq( - "bintray/non" at "http://dl.bintray.com/non/maven", Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots")), parallelExecution in Test := false, From de8a2fc289940eb60ec569b9c7555b5b4998198e Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 18:34:39 -0400 Subject: [PATCH 14/17] Improve testing, remove lawless instance. This commit fixes some problems which better testing exposed. The biggest was that when we moved from the "sparse vector" interpretation of maps to the "strict" interpretation some of our instances became unlawful. We can't define a group directly on maps because we can't enforce the equivalence of "absent key" and "present key with empty value". --- .../scala/cats/kernel/laws/LawTests.scala | 42 +++++++++++-------- kernel/src/main/scala/cats/kernel/Order.scala | 4 +- .../src/main/scala/cats/kernel/std/Util.scala | 15 +------ .../src/main/scala/cats/kernel/std/all.scala | 6 ++- .../src/main/scala/cats/kernel/std/map.scala | 17 +------- 5 files changed, 34 insertions(+), 50 deletions(-) diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index 9064c9b1ef..03ca448319 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -25,19 +25,19 @@ class LawTests extends FunSuite with Discipline { implicit def orderLaws[A: Eq: Arbitrary] = OrderLaws[A] implicit def groupLaws[A: Eq: Arbitrary] = GroupLaws[A] + laws[OrderLaws, Map[String, HasEq[Int]]].check(_.eqv) laws[OrderLaws, List[HasEq[Int]]].check(_.eqv) - laws[OrderLaws, List[HasEq[String]]].check(_.eqv) laws[OrderLaws, Option[HasEq[Int]]].check(_.eqv) - laws[OrderLaws, Option[HasEq[String]]].check(_.eqv) - laws[OrderLaws, Map[Char, Int]].check(_.eqv) - laws[OrderLaws, Map[Int, BigInt]].check(_.eqv) + laws[OrderLaws, Array[HasEq[Int]]].check(_.eqv) + laws[OrderLaws, Vector[HasEq[Int]]].check(_.eqv) + laws[OrderLaws, Stream[HasEq[Int]]].check(_.eqv) + laws[OrderLaws, Set[Int]].check(_.partialOrder) laws[OrderLaws, Option[HasPartialOrder[Int]]].check(_.partialOrder) - laws[OrderLaws, Option[HasPartialOrder[String]]].check(_.partialOrder) laws[OrderLaws, List[HasPartialOrder[Int]]].check(_.partialOrder) - laws[OrderLaws, List[HasPartialOrder[String]]].check(_.partialOrder) - laws[OrderLaws, Set[Int]].check(_.partialOrder) - laws[OrderLaws, Array[Int]].check(_.partialOrder) + laws[OrderLaws, Array[HasPartialOrder[Int]]].check(_.partialOrder) + laws[OrderLaws, Vector[HasPartialOrder[Int]]].check(_.partialOrder) + laws[OrderLaws, Stream[HasPartialOrder[Int]]].check(_.partialOrder) laws[OrderLaws, Unit].check(_.order) laws[OrderLaws, Boolean].check(_.order) @@ -51,26 +51,33 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, Option[String]].check(_.order) laws[OrderLaws, List[String]].check(_.order) laws[OrderLaws, Array[Int]].check(_.order) + laws[OrderLaws, Vector[Int]].check(_.order) + laws[OrderLaws, Stream[Int]].check(_.order) laws[OrderLaws, Int]("fromOrdering").check(_.order(Order.fromOrdering[Int])) - laws[GroupLaws, Int].check(_.monoid) laws[GroupLaws, String].check(_.monoid) laws[GroupLaws, Option[Int]].check(_.monoid) laws[GroupLaws, Option[String]].check(_.monoid) laws[GroupLaws, List[Int]].check(_.monoid) + laws[GroupLaws, Vector[Int]].check(_.monoid) + laws[GroupLaws, Stream[Int]].check(_.monoid) laws[GroupLaws, List[String]].check(_.monoid) + laws[GroupLaws, Map[String, Int]].check(_.monoid) + + laws[GroupLaws, Unit].check(_.commutativeGroup) + laws[GroupLaws, Byte].check(_.commutativeGroup) + laws[GroupLaws, Short].check(_.commutativeGroup) + laws[GroupLaws, Int].check(_.commutativeGroup) + laws[GroupLaws, Long].check(_.commutativeGroup) + //laws[GroupLaws, Float].check(_.commutativeGroup) // approximately associative + //laws[GroupLaws, Double].check(_.commutativeGroup) // approximately associative + laws[GroupLaws, BigInt].check(_.commutativeGroup) laws[GroupLaws, (Int, Int)].check(_.band) + laws[GroupLaws, Unit].check(_.boundedSemilattice) // esoteric machinery follows... - implicit lazy val intGroup: Group[Int] = - new Group[Int] { - def empty: Int = 0 - def combine(x: Int, y: Int): Int = x + y - def inverse(x: Int): Int = -x - } - implicit lazy val band: Band[(Int, Int)] = new Band[(Int, Int)] { def combine(a: (Int, Int), b: (Int, Int)) = (a._1, b._2) @@ -109,8 +116,9 @@ class LawTests extends FunSuite with Discipline { .forall { case (x, y) => a.eqv(x, y) == b.eqv(x, y) } } - implicit val monoidOrderN: Monoid[Order[N]] = Order.whenEqualMonoid[N] + implicit val monoidOrderN = Order.whenEqualMonoid[N] laws[GroupLaws, Order[N]].check(_.monoid) + laws[GroupLaws, Order[N]].check(_.band) { implicit val bsEqN: BoundedSemilattice[Eq[N]] = Eq.allEqualBoundedSemilattice[N] diff --git a/kernel/src/main/scala/cats/kernel/Order.scala b/kernel/src/main/scala/cats/kernel/Order.scala index c3e5a102b0..743920eac9 100644 --- a/kernel/src/main/scala/cats/kernel/Order.scala +++ b/kernel/src/main/scala/cats/kernel/Order.scala @@ -195,8 +195,8 @@ object Order extends OrderFunctions { * * @see [[Order.whenEqual]] */ - def whenEqualMonoid[A]: Band[Order[A]] = - new Band[Order[A]] { + def whenEqualMonoid[A]: Monoid[Order[A]] with Band[Order[A]] = + new Monoid[Order[A]] with Band[Order[A]] { val empty: Order[A] = allEqual[A] def combine(x: Order[A], y: Order[A]): Order[A] = x whenEqual y } diff --git a/kernel/src/main/scala/cats/kernel/std/Util.scala b/kernel/src/main/scala/cats/kernel/std/Util.scala index 5b0859e0da..6974b1b10a 100644 --- a/kernel/src/main/scala/cats/kernel/std/Util.scala +++ b/kernel/src/main/scala/cats/kernel/std/Util.scala @@ -37,19 +37,6 @@ object StaticMethods { wrapMutableMap(m) } - def subtractMap[K, V](x: Map[K, V], y: Map[K, V])(subtract: (V, V) => V)(negate: V => V): Map[K, V] = { - // even if x is smaller, we'd need to call map/foreach on y to - // negate all its values, so this is just as fast or faster. - val m = initMutableMap(x) - y.foreach { case (k, v2) => - m(k) = m.get(k) match { - case Some(v1) => subtract(v1, v2) - case None => negate(v2) - } - } - wrapMutableMap(m) - } - def iteratorCompare[A](xs: Iterator[A], ys: Iterator[A])(implicit ev: Order[A]): Int = { while (true) { if (xs.hasNext) { @@ -90,7 +77,7 @@ object StaticMethods { while (true) { if (xs.hasNext) { if (ys.hasNext) { - if (ev.eqv(xs.next, ys.next)) return true + if (ev.neqv(xs.next, ys.next)) return false } else { return false } diff --git a/kernel/src/main/scala/cats/kernel/std/all.scala b/kernel/src/main/scala/cats/kernel/std/all.scala index 233e7d6c9c..fca7d96228 100644 --- a/kernel/src/main/scala/cats/kernel/std/all.scala +++ b/kernel/src/main/scala/cats/kernel/std/all.scala @@ -4,7 +4,8 @@ package std package object all extends AllInstances trait AllInstances - extends BigIntInstances + extends ArrayInstances + with BigIntInstances with BooleanInstances with ByteInstances with CharInstances @@ -17,7 +18,8 @@ trait AllInstances with OptionInstances with SetInstances with ShortInstances + with StreamInstances with StringInstances with TupleInstances with UnitInstances - with ArrayInstances + with VectorInstances diff --git a/kernel/src/main/scala/cats/kernel/std/map.scala b/kernel/src/main/scala/cats/kernel/std/map.scala index 912b7bab48..4174ed20a9 100644 --- a/kernel/src/main/scala/cats/kernel/std/map.scala +++ b/kernel/src/main/scala/cats/kernel/std/map.scala @@ -1,16 +1,11 @@ package cats.kernel package std -import cats.kernel.std.util.StaticMethods.{ addMap, subtractMap } +import cats.kernel.std.util.StaticMethods.addMap package object map extends MapInstances -trait MapInstances extends MapInstances0 { - implicit def mapGroup[K, V: Group]: MapGroup[K, V] = - new MapGroup[K, V] -} - -trait MapInstances0 { +trait MapInstances { implicit def mapEq[K, V: Eq]: Eq[Map[K, V]] = new MapEq[K, V] implicit def mapMonoid[K, V: Semigroup]: MapMonoid[K, V] = @@ -33,11 +28,3 @@ class MapMonoid[K, V](implicit V: Semigroup[V]) extends Monoid[Map[K, V]] { def combine(x: Map[K, V], y: Map[K, V]): Map[K, V] = addMap(x, y)(V.combine) } - -class MapGroup[K, V](implicit V: Group[V]) extends MapMonoid[K, V] with Group[Map[K, V]] { - def inverse(x: Map[K, V]): Map[K, V] = - x.map { case (k, v) => (k, V.inverse(v)) } - - override def remove(x: Map[K, V], y: Map[K, V]): Map[K, V] = - subtractMap(x, y)(V.remove)(V.inverse) -} From fc0acf2f048eaee0a8be1512c6fb7bb28ca6559f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 25 Apr 2016 23:56:40 -0400 Subject: [PATCH 15/17] Optimize eqv/partialCompare/compare using eq where relevant. This is an optimization that we've known about for awhile and that Oscar suggested should be added to some of the instances for Eq, PartialOrder, and Order (those where the cost of doing the check can be linear). --- kernel/src/main/scala/cats/kernel/std/array.scala | 3 +++ kernel/src/main/scala/cats/kernel/std/bigInt.scala | 4 ++-- kernel/src/main/scala/cats/kernel/std/list.scala | 8 ++++---- kernel/src/main/scala/cats/kernel/std/map.scala | 3 ++- kernel/src/main/scala/cats/kernel/std/set.scala | 3 ++- kernel/src/main/scala/cats/kernel/std/stream.scala | 9 ++++++--- kernel/src/main/scala/cats/kernel/std/string.scala | 5 ++++- kernel/src/main/scala/cats/kernel/std/vector.scala | 9 ++++++--- 8 files changed, 29 insertions(+), 15 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/std/array.scala b/kernel/src/main/scala/cats/kernel/std/array.scala index 1cad7e6185..dc10038d70 100644 --- a/kernel/src/main/scala/cats/kernel/std/array.scala +++ b/kernel/src/main/scala/cats/kernel/std/array.scala @@ -22,6 +22,7 @@ object ArraySupport { else 0 def eqv[@sp A: Eq](x: Array[A], y: Array[A]): Boolean = { + if (x eq y) return true var i = 0 if (x.length != y.length) return false while (i < x.length && i < y.length && Eq.eqv(x(i), y(i))) i += 1 @@ -29,6 +30,7 @@ object ArraySupport { } def compare[@sp A: Order](x: Array[A], y: Array[A]): Int = { + if (x eq y) return 0 var i = 0 while (i < x.length && i < y.length) { val cmp = Order.compare(x(i), y(i)) @@ -39,6 +41,7 @@ object ArraySupport { } def partialCompare[@sp A: PartialOrder](x: Array[A], y: Array[A]): Double = { + if (x eq y) return 0.0 var i = 0 while (i < x.length && i < y.length) { val cmp = PartialOrder.partialCompare(x(i), y(i)) diff --git a/kernel/src/main/scala/cats/kernel/std/bigInt.scala b/kernel/src/main/scala/cats/kernel/std/bigInt.scala index f02552af20..36094a3512 100644 --- a/kernel/src/main/scala/cats/kernel/std/bigInt.scala +++ b/kernel/src/main/scala/cats/kernel/std/bigInt.scala @@ -21,8 +21,8 @@ class BigIntOrder extends Order[BigInt] { def compare(x: BigInt, y: BigInt): Int = x compare y - override def eqv(x:BigInt, y:BigInt): Boolean = x == y - override def neqv(x:BigInt, y:BigInt): Boolean = x != y + override def eqv(x: BigInt, y: BigInt): Boolean = x == y + override def neqv(x: BigInt, y: BigInt): Boolean = x != y override def gt(x: BigInt, y: BigInt): Boolean = x > y override def gteqv(x: BigInt, y: BigInt): Boolean = x >= y override def lt(x: BigInt, y: BigInt): Boolean = x < y diff --git a/kernel/src/main/scala/cats/kernel/std/list.scala b/kernel/src/main/scala/cats/kernel/std/list.scala index ee717ac78e..3baeb84b58 100644 --- a/kernel/src/main/scala/cats/kernel/std/list.scala +++ b/kernel/src/main/scala/cats/kernel/std/list.scala @@ -33,7 +33,7 @@ class ListOrder[A](implicit ev: Order[A]) extends Order[List[A]] { if (n != 0) n else loop(xs, ys) } } - loop(xs, ys) + if (xs eq ys) 0 else loop(xs, ys) } } @@ -51,12 +51,12 @@ class ListPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Lis if (n != 0.0) n else loop(xs, ys) } } - loop(xs, ys) + if (xs eq ys) 0.0 else loop(xs, ys) } } class ListEq[A](implicit ev: Eq[A]) extends Eq[List[A]] { - def eqv(x: List[A], y: List[A]): Boolean = { + def eqv(xs: List[A], ys: List[A]): Boolean = { def loop(xs: List[A], ys: List[A]): Boolean = xs match { case Nil => @@ -69,7 +69,7 @@ class ListEq[A](implicit ev: Eq[A]) extends Eq[List[A]] { false } } - loop(x, y) + (xs eq ys) || loop(xs, ys) } } diff --git a/kernel/src/main/scala/cats/kernel/std/map.scala b/kernel/src/main/scala/cats/kernel/std/map.scala index 4174ed20a9..61c8f65625 100644 --- a/kernel/src/main/scala/cats/kernel/std/map.scala +++ b/kernel/src/main/scala/cats/kernel/std/map.scala @@ -14,7 +14,8 @@ trait MapInstances { class MapEq[K, V](implicit V: Eq[V]) extends Eq[Map[K, V]] { def eqv(x: Map[K, V], y: Map[K, V]): Boolean = - x.size == y.size && x.forall { case (k, v1) => + if (x eq y) true + else x.size == y.size && x.forall { case (k, v1) => y.get(k) match { case Some(v2) => V.eqv(v1, v2) case None => false diff --git a/kernel/src/main/scala/cats/kernel/std/set.scala b/kernel/src/main/scala/cats/kernel/std/set.scala index 6f91f97107..ae84e1d09d 100644 --- a/kernel/src/main/scala/cats/kernel/std/set.scala +++ b/kernel/src/main/scala/cats/kernel/std/set.scala @@ -13,7 +13,8 @@ trait SetInstances { class SetPartialOrder[A] extends PartialOrder[Set[A]] { def partialCompare(x: Set[A], y: Set[A]): Double = - if (x.size < y.size) if (x.subsetOf(y)) -1.0 else Double.NaN + if (x eq y) 0.0 + else if (x.size < y.size) if (x.subsetOf(y)) -1.0 else Double.NaN else if (y.size < x.size) -partialCompare(y, x) else if (x == y) 0.0 else Double.NaN diff --git a/kernel/src/main/scala/cats/kernel/std/stream.scala b/kernel/src/main/scala/cats/kernel/std/stream.scala index 899cc58d72..f924c786d3 100644 --- a/kernel/src/main/scala/cats/kernel/std/stream.scala +++ b/kernel/src/main/scala/cats/kernel/std/stream.scala @@ -20,17 +20,20 @@ trait StreamInstances2 { class StreamOrder[A](implicit ev: Order[A]) extends Order[Stream[A]] { def compare(xs: Stream[A], ys: Stream[A]): Int = - StaticMethods.iteratorCompare(xs.iterator, ys.iterator) + if (xs eq ys) 0 + else StaticMethods.iteratorCompare(xs.iterator, ys.iterator) } class StreamPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Stream[A]] { def partialCompare(xs: Stream[A], ys: Stream[A]): Double = - StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) + if (xs eq ys) 0.0 + else StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) } class StreamEq[A](implicit ev: Eq[A]) extends Eq[Stream[A]] { def eqv(xs: Stream[A], ys: Stream[A]): Boolean = - StaticMethods.iteratorEq(xs.iterator, ys.iterator) + if (xs eq ys) true + else StaticMethods.iteratorEq(xs.iterator, ys.iterator) } class StreamMonoid[A] extends Monoid[Stream[A]] { diff --git a/kernel/src/main/scala/cats/kernel/std/string.scala b/kernel/src/main/scala/cats/kernel/std/string.scala index 52ac8de129..b7394a3086 100644 --- a/kernel/src/main/scala/cats/kernel/std/string.scala +++ b/kernel/src/main/scala/cats/kernel/std/string.scala @@ -9,7 +9,10 @@ trait StringInstances { } class StringOrder extends Order[String] { - def compare(x: String, y: String): Int = x compare y + override def eqv(x: String, y: String): Boolean = + x == y + def compare(x: String, y: String): Int = + if (x eq y) 0 else x compareTo y } class StringMonoid extends Monoid[String] { diff --git a/kernel/src/main/scala/cats/kernel/std/vector.scala b/kernel/src/main/scala/cats/kernel/std/vector.scala index 50c13c5174..3bb99381a8 100644 --- a/kernel/src/main/scala/cats/kernel/std/vector.scala +++ b/kernel/src/main/scala/cats/kernel/std/vector.scala @@ -20,17 +20,20 @@ trait VectorInstances2 { class VectorOrder[A](implicit ev: Order[A]) extends Order[Vector[A]] { def compare(xs: Vector[A], ys: Vector[A]): Int = - StaticMethods.iteratorCompare(xs.iterator, ys.iterator) + if (xs eq ys) 0 + else StaticMethods.iteratorCompare(xs.iterator, ys.iterator) } class VectorPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Vector[A]] { def partialCompare(xs: Vector[A], ys: Vector[A]): Double = - StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) + if (xs eq ys) 0.0 + else StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) } class VectorEq[A](implicit ev: Eq[A]) extends Eq[Vector[A]] { def eqv(xs: Vector[A], ys: Vector[A]): Boolean = - StaticMethods.iteratorEq(xs.iterator, ys.iterator) + if (xs eq ys) true + else StaticMethods.iteratorEq(xs.iterator, ys.iterator) } class VectorMonoid[A] extends Monoid[Vector[A]] { From 5f408e13e076e105e1ba08acbced4cb0678dfc89 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 26 Apr 2016 10:59:17 -0400 Subject: [PATCH 16/17] Address some of Cody's review comments. This does the following: 1. Fixes a lot of return types. 2. Removes cats.kernel.std.array 3. Removes the unused cats.kernel.Priority 4. Improves some comments 5. Makes a small efficiency improvement for Monoid[Map[K, V]] --- .../scala/cats/kernel/laws/LawTests.scala | 3 - .../scala/cats/kernel/CommutativeGroup.scala | 4 +- kernel/src/main/scala/cats/kernel/Order.scala | 5 +- .../src/main/scala/cats/kernel/Priority.scala | 65 ----------------- .../src/main/scala/cats/kernel/std/Util.scala | 10 +-- .../src/main/scala/cats/kernel/std/all.scala | 3 +- .../main/scala/cats/kernel/std/array.scala | 73 ------------------- .../src/main/scala/cats/kernel/std/list.scala | 12 ++- .../src/main/scala/cats/kernel/std/map.scala | 10 ++- .../main/scala/cats/kernel/std/option.scala | 12 ++- .../main/scala/cats/kernel/std/stream.scala | 12 ++- .../main/scala/cats/kernel/std/string.scala | 2 +- .../main/scala/cats/kernel/std/vector.scala | 12 ++- 13 files changed, 51 insertions(+), 172 deletions(-) delete mode 100644 kernel/src/main/scala/cats/kernel/Priority.scala delete mode 100644 kernel/src/main/scala/cats/kernel/std/array.scala diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index 03ca448319..d504f99114 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -28,14 +28,12 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, Map[String, HasEq[Int]]].check(_.eqv) laws[OrderLaws, List[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Option[HasEq[Int]]].check(_.eqv) - laws[OrderLaws, Array[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Vector[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Stream[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Set[Int]].check(_.partialOrder) laws[OrderLaws, Option[HasPartialOrder[Int]]].check(_.partialOrder) laws[OrderLaws, List[HasPartialOrder[Int]]].check(_.partialOrder) - laws[OrderLaws, Array[HasPartialOrder[Int]]].check(_.partialOrder) laws[OrderLaws, Vector[HasPartialOrder[Int]]].check(_.partialOrder) laws[OrderLaws, Stream[HasPartialOrder[Int]]].check(_.partialOrder) @@ -50,7 +48,6 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, List[Int]].check(_.order) laws[OrderLaws, Option[String]].check(_.order) laws[OrderLaws, List[String]].check(_.order) - laws[OrderLaws, Array[Int]].check(_.order) laws[OrderLaws, Vector[Int]].check(_.order) laws[OrderLaws, Stream[Int]].check(_.order) laws[OrderLaws, Int]("fromOrdering").check(_.order(Order.fromOrdering[Int])) diff --git a/kernel/src/main/scala/cats/kernel/CommutativeGroup.scala b/kernel/src/main/scala/cats/kernel/CommutativeGroup.scala index 6072cecd96..80a5c6fa8c 100644 --- a/kernel/src/main/scala/cats/kernel/CommutativeGroup.scala +++ b/kernel/src/main/scala/cats/kernel/CommutativeGroup.scala @@ -3,7 +3,8 @@ package cats.kernel import scala.{ specialized => sp } /** - * An abelian group is a group whose operation is commutative. + * An commutative group (also known as an abelian group) is a group + * whose combine operation is commutative. */ trait CommutativeGroup[@sp(Int, Long, Float, Double) A] extends Any with Group[A] with CommutativeMonoid[A] @@ -14,4 +15,3 @@ object CommutativeGroup extends GroupFunctions[CommutativeGroup] { */ @inline final def apply[A](implicit ev: CommutativeGroup[A]): CommutativeGroup[A] = ev } - diff --git a/kernel/src/main/scala/cats/kernel/Order.scala b/kernel/src/main/scala/cats/kernel/Order.scala index 743920eac9..53fcf6a931 100644 --- a/kernel/src/main/scala/cats/kernel/Order.scala +++ b/kernel/src/main/scala/cats/kernel/Order.scala @@ -184,7 +184,7 @@ object Order extends OrderFunctions { /** - * A `Band[Order[A]]` can be generated for all `A` with the following + * A `Monoid[Order[A]]` can be generated for all `A` with the following * properties: * * `empty` returns a trivial `Order[A]` which considers all `A` instances to @@ -193,6 +193,9 @@ object Order extends OrderFunctions { * `combine(x: Order[A], y: Order[A])` creates an `Order[A]` that first * orders by `x` and then (if two elements are equal) falls back to `y`. * + * This monoid is also a `Band[Order[A]]` since its combine + * operations is idempotent. + * * @see [[Order.whenEqual]] */ def whenEqualMonoid[A]: Monoid[Order[A]] with Band[Order[A]] = diff --git a/kernel/src/main/scala/cats/kernel/Priority.scala b/kernel/src/main/scala/cats/kernel/Priority.scala deleted file mode 100644 index a9021c5c83..0000000000 --- a/kernel/src/main/scala/cats/kernel/Priority.scala +++ /dev/null @@ -1,65 +0,0 @@ -package cats.kernel - -/** - * Priority is a type class for prioritized implicit search. - * - * This type class will attempt to provide an implicit instance of `P` - * (the preferred type). If that type is not available it will - * fallback to `F` (the fallback type). If neither type is available - * then a `Priority[P, F]` instance will not be available. - * - * This type can be useful for problems where multiple algorithms can - * be used, depending on the type classes available. - */ -sealed trait Priority[+P, +F] { - - import Priority.{Preferred, Fallback} - - def fold[B](f1: P => B)(f2: F => B): B = - this match { - case Preferred(x) => f1(x) - case Fallback(y) => f2(y) - } - - def join[U >: P with F]: U = - fold(_.asInstanceOf[U])(_.asInstanceOf[U]) - - def bimap[P2, F2](f1: P => P2)(f2: F => F2): Priority[P2, F2] = - this match { - case Preferred(x) => Preferred(f1(x)) - case Fallback(y) => Fallback(f2(y)) - } - - def toEither: Either[P, F] = - fold[Either[P, F]](p => Left(p))(f => Right(f)) - - def isPreferred: Boolean = - fold(_ => true)(_ => false) - - def isFallback: Boolean = - fold(_ => false)(_ => true) - - def getPreferred: Option[P] = - fold[Option[P]](p => Some(p))(_ => None) - - def getFallback: Option[F] = - fold[Option[F]](_ => None)(f => Some(f)) -} - -object Priority extends FindPreferred { - - case class Preferred[P](get: P) extends Priority[P, Nothing] - case class Fallback[F](get: F) extends Priority[Nothing, F] - - def apply[P, F](implicit ev: Priority[P, F]): Priority[P, F] = ev -} - -private[kernel] trait FindPreferred extends FindFallback { - implicit def preferred[P](implicit ev: P): Priority[P, Nothing] = - Priority.Preferred(ev) -} - -private[kernel] trait FindFallback { - implicit def fallback[F](implicit ev: F): Priority[Nothing, F] = - Priority.Fallback(ev) -} diff --git a/kernel/src/main/scala/cats/kernel/std/Util.scala b/kernel/src/main/scala/cats/kernel/std/Util.scala index 6974b1b10a..50847f10f7 100644 --- a/kernel/src/main/scala/cats/kernel/std/Util.scala +++ b/kernel/src/main/scala/cats/kernel/std/Util.scala @@ -22,15 +22,13 @@ object StaticMethods { def -(key: K): Map[K, V] = m.toMap - key } - def addMap[K, V](x: Map[K, V], y: Map[K, V])(f: (V, V) => V): Map[K, V] = { - val (small, big, g) = - if (x.size <= y.size) (x, y, f) - else (y, x, (v1: V, v2: V) => f(v2, v1)) - + // the caller should arrange so that the smaller map is the first + // argument, and the larger map is the second. + def addMap[K, V](small: Map[K, V], big: Map[K, V])(f: (V, V) => V): Map[K, V] = { val m = initMutableMap(big) small.foreach { case (k, v1) => m(k) = m.get(k) match { - case Some(v2) => g(v1, v2) + case Some(v2) => f(v1, v2) case None => v1 } } diff --git a/kernel/src/main/scala/cats/kernel/std/all.scala b/kernel/src/main/scala/cats/kernel/std/all.scala index fca7d96228..a7c1528958 100644 --- a/kernel/src/main/scala/cats/kernel/std/all.scala +++ b/kernel/src/main/scala/cats/kernel/std/all.scala @@ -4,8 +4,7 @@ package std package object all extends AllInstances trait AllInstances - extends ArrayInstances - with BigIntInstances + extends BigIntInstances with BooleanInstances with ByteInstances with CharInstances diff --git a/kernel/src/main/scala/cats/kernel/std/array.scala b/kernel/src/main/scala/cats/kernel/std/array.scala deleted file mode 100644 index dc10038d70..0000000000 --- a/kernel/src/main/scala/cats/kernel/std/array.scala +++ /dev/null @@ -1,73 +0,0 @@ -package cats.kernel -package std - -import scala.{ specialized => sp } - -package object array extends ArrayInstances - -trait ArrayInstances { - implicit def arrayEq[@sp A: Eq]: Eq[Array[A]] = - new ArrayEq[A] - implicit def arrayOrder[@sp A: Order]: Order[Array[A]] = - new ArrayOrder[A] - implicit def arrayPartialOrder[@sp A: PartialOrder]: PartialOrder[Array[A]] = - new ArrayPartialOrder[A] -} - -object ArraySupport { - - private def signum(x: Int): Int = - if(x < 0) -1 - else if(x > 0) 1 - else 0 - - def eqv[@sp A: Eq](x: Array[A], y: Array[A]): Boolean = { - if (x eq y) return true - var i = 0 - if (x.length != y.length) return false - while (i < x.length && i < y.length && Eq.eqv(x(i), y(i))) i += 1 - i == x.length - } - - def compare[@sp A: Order](x: Array[A], y: Array[A]): Int = { - if (x eq y) return 0 - var i = 0 - while (i < x.length && i < y.length) { - val cmp = Order.compare(x(i), y(i)) - if (cmp != 0) return cmp - i += 1 - } - signum(x.length - y.length) - } - - def partialCompare[@sp A: PartialOrder](x: Array[A], y: Array[A]): Double = { - if (x eq y) return 0.0 - var i = 0 - while (i < x.length && i < y.length) { - val cmp = PartialOrder.partialCompare(x(i), y(i)) - // Double.NaN is also != 0.0 - if (cmp != 0.0) return cmp - i += 1 - } - signum(x.length - y.length).toDouble - } -} - -final class ArrayEq[@sp A: Eq] extends Eq[Array[A]] { - def eqv(x: Array[A], y: Array[A]): Boolean = - ArraySupport.eqv(x, y) -} - -final class ArrayOrder[@sp A: Order] extends Order[Array[A]] { - override def eqv(x: Array[A], y: Array[A]): Boolean = - ArraySupport.eqv(x, y) - def compare(x: Array[A], y: Array[A]): Int = - ArraySupport.compare(x, y) -} - -final class ArrayPartialOrder[@sp A: PartialOrder] extends PartialOrder[Array[A]] { - override def eqv(x: Array[A], y: Array[A]): Boolean = - ArraySupport.eqv(x, y) - override def partialCompare(x: Array[A], y: Array[A]): Double = - ArraySupport.partialCompare(x, y) -} diff --git a/kernel/src/main/scala/cats/kernel/std/list.scala b/kernel/src/main/scala/cats/kernel/std/list.scala index 3baeb84b58..056e4f36f2 100644 --- a/kernel/src/main/scala/cats/kernel/std/list.scala +++ b/kernel/src/main/scala/cats/kernel/std/list.scala @@ -7,16 +7,20 @@ import scala.collection.mutable package object list extends ListInstances trait ListInstances extends ListInstances1 { - implicit def listOrder[A: Order] = new ListOrder[A] - implicit def listMonoid[A] = new ListMonoid[A] + implicit def listOrder[A: Order]: Order[List[A]] = + new ListOrder[A] + implicit def listMonoid[A]: Monoid[List[A]] = + new ListMonoid[A] } trait ListInstances1 extends ListInstances2 { - implicit def listPartialOrder[A: PartialOrder] = new ListPartialOrder[A] + implicit def listPartialOrder[A: PartialOrder]: PartialOrder[List[A]] = + new ListPartialOrder[A] } trait ListInstances2 { - implicit def listEq[A: Eq] = new ListEq[A] + implicit def listEq[A: Eq]: Eq[List[A]] = + new ListEq[A] } class ListOrder[A](implicit ev: Order[A]) extends Order[List[A]] { diff --git a/kernel/src/main/scala/cats/kernel/std/map.scala b/kernel/src/main/scala/cats/kernel/std/map.scala index 61c8f65625..3743be1518 100644 --- a/kernel/src/main/scala/cats/kernel/std/map.scala +++ b/kernel/src/main/scala/cats/kernel/std/map.scala @@ -8,7 +8,7 @@ package object map extends MapInstances trait MapInstances { implicit def mapEq[K, V: Eq]: Eq[Map[K, V]] = new MapEq[K, V] - implicit def mapMonoid[K, V: Semigroup]: MapMonoid[K, V] = + implicit def mapMonoid[K, V: Semigroup]: Monoid[Map[K, V]] = new MapMonoid[K, V] } @@ -26,6 +26,10 @@ class MapEq[K, V](implicit V: Eq[V]) extends Eq[Map[K, V]] { class MapMonoid[K, V](implicit V: Semigroup[V]) extends Monoid[Map[K, V]] { def empty: Map[K, V] = Map.empty - def combine(x: Map[K, V], y: Map[K, V]): Map[K, V] = - addMap(x, y)(V.combine) + def combine(xs: Map[K, V], ys: Map[K, V]): Map[K, V] = + if (xs.size <= ys.size) { + addMap(xs, ys)((x, y) => V.combine(x, y)) + } else { + addMap(ys, xs)((y, x) => V.combine(x, y)) + } } diff --git a/kernel/src/main/scala/cats/kernel/std/option.scala b/kernel/src/main/scala/cats/kernel/std/option.scala index db0c8a7126..4a6fcf36d0 100644 --- a/kernel/src/main/scala/cats/kernel/std/option.scala +++ b/kernel/src/main/scala/cats/kernel/std/option.scala @@ -4,16 +4,20 @@ package std package object option extends OptionInstances trait OptionInstances extends OptionInstances1 { - implicit def optionOrder[A: Order] = new OptionOrder[A] - implicit def optionMonoid[A: Semigroup] = new OptionMonoid[A] + implicit def optionOrder[A: Order]: Order[Option[A]] = + new OptionOrder[A] + implicit def optionMonoid[A: Semigroup]: Monoid[Option[A]] = + new OptionMonoid[A] } trait OptionInstances1 extends OptionInstances0 { - implicit def optionPartialOrder[A: PartialOrder] = new OptionPartialOrder[A] + implicit def optionPartialOrder[A: PartialOrder]: PartialOrder[Option[A]] = + new OptionPartialOrder[A] } trait OptionInstances0 { - implicit def optionEq[A: Eq] = new OptionEq[A] + implicit def optionEq[A: Eq]: Eq[Option[A]] = + new OptionEq[A] } class OptionOrder[A](implicit A: Order[A]) extends Order[Option[A]] { diff --git a/kernel/src/main/scala/cats/kernel/std/stream.scala b/kernel/src/main/scala/cats/kernel/std/stream.scala index f924c786d3..edea35a471 100644 --- a/kernel/src/main/scala/cats/kernel/std/stream.scala +++ b/kernel/src/main/scala/cats/kernel/std/stream.scala @@ -6,16 +6,20 @@ import cats.kernel.std.util.StaticMethods package object stream extends StreamInstances trait StreamInstances extends StreamInstances1 { - implicit def streamOrder[A: Order] = new StreamOrder[A] - implicit def streamMonoid[A] = new StreamMonoid[A] + implicit def streamOrder[A: Order]: Order[Stream[A]] = + new StreamOrder[A] + implicit def streamMonoid[A]: Monoid[Stream[A]] = + new StreamMonoid[A] } trait StreamInstances1 extends StreamInstances2 { - implicit def streamPartialOrder[A: PartialOrder] = new StreamPartialOrder[A] + implicit def streamPartialOrder[A: PartialOrder]: PartialOrder[Stream[A]] = + new StreamPartialOrder[A] } trait StreamInstances2 { - implicit def streamEq[A: Eq] = new StreamEq[A] + implicit def streamEq[A: Eq]: Eq[Stream[A]] = + new StreamEq[A] } class StreamOrder[A](implicit ev: Order[A]) extends Order[Stream[A]] { diff --git a/kernel/src/main/scala/cats/kernel/std/string.scala b/kernel/src/main/scala/cats/kernel/std/string.scala index b7394a3086..6e46989761 100644 --- a/kernel/src/main/scala/cats/kernel/std/string.scala +++ b/kernel/src/main/scala/cats/kernel/std/string.scala @@ -5,7 +5,7 @@ package object string extends StringInstances trait StringInstances { implicit val stringOrder: Order[String] = new StringOrder - implicit val stringMonoid = new StringMonoid + implicit val stringMonoid: Monoid[String] = new StringMonoid } class StringOrder extends Order[String] { diff --git a/kernel/src/main/scala/cats/kernel/std/vector.scala b/kernel/src/main/scala/cats/kernel/std/vector.scala index 3bb99381a8..77ed2ae954 100644 --- a/kernel/src/main/scala/cats/kernel/std/vector.scala +++ b/kernel/src/main/scala/cats/kernel/std/vector.scala @@ -6,16 +6,20 @@ import cats.kernel.std.util.StaticMethods package object vector extends VectorInstances trait VectorInstances extends VectorInstances1 { - implicit def vectorOrder[A: Order] = new VectorOrder[A] - implicit def vectorMonoid[A] = new VectorMonoid[A] + implicit def vectorOrder[A: Order]: Order[Vector[A]] = + new VectorOrder[A] + implicit def vectorMonoid[A]: Monoid[Vector[A]] = + new VectorMonoid[A] } trait VectorInstances1 extends VectorInstances2 { - implicit def vectorPartialOrder[A: PartialOrder] = new VectorPartialOrder[A] + implicit def vectorPartialOrder[A: PartialOrder]: PartialOrder[Vector[A]] = + new VectorPartialOrder[A] } trait VectorInstances2 { - implicit def vectorEq[A: Eq] = new VectorEq[A] + implicit def vectorEq[A: Eq]: Eq[Vector[A]] = + new VectorEq[A] } class VectorOrder[A](implicit ev: Order[A]) extends Order[Vector[A]] { From 8fe89386ba4001b77fc08170c54051392df17f3f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 27 Apr 2016 00:25:53 -0400 Subject: [PATCH 17/17] Change function traits to be abstract classes. This commit also adds some "missing" function traits (as abstract classes) and replaces an instance of c.isNaN with isNaN(c) for performance. --- kernel/src/main/scala/cats/kernel/Band.scala | 2 +- .../cats/kernel/BoundedSemilattice.scala | 2 +- kernel/src/main/scala/cats/kernel/Eq.scala | 11 ++++--- kernel/src/main/scala/cats/kernel/Group.scala | 3 +- .../src/main/scala/cats/kernel/Monoid.scala | 8 +++-- kernel/src/main/scala/cats/kernel/Order.scala | 27 +++++------------ .../main/scala/cats/kernel/PartialOrder.scala | 29 +++++++++---------- .../main/scala/cats/kernel/Semigroup.scala | 2 +- .../main/scala/cats/kernel/Semilattice.scala | 9 +++++- 9 files changed, 48 insertions(+), 45 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/Band.scala b/kernel/src/main/scala/cats/kernel/Band.scala index 5404005633..e2d6c6ab05 100644 --- a/kernel/src/main/scala/cats/kernel/Band.scala +++ b/kernel/src/main/scala/cats/kernel/Band.scala @@ -9,7 +9,7 @@ import scala.{specialized => sp} */ trait Band[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] -object Band { +object Band extends SemigroupFunctions[Band] { /** * Access an implicit `Band[A]`. diff --git a/kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala b/kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala index b9497ec63e..9838164ea7 100644 --- a/kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala +++ b/kernel/src/main/scala/cats/kernel/BoundedSemilattice.scala @@ -4,7 +4,7 @@ import scala.{specialized => sp} trait BoundedSemilattice[@sp(Int, Long, Float, Double) A] extends Any with Semilattice[A] with CommutativeMonoid[A] -object BoundedSemilattice { +object BoundedSemilattice extends SemilatticeFunctions[BoundedSemilattice] { /** * Access an implicit `BoundedSemilattice[A]`. diff --git a/kernel/src/main/scala/cats/kernel/Eq.scala b/kernel/src/main/scala/cats/kernel/Eq.scala index c76f51ad28..5ae4ec752e 100644 --- a/kernel/src/main/scala/cats/kernel/Eq.scala +++ b/kernel/src/main/scala/cats/kernel/Eq.scala @@ -48,15 +48,17 @@ trait Eq[@sp A] extends Any with Serializable { self => } } -trait EqFunctions { - def eqv[@sp A](x: A, y: A)(implicit ev: Eq[A]): Boolean = +abstract class EqFunctions[E[T] <: Eq[T]] { + + def eqv[@sp A](x: A, y: A)(implicit ev: E[A]): Boolean = ev.eqv(x, y) - def neqv[@sp A](x: A, y: A)(implicit ev: Eq[A]): Boolean = + def neqv[@sp A](x: A, y: A)(implicit ev: E[A]): Boolean = ev.neqv(x, y) + } -object Eq extends EqFunctions { +object Eq extends EqFunctions[Eq] { /** * Access an implicit `Eq[A]`. @@ -120,6 +122,7 @@ object Eq extends EqFunctions { }) } } + /** * This is a monoid that creates an Eq that * checks that at least one equality check passes diff --git a/kernel/src/main/scala/cats/kernel/Group.scala b/kernel/src/main/scala/cats/kernel/Group.scala index 11776f69bd..bb4f7b3a50 100644 --- a/kernel/src/main/scala/cats/kernel/Group.scala +++ b/kernel/src/main/scala/cats/kernel/Group.scala @@ -45,9 +45,10 @@ trait Group[@sp(Int, Long, Float, Double) A] extends Any with Monoid[A] { } } -trait GroupFunctions[G[T] <: Group[T]] extends MonoidFunctions[Group] { +abstract class GroupFunctions[G[T] <: Group[T]] extends MonoidFunctions[Group] { def inverse[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: G[A]): A = ev.inverse(a) + def remove[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: G[A]): A = ev.remove(x, y) } diff --git a/kernel/src/main/scala/cats/kernel/Monoid.scala b/kernel/src/main/scala/cats/kernel/Monoid.scala index aa2cbaf317..0f3b58709f 100644 --- a/kernel/src/main/scala/cats/kernel/Monoid.scala +++ b/kernel/src/main/scala/cats/kernel/Monoid.scala @@ -18,7 +18,8 @@ trait Monoid[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] { /** * Tests if `a` is the identity. */ - def isEmpty(a: A)(implicit ev: Eq[A]) = ev.eqv(a, empty) + def isEmpty(a: A)(implicit ev: Eq[A]): Boolean = + ev.eqv(a, empty) /** * Return `a` appended to itself `n` times. @@ -35,10 +36,13 @@ trait Monoid[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] { as.foldLeft(empty)(combine) } -trait MonoidFunctions[M[T] <: Monoid[T]] extends SemigroupFunctions[M] { +abstract class MonoidFunctions[M[T] <: Monoid[T]] extends SemigroupFunctions[M] { def empty[@sp(Int, Long, Float, Double) A](implicit ev: M[A]): A = ev.empty + def isEmpty[@sp(Int, Long, Float, Double) A](a: A)(implicit m: M[A], ev: Eq[A]): Boolean = + m.isEmpty(a) + def combineAll[@sp(Int, Long, Float, Double) A](as: TraversableOnce[A])(implicit ev: M[A]): A = ev.combineAll(as) } diff --git a/kernel/src/main/scala/cats/kernel/Order.scala b/kernel/src/main/scala/cats/kernel/Order.scala index 53fcf6a931..e982c866dc 100644 --- a/kernel/src/main/scala/cats/kernel/Order.scala +++ b/kernel/src/main/scala/cats/kernel/Order.scala @@ -122,38 +122,27 @@ trait Order[@sp A] extends Any with PartialOrder[A] { self => } } -trait OrderFunctions { - def compare[@sp A](x: A, y: A)(implicit ev: Order[A]): Int = +abstract class OrderFunctions[O[T] <: Order[T]] extends PartialOrderFunctions[O] { + + def compare[@sp A](x: A, y: A)(implicit ev: O[A]): Int = ev.compare(x, y) - def eqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = - ev.eqv(x, y) - def neqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = - ev.neqv(x, y) - def gt[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = - ev.gt(x, y) - def gteqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = - ev.gteqv(x, y) - def lt[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = - ev.lt(x, y) - def lteqv[@sp A](x: A, y: A)(implicit ev: Order[A]): Boolean = - ev.lteqv(x, y) - - def min[@sp A](x: A, y: A)(implicit ev: Order[A]): A = + def min[@sp A](x: A, y: A)(implicit ev: O[A]): A = ev.min(x, y) - def max[@sp A](x: A, y: A)(implicit ev: Order[A]): A = + + def max[@sp A](x: A, y: A)(implicit ev: O[A]): A = ev.max(x, y) } object Order extends OrderFunctions { /** - * Access an implicit `Eq[A]`. + * Access an implicit `Order[A]`. */ @inline final def apply[A](implicit ev: Order[A]) = ev /** - * Convert an implicit `Order[A]` to an `Order[B]` using the given + * Convert an implicit `Order[B]` to an `Order[A]` using the given * function `f`. */ def by[@sp A, @sp B](f: A => B)(implicit ev: Order[B]): Order[A] = diff --git a/kernel/src/main/scala/cats/kernel/PartialOrder.scala b/kernel/src/main/scala/cats/kernel/PartialOrder.scala index e3adbe0f9a..334f661139 100644 --- a/kernel/src/main/scala/cats/kernel/PartialOrder.scala +++ b/kernel/src/main/scala/cats/kernel/PartialOrder.scala @@ -1,5 +1,6 @@ package cats.kernel +import java.lang.Double.isNaN import scala.{specialized => sp} /** @@ -43,7 +44,7 @@ trait PartialOrder[@sp A] extends Any with Eq[A] { self => */ def tryCompare(x: A, y: A): Option[Int] = { val c = partialCompare(x, y) - if (c.isNaN) None else Some(c.signum) + if (isNaN(c)) None else Some(c.signum) } /** @@ -111,40 +112,38 @@ trait PartialOrder[@sp A] extends Any with Eq[A] { self => def gt(x: A, y: A): Boolean = partialCompare(x, y) > 0 } -trait PartialOrderFunctions { +abstract class PartialOrderFunctions[P[T] <: PartialOrder[T]] extends EqFunctions[P] { - def partialCompare[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Double = + def partialCompare[@sp A](x: A, y: A)(implicit ev: P[A]): Double = ev.partialCompare(x, y) - def tryCompare[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Option[Int] = + def tryCompare[@sp A](x: A, y: A)(implicit ev: P[A]): Option[Int] = ev.tryCompare(x, y) - def pmin[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Option[A] = + def pmin[@sp A](x: A, y: A)(implicit ev: P[A]): Option[A] = ev.pmin(x, y) - def pmax[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Option[A] = + def pmax[@sp A](x: A, y: A)(implicit ev: P[A]): Option[A] = ev.pmax(x, y) - def eqv[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = - ev.eqv(x, y) - def lteqv[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + def lteqv[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean = ev.lteqv(x, y) - def lt[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + def lt[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean = ev.lt(x, y) - def gteqv[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + def gteqv[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean = ev.gteqv(x, y) - def gt[@sp A](x: A, y: A)(implicit ev: PartialOrder[A]): Boolean = + def gt[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean = ev.gt(x, y) } object PartialOrder extends PartialOrderFunctions { /** - * Access an implicit `Eq[A]`. + * Access an implicit `PartialOrder[A]`. */ @inline final def apply[A](implicit ev: PartialOrder[A]) = ev /** - * Convert an implicit `PartialOrder[A]` to an `PartialOrder[B]` - * using the given function `f`. + * Convert an implicit `PartialOrder[B]` to an `PartialOrder[A]` using the given + * function `f`. */ def by[@sp A, @sp B](f: A => B)(implicit ev: PartialOrder[B]): PartialOrder[A] = ev.on(f) diff --git a/kernel/src/main/scala/cats/kernel/Semigroup.scala b/kernel/src/main/scala/cats/kernel/Semigroup.scala index 8e17529793..736e5da2ad 100644 --- a/kernel/src/main/scala/cats/kernel/Semigroup.scala +++ b/kernel/src/main/scala/cats/kernel/Semigroup.scala @@ -41,7 +41,7 @@ trait Semigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable { as.reduceOption(combine) } -trait SemigroupFunctions[S[T] <: Semigroup[T]] { +abstract class SemigroupFunctions[S[T] <: Semigroup[T]] { def combine[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: S[A]): A = ev.combine(x, y) diff --git a/kernel/src/main/scala/cats/kernel/Semilattice.scala b/kernel/src/main/scala/cats/kernel/Semilattice.scala index 92cc80db86..77a69ff2d5 100644 --- a/kernel/src/main/scala/cats/kernel/Semilattice.scala +++ b/kernel/src/main/scala/cats/kernel/Semilattice.scala @@ -53,7 +53,14 @@ trait Semilattice[@sp(Int, Long, Float, Double) A] extends Any } } -object Semilattice { +abstract class SemilatticeFunctions[S[T] <: Semilattice[T]] extends SemigroupFunctions[S] { + def asMeetPartialOrder[A](implicit s: S[A], ev: Eq[A]): PartialOrder[A] = + s.asMeetPartialOrder(ev) + def asJoinPartialOrder[A](implicit s: S[A], ev: Eq[A]): PartialOrder[A] = + s.asJoinPartialOrder(ev) +} + +object Semilattice extends SemilatticeFunctions[Semilattice] { /** * Access an implicit `Semilattice[A]`.