-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
This commit make somse changes to some of our basic ops: #368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package cats | ||
| package syntax | ||
|
|
||
| import cats.macros.Ops | ||
|
|
||
| trait GroupSyntax { | ||
| // TODO: use simulacrum instances eventually | ||
| implicit def groupSyntax[A: Group](a: A): GroupOps[A] = | ||
| new GroupOps[A](a) | ||
| } | ||
|
|
||
| class GroupOps[A: Group](lhs: A) { | ||
| def |-|(rhs: A): A = macro Ops.binop[A, A] | ||
| def remove(rhs: A): A = macro Ops.binop[A, A] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,16 @@ | ||
| package cats | ||
| package syntax | ||
|
|
||
| import cats.macros.Ops | ||
|
|
||
| trait SemigroupSyntax { | ||
| // TODO: use simulacrum instances eventually | ||
| implicit def semigroupSyntax[A: Semigroup](a: A): SemigroupOps[A] = | ||
| new SemigroupOps[A](a) | ||
| } | ||
|
|
||
| class SemigroupOps[A](lhs: A)(implicit A: Semigroup[A]) { | ||
| def |+|(rhs: A): A = A.combine(lhs, rhs) | ||
| def combine(rhs: A): A = A.combine(lhs, rhs) | ||
| def combineN(rhs: Int): A = A.combineN(lhs, rhs) | ||
| class SemigroupOps[A: Semigroup](lhs: A) { | ||
| def |+|(rhs: A): A = macro Ops.binop[A, A] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I still don't have a good picture of what Cats is doing with Simulacrum and/or Machinist. What's the advantage of using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider the following generic code using semigroup: Say I pass it a So it is an efficiency improvement that removes the penalty of using Ops implicits rather than explicitly threading and using the type class instances.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @non thanks. That makes sense. What's to prevent us from doing this in other places, such as in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing -- I'd be happy to add this everywhere. I wanted to add it here since these are the places where the cost of the actual work (e.g. integer addition) is so small that the overhead would be particularly unfortunate (although we will still do better than Scalaz where we would be allocating a by-name parameter every time). |
||
| def combine(rhs: A): A = macro Ops.binop[A, A] | ||
| def combineN(rhs: Int): A = macro Ops.binop[A, A] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| package cats | ||
| package tests | ||
|
|
||
| import algebra.laws.GroupLaws | ||
| import cats.functor.{Invariant, Contravariant} | ||
| import cats.laws.discipline.SerializableTests | ||
|
|
||
| import org.scalacheck.{Arbitrary} | ||
| import org.scalatest.prop.PropertyChecks | ||
| import scala.reflect.runtime.universe.TypeTag | ||
|
|
||
| /** | ||
| * Test that our syntax implicits are working. | ||
| * | ||
| * Each method should correspond to one type class worth of syntax. | ||
| * Ideally, we should be testing every operator or method that we | ||
| * expect to add to generic parameters. This file is a safeguard | ||
| * against accidentally breaking (or removing) syntax which was | ||
| * otherwise untested. | ||
| * | ||
| * The strategy here is to create "mock" values of particular types, | ||
| * and then ensure that the syntax we want is available. We never plan | ||
| * to run any of these methods, so we don't need real values. All | ||
| * values in the methods should be generic -- we rely on parametricity | ||
| * to guarantee that the syntax will be available for any type with | ||
| * the proper type class instance(s). | ||
| * | ||
| * None of these tests should ever run, or do any runtime checks. | ||
| */ | ||
| class SyntaxTests extends CatsSuite with PropertyChecks { | ||
|
|
||
| // pretend we have a value of type A | ||
| def mock[A]: A = ??? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this is well known or not, but I wonder if using
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dwijnand yeah I've run into that before as well. If it start causing trouble we can always switch it over to another hack :) |
||
|
|
||
| def testSemigroup[A: Semigroup]: Unit = { | ||
| val x = mock[A] | ||
| val y = mock[A] | ||
| val z: A = x |+| y | ||
| } | ||
|
|
||
| def testGroup[A: Group](x: A, y: A): Unit = { | ||
| val x = mock[A] | ||
| val y = mock[A] | ||
| val z: A = x |-| y | ||
| } | ||
|
|
||
| def testEq[A: Eq]: Unit = { | ||
| val x = mock[A] | ||
| val y = mock[A] | ||
| val b0: Boolean = x === y | ||
| val b1: Boolean = x =!= y | ||
| } | ||
|
|
||
| def testPartialOrder[A: PartialOrder]: Unit = { | ||
| val x = mock[A] | ||
| val y = mock[A] | ||
| val b0: Boolean = x < y | ||
| val b1: Boolean = x <= y | ||
| val b2: Boolean = x > y | ||
| val b3: Boolean = x >= y | ||
| val f: Double = x partialCompare y | ||
| val oi: Option[Int] = x tryCompare y | ||
| val oz0: Option[A] = x pmin y | ||
| val oz1: Option[A] = x pmax y | ||
| } | ||
|
|
||
| def testOrder[A: Order]: Unit = { | ||
| val x = mock[A] | ||
| val y = mock[A] | ||
| val i: Int = x compare y | ||
| val z0: A = x min y | ||
| val z1: A = x max y | ||
| } | ||
|
|
||
| def testInvariantFunctor[F[_]: Invariant, A, B]: Unit = { | ||
| val fa = mock[F[A]] | ||
| val f = mock[A => B] | ||
| val g = mock[B => A] | ||
| val fb: F[B] = fa.imap(f)(g) | ||
| } | ||
|
|
||
| def testInvariantFunctor[F[_]: Contravariant, A, B]: Unit = { | ||
| val fa = mock[F[A]] | ||
| val f = mock[B => A] | ||
| val fb: F[B] = fa.contramap(f) | ||
| } | ||
|
|
||
| def testFoldable[F[_]: Foldable, G[_]: Applicative: MonoidK, A: Monoid, B, Z]: Unit = { | ||
| val fa = mock[F[A]] | ||
| val b = mock[B] | ||
| val f1 = mock[(B, A) => B] | ||
| val b0: B = fa.foldLeft(b)(f1) | ||
| val a0: A = fa.fold | ||
|
|
||
| val f2 = mock[A => Fold[B]] | ||
| val lb0: Lazy[B] = fa.foldRight(Lazy(b))(f2) | ||
|
|
||
| val fz = mock[F[Z]] | ||
| val f3 = mock[Z => A] | ||
| val a1: A = fz.foldMap(f3) | ||
|
|
||
| val f4 = mock[A => G[B]] | ||
| val gu0: G[Unit] = fa.traverse_(f4) | ||
|
|
||
| val fga = mock[F[G[A]]] | ||
| val gu1: G[Unit] = fga.sequence_ | ||
| val ga: G[A] = fga.foldK | ||
|
|
||
| val f5 = mock[A => Boolean] | ||
| val oa: Option[A] = fa.find(f5) | ||
|
|
||
| val as0: List[A] = fa.toList | ||
| val as1: List[A] = fa.filter_(f5) | ||
| val as2: List[A] = fa.dropWhile_(f5) | ||
| } | ||
|
|
||
| def testReducible[F[_]: Reducible, G[_]: Apply: SemigroupK, A: Semigroup, B, Z]: Unit = { | ||
| val fa = mock[F[A]] | ||
| val f1 = mock[(A, A) => A] | ||
| val a1: A = fa.reduceLeft(f1) | ||
|
|
||
| val f2 = mock[A => Fold[A]] | ||
| val la: Lazy[A] = fa.reduceRight(f2) | ||
|
|
||
| val a2: A = fa.reduce | ||
|
|
||
| val fga = mock[F[G[A]]] | ||
| val ga: G[A] = fga.reduceK | ||
|
|
||
| val fz = mock[F[Z]] | ||
| val f3 = mock[Z => A] | ||
| val a3: A = fz.reduceMap(f3) | ||
|
|
||
| val f4 = mock[A => B] | ||
| val f5 = mock[(B, A) => B] | ||
| val b1: B = fa.reduceLeftTo(f4)(f5) | ||
|
|
||
| val f6 = mock[A => Fold[B]] | ||
| val lb: Lazy[B] = fa.reduceRightTo(f4)(f6) | ||
|
|
||
| val f7 = mock[A => G[B]] | ||
| val gu1: G[Unit] = fa.traverse1_(f7) | ||
|
|
||
| val gu2: G[Unit] = fga.sequence1_ | ||
| } | ||
|
|
||
| def testFunctor[F[_]: Functor, A, B]: Unit = { | ||
| val fa = mock[F[A]] | ||
| val f = mock[A => B] | ||
| val fb0: F[B] = fa.map(f) | ||
| val fu: F[Unit] = fa.void | ||
| val fab: F[(A, B)] = fa.fproduct(f) | ||
|
|
||
| val b = mock[B] | ||
| val fb1: F[B] = fa.as(b) | ||
| } | ||
|
|
||
| def testApply[F[_]: Apply, A, B, C, D, Z]: Unit = { | ||
| val fa = mock[F[A]] | ||
| val fab = mock[F[A => B]] | ||
| val fb0: F[B] = fa.ap(fab) | ||
|
|
||
| val fb = mock[F[B]] | ||
| val fabz = mock[F[(A, B) => Z]] | ||
| val fz0: F[Z] = fa.ap2(fb)(fabz) | ||
|
|
||
| val f = mock[(A, B) => Z] | ||
| val fz1: F[Z] = fa.map2(fb)(f) | ||
|
|
||
| val f1 = mock[(A, B) => Z] | ||
| val ff1 = mock[F[(A, B) => Z]] | ||
| val fz2: F[Z] = (fa |@| fb).map(f1) | ||
| val fz3: F[Z] = (fa |@| fb).ap(ff1) | ||
|
|
||
| val fc = mock[F[C]] | ||
| val f2 = mock[(A, B, C) => Z] | ||
| val ff2 = mock[F[(A, B, C) => Z]] | ||
| val fz4: F[Z] = (fa |@| fb |@| fc).map(f2) | ||
| val fz5: F[Z] = (fa |@| fb |@| fc).ap(ff2) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be adding any unit tests along with this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! I'll add some law-checking code here. Good catch :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note -- the syntax tests I ended up writing found several omissions. So thanks for suggesting I add tests! 🐈