From 2a6e52a566bb51c778c2af5218a5c5acb0ded35a Mon Sep 17 00:00:00 2001 From: Arya Irani Date: Tue, 5 Jan 2016 15:07:40 -0500 Subject: [PATCH 01/34] factor out a static inner method from #769 for better GC --- core/src/main/scala/cats/Eval.scala | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 93ec9297ee..dfe03f7d1e 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -223,20 +223,21 @@ object Eval extends EvalInstances { */ sealed abstract class Call[A](val thunk: () => Eval[A]) extends Eval[A] { def memoize: Eval[A] = new Later(() => value) - def value: A = { - def loop(fa: Eval[A]): Eval[A] = fa match { - case call: Eval.Call[A] => - loop(call.thunk()) - case compute: Eval.Compute[A] => - new Eval.Compute[A] { - type Start = compute.Start - val start: () => Eval[Start] = () => compute.start() - val run: Start => Eval[A] = s => loop(compute.run(s)) - } - case other => other - } - - loop(this).value + def value: A = Call.loop(this).value + } + + object Call { + /** Collapse the call stack for eager evaluations */ + private def loop[A](fa: Eval[A]): Eval[A] = fa match { + case call: Eval.Call[A] => + loop(call.thunk()) + case compute: Eval.Compute[A] => + new Eval.Compute[A] { + type Start = compute.Start + val start: () => Eval[Start] = () => compute.start() + val run: Start => Eval[A] = s => loop(compute.run(s)) + } + case other => other } } From 81a24d94d0d03c1e7dea0055d7620b519ecdbde2 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 6 Jan 2016 22:34:55 -0500 Subject: [PATCH 02/34] Use Eval instead of Trampoline for State This may provide the following benefits: 1) It should be a bit more efficient. 2) It removes the need to `import cats.std.function._` for some operations, which may make it a bit more newcomer friendly. 3) We are already using Eval for things like foldRight, so it seems more consistent to use it for `State`. 4) I think it's a bit easier to explain `Eval` than `Trampoline`, since it's hard to explain the latter without first explaining `Free`. So again, this may be a bit more newcomer friendly. --- core/src/main/scala/cats/state/StateT.scala | 6 ++---- core/src/main/scala/cats/state/package.scala | 4 +--- docs/src/main/tut/state.md | 11 +++++------ .../test/scala/cats/tests/StateTTests.scala | 18 +++++++++--------- .../test/scala/cats/tests/WordCountTest.scala | 2 +- 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/core/src/main/scala/cats/state/StateT.scala b/core/src/main/scala/cats/state/StateT.scala index 6f6ccce926..c713b346df 100644 --- a/core/src/main/scala/cats/state/StateT.scala +++ b/core/src/main/scala/cats/state/StateT.scala @@ -1,9 +1,7 @@ package cats package state -import cats.free.Trampoline import cats.data.Kleisli -import cats.std.function.function0Instance /** * `StateT[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S` @@ -145,7 +143,7 @@ private[state] sealed abstract class StateTInstances extends StateTInstances0 { private[state] sealed abstract class StateTInstances0 { implicit def stateMonadState[S]: MonadState[State[S, ?], S] = - StateT.stateTMonadState[Trampoline, S] + StateT.stateTMonadState[Eval, S] } // To workaround SI-7139 `object State` needs to be defined inside the package object @@ -153,7 +151,7 @@ private[state] sealed abstract class StateTInstances0 { private[state] abstract class StateFunctions { def apply[S, A](f: S => (S, A)): State[S, A] = - StateT.applyF(Trampoline.done((s: S) => Trampoline.done(f(s)))) + StateT.applyF(Now((s: S) => Now(f(s)))) /** * Return `a` and maintain the input state. diff --git a/core/src/main/scala/cats/state/package.scala b/core/src/main/scala/cats/state/package.scala index bba80c5f21..47eaf1ca70 100644 --- a/core/src/main/scala/cats/state/package.scala +++ b/core/src/main/scala/cats/state/package.scala @@ -1,8 +1,6 @@ package cats -import free.Trampoline - package object state { - type State[S, A] = StateT[Trampoline, S, A] + type State[S, A] = StateT[Eval, S, A] object State extends StateFunctions } diff --git a/docs/src/main/tut/state.md b/docs/src/main/tut/state.md index d7be7078bd..82616be992 100644 --- a/docs/src/main/tut/state.md +++ b/docs/src/main/tut/state.md @@ -132,7 +132,6 @@ Let's write a new version of `nextLong` using `State`: ```tut:silent import cats.state.State -import cats.std.function._ val nextLong: State[Seed, Long] = State(seed => (seed.next, seed.long)) @@ -159,16 +158,16 @@ val createRobot: State[Seed, Robot] = } yield Robot(id, sentient, name, model) ``` -At this point, we have not yet created a robot; we have written instructions for creating a robot. We need to pass in an initial seed value, and then we can call `run` to actually create the robot: +At this point, we have not yet created a robot; we have written instructions for creating a robot. We need to pass in an initial seed value, and then we can call `value` to actually create the robot: ```tut -val (finalState, robot) = createRobot.run(initialSeed).run +val (finalState, robot) = createRobot.run(initialSeed).value ``` If we only care about the robot and not the final state, then we can use `runA`: ```tut -val robot = createRobot.runA(initialSeed).run +val robot = createRobot.runA(initialSeed).value ``` The `createRobot` implementation reads much like the imperative code we initially wrote for the mutable RNG. However, this implementation is free of mutation and side-effects. Since this code is referentially transparent, we can perform the refactoring that we tried earlier without affecting the result: @@ -189,11 +188,11 @@ val createRobot: State[Seed, Robot] = { ``` ```tut -val robot = createRobot.runA(initialSeed).run +val robot = createRobot.runA(initialSeed).value ``` This may seem surprising, but keep in mind that `b` isn't simply a `Boolean`. It is a function that takes a seed and _returns_ a `Boolean`, threading state along the way. Since the seed that is being passed into `b` changes from line to line, so do the returned `Boolean` values. ## Fine print -TODO explain StateT and the fact that State is an alias for StateT with trampolining. +TODO explain StateT and the fact that State is an alias for StateT with Eval. diff --git a/tests/src/test/scala/cats/tests/StateTTests.scala b/tests/src/test/scala/cats/tests/StateTTests.scala index 12a640e90a..257f89e46e 100644 --- a/tests/src/test/scala/cats/tests/StateTTests.scala +++ b/tests/src/test/scala/cats/tests/StateTTests.scala @@ -3,40 +3,40 @@ package tests import cats.laws.discipline.{MonoidalTests, MonadStateTests, MonoidKTests, SerializableTests} import cats.state.{State, StateT} -import cats.tests.FreeTests._ import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Gen} class StateTTests extends CatsSuite { import StateTTests._ test("basic state usage"){ - add1.run(1).run should === (2 -> 1) + add1.run(1).value should === (2 -> 1) } test("traversing state is stack-safe"){ val ns = (0 to 100000).toList val x = ns.traverseU(_ => add1) - x.runS(0).run should === (100001) + x.runS(0).value should === (100001) } test("State.pure and StateT.pure are consistent"){ forAll { (s: String, i: Int) => val state: State[String, Int] = State.pure(i) val stateT: State[String, Int] = StateT.pure(i) - state.run(s).run should === (stateT.run(s).run) + state.run(s) should === (stateT.run(s)) } } test("Monoidal syntax is usable on State") { val x = add1 *> add1 - x.runS(0).run should === (2) + x.runS(0).value should === (2) } test("Singleton and instance inspect are consistent"){ forAll { (s: String, i: Int) => - State.inspect[Int, String](_.toString).run(i).run should === ( - State.pure[Int, Unit](()).inspect(_.toString).run(i).run) + State.inspect[Int, String](_.toString).run(i) should === ( + State.pure[Int, Unit](()).inspect(_.toString).run(i)) } } @@ -109,10 +109,10 @@ class StateTTests extends CatsSuite { object StateTTests extends StateTTestsInstances { implicit def stateEq[S:Eq:Arbitrary, A:Eq]: Eq[State[S, A]] = - stateTEq[free.Trampoline, S, A] + stateTEq[Eval, S, A] implicit def stateArbitrary[S: Arbitrary, A: Arbitrary]: Arbitrary[State[S, A]] = - stateTArbitrary[free.Trampoline, S, A] + stateTArbitrary[Eval, S, A] val add1: State[Int, Int] = State(n => (n + 1, n)) } diff --git a/tests/src/test/scala/cats/tests/WordCountTest.scala b/tests/src/test/scala/cats/tests/WordCountTest.scala index 174a23329d..8324349a38 100644 --- a/tests/src/test/scala/cats/tests/WordCountTest.scala +++ b/tests/src/test/scala/cats/tests/WordCountTest.scala @@ -43,7 +43,7 @@ class WordCountTest extends CatsSuite { val wordCountState = allResults.first.first val lineCount = allResults.first.second val charCount = allResults.second - val wordCount = wordCountState.runA(false).run + val wordCount = wordCountState.runA(false).value charCount.getConst should === (96) lineCount.getConst should === (2) wordCount.getConst should === (17) From 5f0324a91c4821e5268a865b1828b521493d04d8 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 6 Jan 2016 22:55:58 -0500 Subject: [PATCH 03/34] Add ScalaDoc example for foldK syntax --- core/src/main/scala/cats/syntax/foldable.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 7e165babae..d71a7628e7 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -16,5 +16,20 @@ trait FoldableSyntax extends Foldable.ToFoldableOps with FoldableSyntax1 { final class NestedFoldableOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Foldable[F]) { def sequence_(implicit G: Applicative[G]): G[Unit] = F.sequence_(fga) + + /** + * @see [[Foldable.foldK]]. + * + * Example: + * {{{ + * scala> import cats.std.list._ + * scala> import cats.std.set._ + * scala> import cats.syntax.foldable._ + * + * scala> val l: List[Set[Int]] = List(Set(1, 2), Set(2, 3), Set(3, 4)) + * scala> l.foldK + * res0: Set[Int] = Set(1, 2, 3, 4) + * }}} + */ def foldK(implicit G: MonoidK[G]): G[A] = F.foldK(fga) } From d338150c4c01985ba0a829f97033a3b83d14aee3 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 7 Jan 2016 07:46:03 -0500 Subject: [PATCH 04/34] Remove unused specialized State instance --- core/src/main/scala/cats/state/StateT.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/state/StateT.scala b/core/src/main/scala/cats/state/StateT.scala index c713b346df..5a41f7ea7f 100644 --- a/core/src/main/scala/cats/state/StateT.scala +++ b/core/src/main/scala/cats/state/StateT.scala @@ -123,7 +123,7 @@ object StateT extends StateTInstances { StateT(s => F.pure((s, a))) } -private[state] sealed abstract class StateTInstances extends StateTInstances0 { +private[state] sealed abstract class StateTInstances { implicit def stateTMonadState[F[_], S](implicit F: Monad[F]): MonadState[StateT[F, S, ?], S] = new MonadState[StateT[F, S, ?], S] { def pure[A](a: A): StateT[F, S, A] = @@ -141,11 +141,6 @@ private[state] sealed abstract class StateTInstances extends StateTInstances0 { } } -private[state] sealed abstract class StateTInstances0 { - implicit def stateMonadState[S]: MonadState[State[S, ?], S] = - StateT.stateTMonadState[Eval, S] -} - // To workaround SI-7139 `object State` needs to be defined inside the package object // together with the type alias. private[state] abstract class StateFunctions { From fc9a75b6cd490ebbe4f2213a2b1d7b25e4a792c4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 7 Jan 2016 08:19:44 -0500 Subject: [PATCH 05/34] Add ScalaDoc examples for Coproduct syntax Also add the missing `coproduct` syntax `object`. --- .../main/scala/cats/syntax/coproduct.scala | 36 +++++++++++++++++-- core/src/main/scala/cats/syntax/package.scala | 1 + 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/syntax/coproduct.scala b/core/src/main/scala/cats/syntax/coproduct.scala index 60043f9331..b4444dfd72 100644 --- a/core/src/main/scala/cats/syntax/coproduct.scala +++ b/core/src/main/scala/cats/syntax/coproduct.scala @@ -7,7 +7,37 @@ trait CoproductSyntax { implicit def coproductSyntax[F[_], A](a: F[A]): CoproductOps[F, A] = new CoproductOps(a) } -final class CoproductOps[F[_], A](val a: F[A]) extends AnyVal { - def leftc[G[_]]: Coproduct[F, G, A] = Coproduct.leftc(a) - def rightc[G[_]]: Coproduct[G, F, A] = Coproduct.rightc(a) +final class CoproductOps[F[_], A](val fa: F[A]) extends AnyVal { + + /** + * Lift an `F[A]` into a `Coproduct[F, G, A]` for any type constructor `G[_]`. + * + * @see [[rightc]] to swap the order of `F` and `G` in the result type. + * + * Example: + * {{{ + * scala> import cats.data.Coproduct + * scala> import cats.Eval + * scala> import cats.syntax.coproduct._ + * scala> List(1, 2, 3).leftc[Eval] + * res0: Coproduct[List, Eval, Int] = Coproduct(Left(List(1, 2, 3))) + * }}} + */ + def leftc[G[_]]: Coproduct[F, G, A] = Coproduct.leftc(fa) + + /** + * Lift an `F[A]` into a `Coproduct[G, F, A]` for any type constructor `G[_]`. + * + * @see [[leftc]] to swap the order of `F` and `G` in the result type. + * + * Example: + * {{{ + * scala> import cats.data.Coproduct + * scala> import cats.Eval + * scala> import cats.syntax.coproduct._ + * scala> List(1, 2, 3).rightc[Eval] + * res0: Coproduct[Eval, List, Int] = Coproduct(Right(List(1, 2, 3))) + * }}} + */ + def rightc[G[_]]: Coproduct[G, F, A] = Coproduct.rightc(fa) } diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index c61f8df9fe..fb1cc368e4 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -6,6 +6,7 @@ package object syntax { object monoidal extends MonoidalSyntax object bifunctor extends BifunctorSyntax object coflatMap extends CoflatMapSyntax + object coproduct extends CoproductSyntax object comonad extends ComonadSyntax object compose extends ComposeSyntax object contravariant extends ContravariantSyntax From 2c6abdf0b0cd7e563ce8fae014d00f6a70b57e05 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 8 Jan 2016 07:29:26 -0500 Subject: [PATCH 06/34] Add ScalaDoc for Either syntax --- core/src/main/scala/cats/syntax/either.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index a748020604..36c9c102d7 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -8,5 +8,23 @@ trait EitherSyntax { } final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { + + /** + * Convert a `scala.util.Either` into a [[cats.data.Xor]]. + * + * Example: + * {{{ + * scala> import cats.data.Xor + * scala> import cats.syntax.either._ + * + * scala> val i: Either[String, Int] = Right(3) + * scala> i.toXor + * res0: Xor[String, Int] = Right(3) + * + * scala> val s: Either[String, Int] = Left("error!") + * scala> s.toXor + * res0: Xor[String, Int] = Left(error!) + * }}} + */ def toXor: A Xor B = Xor.fromEither(eab) } From 05add1a5c5e704d2199fac2e2d09e2cbeb1a6215 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 8 Jan 2016 07:40:39 -0500 Subject: [PATCH 07/34] Add ScalaDoc for MonadCombine unite syntax --- core/src/main/scala/cats/syntax/monadCombine.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/main/scala/cats/syntax/monadCombine.scala b/core/src/main/scala/cats/syntax/monadCombine.scala index 9d912f7c19..8490b0de5b 100644 --- a/core/src/main/scala/cats/syntax/monadCombine.scala +++ b/core/src/main/scala/cats/syntax/monadCombine.scala @@ -8,5 +8,19 @@ trait MonadCombineSyntax { } final class NestedMonadCombineOps[F[_], G[_], A](fga: F[G[A]])(implicit F: MonadCombine[F]) { + + /** + * @see [[MonadCombine.unite]] + * + * Example: + * {{{ + * scala> import cats.data.Streaming + * scala> import cats.std.list._ + * scala> import cats.syntax.monadCombine._ + * scala> val x: List[Streaming[Int]] = List(Streaming(1, 2), Streaming(3, 4)) + * scala> x.unite + * res0: List[Int] = List(1, 2, 3, 4) + * }}} + */ def unite(implicit G: Foldable[G]): F[A] = F.unite(fga) } From 8e92896b75351a569e0b048c92d11b4afa4c6964 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sat, 9 Jan 2016 22:13:48 -0800 Subject: [PATCH 08/34] Add traverseU_ and sequenceU_ - Also remove unused type parameter in sequence_ --- core/src/main/scala/cats/Foldable.scala | 49 ++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 0db2e66d95..27c0695556 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -107,6 +107,31 @@ import simulacrum.typeclass G.map2(acc, f(a)) { (_, _) => () } } + /** + * Behaves like traverse_, but uses [[Unapply]] to find the + * [[Applicative]] instance for `G` - used when `G` is a + * type constructor with two or more parameters such as [[cats.data.Xor]] + * + * {{{ + * scala> import cats.data.Xor + * scala> import cats.std.list._ + * scala> def parseInt(s: String): Xor[String, Int] = + * | try { Xor.Right(s.toInt) } + * | catch { case _: NumberFormatException => Xor.Left("boo") } + * scala> val F = Foldable[List] + * scala> F.traverseU_(List("333", "444"))(parseInt) + * res0: Xor[String, Unit] = Right(()) + * scala> F.traverseU_(List("333", "zzz"))(parseInt) + * res1: Xor[String, Unit] = Left(boo) + * }}} + * + * Note that using `traverse_` instead of `traverseU_` would not compile without + * explicitly passing in the type parameters - the type checker has trouble + * inferring the appropriate instance. + */ + def traverseU_[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[Unit] = + traverse_(fa)(f.andThen(U.subst))(U.TC) + /** * Sequence `F[G[A]]` using `Applicative[G]`. * @@ -125,9 +150,31 @@ import simulacrum.typeclass * res1: Option[Unit] = None * }}} */ - def sequence_[G[_]: Applicative, A, B](fga: F[G[A]]): G[Unit] = + def sequence_[G[_]: Applicative, A](fga: F[G[A]]): G[Unit] = traverse_(fga)(identity) + /** + * Behaves like sequence_, but uses [[Unapply]] to find the + * [[Applicative]] instance for `G` - used when `G` is a + * type constructor with two or more parameters such as [[cats.data.Xor]] + * + * {{{ + * scala> import cats.data.Xor + * scala> import cats.std.list._ + * scala> val F = Foldable[List] + * scala> F.sequenceU_(List(Xor.right[String, Int](333), Xor.Right(444))) + * res0: Xor[String, Unit] = Right(()) + * scala> F.sequenceU_(List(Xor.right[String, Int](333), Xor.Left("boo"))) + * res1: Xor[String, Unit] = Left(boo) + * }}} + * + * Note that using `sequence_` instead of `sequenceU_` would not compile without + * explicitly passing in the type parameters - the type checker has trouble + * inferring the appropriate instance. + */ + def sequenceU_[GA](fa: F[GA])(implicit U: Unapply[Applicative, GA]): U.M[Unit] = + traverseU_(fa)(identity) + /** * Fold implemented using the given `MonoidK[G]` instance. * From 5d87e7096f406e9072e32723bf5402ba603af716 Mon Sep 17 00:00:00 2001 From: Luke Wyman Date: Mon, 11 Jan 2016 16:58:42 -0700 Subject: [PATCH 09/34] Added scaladoc comments to all the function definitions in cats/arrow/Arrow --- core/src/main/scala/cats/arrow/Arrow.scala | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/core/src/main/scala/cats/arrow/Arrow.scala b/core/src/main/scala/cats/arrow/Arrow.scala index bbf1b15c52..828d27acaa 100644 --- a/core/src/main/scala/cats/arrow/Arrow.scala +++ b/core/src/main/scala/cats/arrow/Arrow.scala @@ -5,11 +5,58 @@ import cats.functor.Strong trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self => + /** + * Lift a function into the context of an Arrow + */ def lift[A, B](f: A => B): F[A, B] + /** + * Create a new arrow from an existing arrow that applies `f` to the input + * of the original arrow and then applies `g` to the output. + * + * Example: + * {{{ + * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow + * scala> val fab: Double => Double = x => x + 0.3 + * scala> val f: Int => Double = x => x.toDouble / 2 + * scala> val g: Double => Double = x => x * 3 + * scala> val dimapArrow = Arrow[Function1].dimap(fab)(f)(g) + * scala> dimapArrow(3) + * res0: Double = 5.4 + * }}} + */ def dimap[A, B, C, D](fab: F[A, B])(f: C => A)(g: B => D): F[C, D] = compose(lift(g), andThen(lift(f), fab)) + /** + * Create a new arrow that takes two inputs, but only modifies the second input + * + * Example: + * {{{ + * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow + * scala> val f: Int => Int = _ * 2 + * scala> val fab = Arrow[Function1].first[Int,Int,Int](f) + * scala> fab((2,3)) + * res0: (Int, Int) = (4,3) + * }}} + */ + def first[A, B, C](fa: F[A, B]): F[(A, C), (B, C)] + + /** + * Create a new arrow that takes two inputs, but only modifies the second input + * + * Example: + * {{{ + * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow + * scala> val f: Int => Int = _ * 2 + * scala> val fab = Arrow[Function1].second[Int,Int,Int](f) + * scala> fab((2,3)) + * res0: (Int, Int) = (2,6) + * }}} + */ def second[A, B, C](fa: F[A, B]): F[(C, A), (C, B)] = { def swap[X, Y]: F[(X, Y), (Y, X)] = lift[(X, Y), (Y, X)] { case (x, y) => (y, x) } compose(swap, compose(first[A, B, C](fa), swap)) @@ -22,6 +69,7 @@ trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self => * Example: * {{{ * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow * scala> val toLong: Int => Long = _.toLong * scala> val toDouble: Float => Double = _.toDouble * scala> val f: ((Int, Float)) => (Long, Double) = Arrow[Function1].split(toLong, toDouble) From 815b8fb94246b480b1356a8199396b89f69e4e3e Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 12 Jan 2016 08:12:33 -0500 Subject: [PATCH 10/34] Fix StreamingT.filter bug As reported by @liff [here](https://gitter.im/non/cats?at=5694ce218fdd3c0c382e20f1). --- core/src/main/scala/cats/data/StreamingT.scala | 4 +++- tests/src/test/scala/cats/tests/StreamingTTests.scala | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 3c104fd194..214a0c8021 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -70,7 +70,9 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh */ def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case Cons(a, ft) => if (f(a)) this else Wait(ft.map(_.filter(f))) + case Cons(a, ft) => + val tail = ft.map(_.filter(f)) + if (f(a)) Cons(a, tail) else Wait(tail) case Wait(ft) => Wait(ft.map(_.filter(f))) case Empty() => this } diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 753bdae828..7140b9d496 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -78,6 +78,11 @@ class StreamingTTests extends CatsSuite { } } + test("filter - check regression") { + val s = StreamingT[Option, Int](1, 2, 1) + s.filter(_ > 1).toList should === (Some(List(2))) + } + test("foldLeft with Id consistent with List.foldLeft") { forAll { (s: StreamingT[Id, Int], l: Long, f: (Long, Int) => Long) => s.foldLeft(l)(f) should === (s.toList.foldLeft(l)(f)) From 61f148ae8f7426aab14b954d6266808dfac7e35a Mon Sep 17 00:00:00 2001 From: Luke Wyman Date: Tue, 12 Jan 2016 16:59:58 -0700 Subject: [PATCH 11/34] fixed a typo in scaladoc comment on cat/arrow/Arrow --- core/src/main/scala/cats/arrow/Arrow.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/arrow/Arrow.scala b/core/src/main/scala/cats/arrow/Arrow.scala index 828d27acaa..81820db3fd 100644 --- a/core/src/main/scala/cats/arrow/Arrow.scala +++ b/core/src/main/scala/cats/arrow/Arrow.scala @@ -30,7 +30,7 @@ trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self => compose(lift(g), andThen(lift(f), fab)) /** - * Create a new arrow that takes two inputs, but only modifies the second input + * Create a new arrow that takes two inputs, but only modifies the first input * * Example: * {{{ From 56dede8def4f906d3a060b947b55f86746b81e90 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 13 Jan 2016 18:08:14 -0500 Subject: [PATCH 12/34] Fix order of effects in FreeApplicative.foldMap Fixes #799 --- .../scala/cats/free/FreeApplicative.scala | 2 +- .../cats/tests/FreeApplicativeTests.scala | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/free/FreeApplicative.scala b/core/src/main/scala/cats/free/FreeApplicative.scala index 204815b0bb..92d68eef6a 100644 --- a/core/src/main/scala/cats/free/FreeApplicative.scala +++ b/core/src/main/scala/cats/free/FreeApplicative.scala @@ -30,7 +30,7 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable final def foldMap[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A] = this match { case Pure(a) => G.pure(a) - case Ap(pivot, fn) => G.ap(f(pivot))(fn.foldMap(f)) + case Ap(pivot, fn) => G.map2(f(pivot), fn.foldMap(f))((a, g) => g(a)) } /** Interpret/run the operations using the semantics of `Applicative[F]`. diff --git a/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala b/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala index aae6fdf5ca..3ac915abdd 100644 --- a/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala +++ b/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala @@ -4,8 +4,9 @@ package tests import cats.arrow.NaturalTransformation import cats.free.FreeApplicative import cats.laws.discipline.{MonoidalTests, ApplicativeTests, SerializableTests} -import cats.laws.discipline.eq.tuple3Eq +import cats.laws.discipline.eq.{tuple3Eq, tuple2Eq} import cats.data.Const +import cats.state.State import org.scalacheck.{Arbitrary, Gen} @@ -86,4 +87,45 @@ class FreeApplicativeTests extends CatsSuite { val fli2 = FreeApplicative.lift[List, Int](List.empty) fli2.analyze[G[Int]](countingNT) should ===(List(0)) } + + test("foldMap order of effects - regression check for #799") { + trait Foo[A] { + def getA: A + } + final case class Bar(getA: Int) extends Foo[Int] + final case class Baz(getA: Long) extends Foo[Long] + + type Dsl[A] = FreeApplicative[Foo, A] + + type Tracked[A] = State[String, A] + + val f: Foo ~> Tracked = new (Foo ~> Tracked) { + def apply[A](fa: Foo[A]): Tracked[A] = State[String, A]{ s0 => + (s0 + fa.toString + ";", fa.getA) + } + } + + val x: Dsl[Int] = FreeApplicative.lift(Bar(3)) + val y: Dsl[Long] = FreeApplicative.lift(Baz(5L)) + + val z1: Dsl[Long] = Apply[Dsl].map2(x, y)((x, y) => x.toLong + y) + val z2: Dsl[Long] = Apply[Dsl].map2(y, x)((y, x) => x.toLong + y) + + z1.foldMap(f).run("").value should === (("Bar(3);Baz(5);", 8L)) + z2.foldMap(f).run("").value should === (("Baz(5);Bar(3);", 8L)) + } + + test("analyze order of effects - regression check for #799") { + type Dsl[A] = FreeApplicative[Id, A] + val x: Dsl[String] = FreeApplicative.lift[Id, String]("x") + val y: Dsl[String] = FreeApplicative.lift[Id, String]("y") + + val z = Apply[Dsl].map2(x, y)((_, _) => ()) + + val asString: Id ~> λ[α => String] = new (Id ~> λ[α => String]) { + def apply[A](a: A): String = a.toString + } + + z.analyze(asString) should === ("xy") + } } From 95f0897578902e98c86154c3bd1316c4e59e1833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 21:28:54 +0100 Subject: [PATCH 13/34] Hide simulacrum dependency from the .pom --- build.sbt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9d80e4d9d1..17762d6d52 100644 --- a/build.sbt +++ b/build.sbt @@ -33,8 +33,10 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots") ), + ivyConfigurations += config("compileonly").hide, + unmanagedClasspath in Compile ++= update.value.select(configurationFilter("compileonly")), libraryDependencies ++= Seq( - "com.github.mpilquist" %%% "simulacrum" % "0.5.0", + "com.github.mpilquist" %%% "simulacrum" % "0.5.0" % "compileonly", "org.typelevel" %%% "machinist" % "0.4.1", compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") From 1b9d2f1939f4a2caf0e352c0947e55ec9bed09d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 21:49:04 +0100 Subject: [PATCH 14/34] Remove machinist dependency from cats-kernel --- build.sbt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 17762d6d52..2931578387 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,6 @@ lazy val commonSettings = Seq( unmanagedClasspath in Compile ++= update.value.select(configurationFilter("compileonly")), libraryDependencies ++= Seq( "com.github.mpilquist" %%% "simulacrum" % "0.5.0" % "compileonly", - "org.typelevel" %%% "machinist" % "0.4.1", compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), @@ -45,6 +44,10 @@ lazy val commonSettings = Seq( scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings") ) ++ warnUnusedImport +lazy val machinistDependencies = Seq( + libraryDependencies += "org.typelevel" %%% "machinist" % "0.4.1" +) + lazy val commonJsSettings = Seq( scalaJSStage in Global := FastOptStage, parallelExecution := false @@ -56,7 +59,9 @@ lazy val commonJvmSettings = Seq( // JVM settings. https://github.com/tkawachi/sbt-doctest/issues/52 ) ++ catsDoctestSettings -lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings +lazy val kernelSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings + +lazy val catsSettings = kernelSettings ++ machinistDependencies lazy val scalacheckVersion = "1.12.5" @@ -131,7 +136,7 @@ lazy val macrosJS = macros.js lazy val kernel = crossProject.crossType(CrossType.Pure) .settings(moduleName := "cats-kernel") - .settings(catsSettings:_*) + .settings(kernelSettings:_*) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) From 289484cb907f1290d9568e14b3d9e9a7a595ab15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 22:00:27 +0100 Subject: [PATCH 15/34] Use @typeclass annotation for Eq typeclass --- kernel/src/main/scala/cats/Eq.scala | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/kernel/src/main/scala/cats/Eq.scala b/kernel/src/main/scala/cats/Eq.scala index 2d4b034f5f..1bbbe61a1d 100644 --- a/kernel/src/main/scala/cats/Eq.scala +++ b/kernel/src/main/scala/cats/Eq.scala @@ -1,5 +1,7 @@ package cats +import simulacrum.typeclass + import scala.{specialized => sp} import scala.math.Equiv @@ -9,7 +11,7 @@ import scala.math.Equiv * 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 => +@typeclass trait Eq[@sp A] extends Any with Serializable { self => /** * Returns `true` if `x` and `y` are equivalent, `false` otherwise. @@ -58,11 +60,6 @@ trait EqFunctions { 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`. From b8c686adcb5e1270e2d5b3e7600761a961c5f273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 22:15:58 +0100 Subject: [PATCH 16/34] Add typeclass annotation to Semigroup and Monoid Question: is the apply method generated by simulacrum also @inline marked? --- kernel/src/main/scala/cats/Monoid.scala | 11 +++-------- kernel/src/main/scala/cats/Semigroup.scala | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/kernel/src/main/scala/cats/Monoid.scala b/kernel/src/main/scala/cats/Monoid.scala index 32f9f334b7..bf4232599a 100644 --- a/kernel/src/main/scala/cats/Monoid.scala +++ b/kernel/src/main/scala/cats/Monoid.scala @@ -1,5 +1,6 @@ package cats +import simulacrum.typeclass import scala.{ specialized => sp } /** @@ -8,7 +9,7 @@ import scala.{ specialized => sp } * `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] { +@typeclass trait Monoid[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] { /** * Return the identity element for this monoid. @@ -43,10 +44,4 @@ trait MonoidFunctions[M[T] <: Monoid[T]] extends SemigroupFunctions[M] { 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 -} +object Monoid extends MonoidFunctions[Monoid] diff --git a/kernel/src/main/scala/cats/Semigroup.scala b/kernel/src/main/scala/cats/Semigroup.scala index f59e90968a..b377a4b65b 100644 --- a/kernel/src/main/scala/cats/Semigroup.scala +++ b/kernel/src/main/scala/cats/Semigroup.scala @@ -1,12 +1,13 @@ package cats +import simulacrum.typeclass import scala.{ specialized => sp } import scala.annotation.{ switch, tailrec } /** * A semigroup is any set `A` with an associative operation (`combine`). */ -trait Semigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable { +@typeclass trait Semigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable { /** * Associative operation taking which combines two values. @@ -64,10 +65,4 @@ trait SemigroupFunctions[S[T] <: Semigroup[T]] { ev.combineAllOption(as) } -object Semigroup extends SemigroupFunctions[Semigroup] { - - /** - * Access an implicit `Semigroup[A]`. - */ - @inline final def apply[A](implicit ev: Semigroup[A]) = ev -} +object Semigroup extends SemigroupFunctions[Semigroup] From 66d485f059b7d6c21f2f5c59492725bc184b7556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 22:25:48 +0100 Subject: [PATCH 17/34] Add typeclass annotation to Group --- kernel/src/main/scala/cats/Group.scala | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/kernel/src/main/scala/cats/Group.scala b/kernel/src/main/scala/cats/Group.scala index b5db4eed7f..242dc155ab 100644 --- a/kernel/src/main/scala/cats/Group.scala +++ b/kernel/src/main/scala/cats/Group.scala @@ -1,11 +1,12 @@ package cats +import simulacrum.typeclass 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] { +@typeclass trait Group[@sp(Int, Long, Float, Double) A] extends Any with Monoid[A] { /** * Find the inverse of `a`. @@ -39,10 +40,4 @@ trait GroupFunctions[G[T] <: Group[T]] extends MonoidFunctions[Group] { 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 -} +object Group extends GroupFunctions[Group] From b33c9de93dbb441a0b6a360c7ab310462079861f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 22:34:07 +0100 Subject: [PATCH 18/34] Add typeclass annotation to Order and PartialOrder --- kernel/src/main/scala/cats/Order.scala | 8 ++------ kernel/src/main/scala/cats/PartialOrder.scala | 10 +++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/kernel/src/main/scala/cats/Order.scala b/kernel/src/main/scala/cats/Order.scala index 8af47bfb3e..1f957a2778 100644 --- a/kernel/src/main/scala/cats/Order.scala +++ b/kernel/src/main/scala/cats/Order.scala @@ -1,5 +1,6 @@ package cats +import simulacrum.typeclass import scala.{specialized => sp} /** @@ -19,7 +20,7 @@ import scala.{specialized => sp} * * By the totality law, x <= y and y <= x cannot be both false. */ -trait Order[@sp A] extends Any with PartialOrder[A] { self => +@typeclass trait Order[@sp A] extends Any with PartialOrder[A] { self => /** * Result of comparing `x` with `y`. Returns an Int whose sign is: @@ -147,11 +148,6 @@ trait OrderFunctions { 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`. diff --git a/kernel/src/main/scala/cats/PartialOrder.scala b/kernel/src/main/scala/cats/PartialOrder.scala index c35a1f9866..a5af151652 100644 --- a/kernel/src/main/scala/cats/PartialOrder.scala +++ b/kernel/src/main/scala/cats/PartialOrder.scala @@ -1,5 +1,6 @@ package cats +import simulacrum.typeclass import scala.{specialized => sp} /** @@ -21,7 +22,7 @@ import scala.{specialized => sp} * 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 => +@typeclass trait PartialOrder[@sp A] extends Any with Eq[A] { self => /** * Result of comparing `x` with `y`. Returns NaN if operands are not @@ -88,7 +89,7 @@ trait PartialOrder[@sp A] extends Any with Eq[A] { self => /** * Returns true if `x` = `y`, false otherwise. */ - def eqv(x: A, y: A): Boolean = partialCompare(x, y) == 0 + override def eqv(x: A, y: A): Boolean = partialCompare(x, y) == 0 /** * Returns true if `x` <= `y`, false otherwise. @@ -137,11 +138,6 @@ trait PartialOrderFunctions { 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`. From 0bba2900d87a86b1373140aa309686732acf04a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 22:39:54 +0100 Subject: [PATCH 19/34] Fixed another compile error due to missing override --- kernel/src/main/scala/cats/Order.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/main/scala/cats/Order.scala b/kernel/src/main/scala/cats/Order.scala index 1f957a2778..52d664e39c 100644 --- a/kernel/src/main/scala/cats/Order.scala +++ b/kernel/src/main/scala/cats/Order.scala @@ -30,7 +30,7 @@ import scala.{specialized => sp} */ def compare(x: A, y: A): Int - def partialCompare(x: A, y: A): Double = compare(x, y).toDouble + override def partialCompare(x: A, y: A): Double = compare(x, y).toDouble /** * If x <= y, return x, else return y. From 8cbf29f0c692e5e389bb879c7910761c4dae7d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 22:59:59 +0100 Subject: [PATCH 20/34] Remove reference to algebra in docs and adjust source paths --- docs/src/main/tut/monoid.md | 12 +----------- docs/src/main/tut/semigroup.md | 10 +--------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/docs/src/main/tut/monoid.md b/docs/src/main/tut/monoid.md index e9f9b6020f..b1b6fe1c9e 100644 --- a/docs/src/main/tut/monoid.md +++ b/docs/src/main/tut/monoid.md @@ -2,7 +2,7 @@ layout: default title: "Monoid" section: "typeclasses" -source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Monoid.scala" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Monoid.scala" --- # Monoid @@ -81,13 +81,3 @@ Thus. ```tut l.foldMap(i => (i, i.toString)) // do both of the above in one pass, hurrah! ``` - -------------------------------------------------------------------------------- - -N.B. -Cats does not define a `Monoid` type class itself, it uses the [`Monoid` -trait](https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Monoid.scala) -which is defined in the [algebra project](https://github.com/non/algebra) on -which it depends. The [`cats` package object](https://github.com/non/cats/blob/master/core/src/main/scala/cats/package.scala) -defines type aliases to the `Monoid` from algebra, so that you can -`import cats.Monoid`. diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md index 3dad3715cd..75d9bbd1ba 100644 --- a/docs/src/main/tut/semigroup.md +++ b/docs/src/main/tut/semigroup.md @@ -2,7 +2,7 @@ layout: default title: "Semigroup" section: "typeclasses" -source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Semigroup.scala" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Semigroup.scala" --- # Semigroup @@ -94,11 +94,3 @@ Option. If we try to use Some and None, we'll get errors: Some(1) |+| None None |+| Some(1) ``` - -N.B. -Cats does not define a `Semigroup` type class itself, it uses the [`Semigroup` -trait](https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Semigroup.scala) -which is defined in the [algebra project](https://github.com/non/algebra) on -which it depends. The [`cats` package object](https://github.com/non/cats/blob/master/core/src/main/scala/cats/package.scala) -defines type aliases to the `Semigroup` from algebra, so that you can -`import cats.Semigroup`. \ No newline at end of file From b868421b4c1fe4cf68690d28854afe0642245381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Thu, 14 Jan 2016 23:21:34 +0100 Subject: [PATCH 21/34] Added stubs for documentation of remaining cats-kernel typeclasses Stub is modified from show.md --- docs/src/main/tut/eq.md | 9 +++++++++ docs/src/main/tut/group.md | 9 +++++++++ docs/src/main/tut/order.md | 9 +++++++++ docs/src/main/tut/partialorder.md | 9 +++++++++ 4 files changed, 36 insertions(+) create mode 100644 docs/src/main/tut/eq.md create mode 100644 docs/src/main/tut/group.md create mode 100644 docs/src/main/tut/order.md create mode 100644 docs/src/main/tut/partialorder.md diff --git a/docs/src/main/tut/eq.md b/docs/src/main/tut/eq.md new file mode 100644 index 0000000000..61a580cb54 --- /dev/null +++ b/docs/src/main/tut/eq.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "Eq" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Eq.scala" +scaladoc: "#cats.Eq" +--- +# Eq + diff --git a/docs/src/main/tut/group.md b/docs/src/main/tut/group.md new file mode 100644 index 0000000000..043e27b3bb --- /dev/null +++ b/docs/src/main/tut/group.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "Group" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Group.scala" +scaladoc: "#cats.Group" +--- +# Group + diff --git a/docs/src/main/tut/order.md b/docs/src/main/tut/order.md new file mode 100644 index 0000000000..f79a9d295f --- /dev/null +++ b/docs/src/main/tut/order.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "Order" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Order.scala" +scaladoc: "#cats.Order" +--- +# Order + diff --git a/docs/src/main/tut/partialorder.md b/docs/src/main/tut/partialorder.md new file mode 100644 index 0000000000..8e0c9894cd --- /dev/null +++ b/docs/src/main/tut/partialorder.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "PartialOrder" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/PartialOrder.scala" +scaladoc: "#cats.PartialOrder" +--- +# PartialOrder + From 45c0be4f057c7e9868338367e4add1c94d9d04a7 Mon Sep 17 00:00:00 2001 From: Denis Mikhaylov Date: Fri, 15 Jan 2016 11:24:10 +0300 Subject: [PATCH 22/34] Add XorT#valueOr --- core/src/main/scala/cats/data/XorT.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index b50282cfe3..edf75190e2 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -47,6 +47,8 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { } }) + def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity) + def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.exists(f)) From 07d0cd1feaea225ba8674c985f9e5c546b5e2a29 Mon Sep 17 00:00:00 2001 From: Denis Mikhaylov Date: Fri, 15 Jan 2016 11:24:19 +0300 Subject: [PATCH 23/34] Add test for XorT#valueOr --- tests/src/test/scala/cats/tests/XorTTests.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index c24f1561ff..419036405f 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -153,6 +153,12 @@ class XorTTests extends CatsSuite { } } + test("valueOr with Id consistent with Xor valueOr") { + forAll { (xort: XorT[Id, String, Int], f: String => Int) => + xort.valueOr(f) should === (xort.value.valueOr(f)) + } + } + test("getOrElse with Id consistent with Xor getOrElse") { forAll { (xort: XorT[Id, String, Int], i: Int) => xort.getOrElse(i) should === (xort.value.getOrElse(i)) From d4f896a7bff1a9f93713d3452daf9cd50d670b60 Mon Sep 17 00:00:00 2001 From: Denis Mikhaylov Date: Fri, 15 Jan 2016 11:28:04 +0300 Subject: [PATCH 24/34] Prettify XorT#recoverWith --- core/src/main/scala/cats/data/XorT.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index b50282cfe3..4c68106153 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -40,11 +40,9 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { XorT(F.map(value)(_.recover(pf))) def recoverWith(pf: PartialFunction[A, XorT[F, A, B]])(implicit F: Monad[F]): XorT[F, A, B] = - XorT(F.flatMap(value) { xor => - xor match { - case Xor.Left(a) if pf.isDefinedAt(a) => pf(a).value - case _ => F.pure(xor) - } + XorT(F.flatMap(value) { + case Xor.Left(a) if pf.isDefinedAt(a) => pf(a).value + case other => F.pure(other) }) def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) From e0cd78ce451286e51f4d9f1e58ecb3cac411d2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Sat, 16 Jan 2016 17:03:55 +0100 Subject: [PATCH 25/34] Use "provided" instead of "compileonly" for simulacrum As suggested by @mpilquist sbt validate works locally. --- build.sbt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 2931578387..9d76d77c2b 100644 --- a/build.sbt +++ b/build.sbt @@ -33,10 +33,8 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots") ), - ivyConfigurations += config("compileonly").hide, - unmanagedClasspath in Compile ++= update.value.select(configurationFilter("compileonly")), libraryDependencies ++= Seq( - "com.github.mpilquist" %%% "simulacrum" % "0.5.0" % "compileonly", + "com.github.mpilquist" %%% "simulacrum" % "0.5.0" % "provided", compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), From bfa872745c7ca0e141610c9aa9674757a2ea39d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Sat, 16 Jan 2016 19:05:16 +0100 Subject: [PATCH 26/34] Remove "provided" dependencies This gets rid of the simulacrum dependency, as well as the strange and definitely unneeded scoverage dependencies, resulting in a nice and clean .pom Code taken from scodec-build. Thanks @mpilquist --- build.sbt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9d76d77c2b..08e03c4bbe 100644 --- a/build.sbt +++ b/build.sbt @@ -131,10 +131,19 @@ lazy val macros = crossProject.crossType(CrossType.Pure) lazy val macrosJVM = macros.jvm lazy val macrosJS = macros.js - lazy val kernel = crossProject.crossType(CrossType.Pure) .settings(moduleName := "cats-kernel") .settings(kernelSettings:_*) + .settings(pomPostProcess := { (node) => + import scala.xml._ + import scala.xml.transform._ + def stripIf(f: Node => Boolean) = new RewriteRule { + override def transform(n: Node) = + if (f(n)) NodeSeq.Empty else n + } + val stripProvidedScope = stripIf { n => n.label == "dependency" && (n \ "scope").text == "provided" } + new RuleTransformer(stripProvidedScope).transform(node)(0) + }) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) From 312afa7de918e678cbda7539b967985bbe0cb9b0 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Sun, 17 Jan 2016 18:01:44 -0600 Subject: [PATCH 27/34] Add ApplicativeError --- .../main/scala/cats/ApplicativeError.scala | 81 +++++++++++++++++++ core/src/main/scala/cats/MonadError.scala | 69 +--------------- core/src/main/scala/cats/data/Validated.scala | 11 ++- .../cats/laws/ApplicativeErrorLaws.scala | 44 ++++++++++ .../main/scala/cats/laws/MonadErrorLaws.scala | 34 +------- .../discipline/ApplicativeErrorTests.scala | 57 +++++++++++++ .../laws/discipline/MonadErrorTests.scala | 17 +--- .../scala/cats/tests/ValidatedTests.scala | 13 +-- 8 files changed, 204 insertions(+), 122 deletions(-) create mode 100644 core/src/main/scala/cats/ApplicativeError.scala create mode 100644 laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala new file mode 100644 index 0000000000..f575c7f703 --- /dev/null +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -0,0 +1,81 @@ +package cats + +import cats.data.{Xor, XorT} + +/** + * An applicative that also allows you to raise and or handle an error value. + * + * This type class allows one to abstract over error-handling applicatives. + */ +trait ApplicativeError[F[_], E] extends Applicative[F] { + /** + * Lift an error into the `F` context. + */ + def raiseError[A](e: E): F[A] + + /** + * Handle any error, potentially recovering from it, by mapping it to an + * `F[A]` value. + * + * @see [[handleError]] to handle any error by simply mapping it to an `A` + * value instead of an `F[A]`. + * + * @see [[recoverWith]] to recover from only certain errors. + */ + def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] + + /** + * Handle any error, by mapping it to an `A` value. + * + * @see [[handleErrorWith]] to map to an `F[A]` value instead of simply an + * `A` value. + * + * @see [[recover]] to only recover from certain errors. + */ + def handleError[A](fa: F[A])(f: E => A): F[A] = handleErrorWith(fa)(f andThen pure) + + /** + * Handle errors by turning them into [[cats.data.Xor.Left]] values. + * + * If there is no error, then an [[cats.data.Xor.Right]] value will be returned instead. + * + * All non-fatal errors should be handled by this method. + */ + def attempt[A](fa: F[A]): F[E Xor A] = handleErrorWith( + map(fa)(Xor.right[E, A]) + )(e => pure(Xor.left(e))) + + /** + * Similar to [[attempt]], but wraps the result in a [[cats.data.XorT]] for + * convenience. + */ + def attemptT[A](fa: F[A]): XorT[F, E, A] = XorT(attempt(fa)) + + /** + * Recover from certain errors by mapping them to an `A` value. + * + * @see [[handleError]] to handle any/all errors. + * + * @see [[recoverWith]] to recover from certain errors by mapping them to + * `F[A]` values. + */ + def recover[A](fa: F[A])(pf: PartialFunction[E, A]): F[A] = + handleErrorWith(fa)(e => + (pf andThen pure) applyOrElse(e, raiseError)) + + /** + * Recover from certain errors by mapping them to an `F[A]` value. + * + * @see [[handleErrorWith]] to handle any/all errors. + * + * @see [[recover]] to recover from certain errors by mapping them to `A` + * values. + */ + def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] = + handleErrorWith(fa)(e => + pf applyOrElse(e, raiseError)) +} + +object ApplicativeError { + def apply[F[_], E](implicit F: ApplicativeError[F, E]): ApplicativeError[F, E] = F +} diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 4541a44751..0284edcab5 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -7,74 +7,7 @@ import cats.data.{Xor, XorT} * * This type class allows one to abstract over error-handling monads. */ -trait MonadError[F[_], E] extends Monad[F] { - /** - * Lift an error into the `F` context. - */ - def raiseError[A](e: E): F[A] - - /** - * Handle any error, potentially recovering from it, by mapping it to an - * `F[A]` value. - * - * @see [[handleError]] to handle any error by simply mapping it to an `A` - * value instead of an `F[A]`. - * - * @see [[recoverWith]] to recover from only certain errors. - */ - def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] - - /** - * Handle any error, by mapping it to an `A` value. - * - * @see [[handleErrorWith]] to map to an `F[A]` value instead of simply an - * `A` value. - * - * @see [[recover]] to only recover from certain errors. - */ - def handleError[A](fa: F[A])(f: E => A): F[A] = handleErrorWith(fa)(f andThen pure) - - /** - * Handle errors by turning them into [[cats.data.Xor.Left]] values. - * - * If there is no error, then an [[cats.data.Xor.Right]] value will be returned instead. - * - * All non-fatal errors should be handled by this method. - */ - def attempt[A](fa: F[A]): F[E Xor A] = handleErrorWith( - map(fa)(Xor.right[E, A]) - )(e => pure(Xor.left(e))) - - /** - * Similar to [[attempt]], but wraps the result in a [[cats.data.XorT]] for - * convenience. - */ - def attemptT[A](fa: F[A]): XorT[F, E, A] = XorT(attempt(fa)) - - /** - * Recover from certain errors by mapping them to an `A` value. - * - * @see [[handleError]] to handle any/all errors. - * - * @see [[recoverWith]] to recover from certain errors by mapping them to - * `F[A]` values. - */ - def recover[A](fa: F[A])(pf: PartialFunction[E, A]): F[A] = - handleErrorWith(fa)(e => - (pf andThen pure) applyOrElse(e, raiseError)) - - /** - * Recover from certain errors by mapping them to an `F[A]` value. - * - * @see [[handleErrorWith]] to handle any/all errors. - * - * @see [[recover]] to recover from certain errors by mapping them to `A` - * values. - */ - def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] = - handleErrorWith(fa)(e => - pf applyOrElse(e, raiseError)) -} +trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] object MonadError { def apply[F[_], E](implicit F: MonadError[F, E]): MonadError[F, E] = F diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 1a5c06160f..c1259ec3c6 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -234,8 +234,8 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance override def leftMap[A, B, C](fab: Validated[A, B])(f: A => C): Validated[C, B] = fab.leftMap(f) } - implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with Applicative[Validated[E, ?]] = - new Traverse[Validated[E, ?]] with Applicative[Validated[E,?]] { + implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] = + new Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] { def traverse[F[_]: Applicative, A, B](fa: Validated[E,A])(f: A => F[B]): F[Validated[E,B]] = fa.traverse(f) @@ -256,6 +256,13 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = fa.product(fb)(E) + + def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = + fa match { + case Validated.Invalid(e) => f(e) + case v @ Validated.Valid(_) => v + } + def raiseError[A](e: E): Validated[E, A] = Validated.Invalid(e) } } diff --git a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala new file mode 100644 index 0000000000..b912534bf7 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala @@ -0,0 +1,44 @@ +package cats +package laws + +import cats.data.{Xor, XorT} + +// Taken from http://functorial.com/psc-pages/docs/Control/Monad/Error/Class/index.html +trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { + implicit override def F: ApplicativeError[F, E] + + def applicativeErrorHandleWith[A](e: E, f: E => F[A]): IsEq[F[A]] = + F.handleErrorWith(F.raiseError[A](e))(f) <-> f(e) + + def applicativeErrorHandle[A](e: E, f: E => A): IsEq[F[A]] = + F.handleError(F.raiseError[A](e))(f) <-> F.pure(f(e)) + + def handleErrorWithPure[A](a: A, f: E => F[A]): IsEq[F[A]] = + F.handleErrorWith(F.pure(a))(f) <-> F.pure(a) + + def handleErrorPure[A](a: A, f: E => A): IsEq[F[A]] = + F.handleError(F.pure(a))(f) <-> F.pure(a) + + def raiseErrorAttempt(e: E): IsEq[F[E Xor Unit]] = + F.attempt(F.raiseError[Unit](e)) <-> F.pure(Xor.left(e)) + + def pureAttempt[A](a: A): IsEq[F[E Xor A]] = + F.attempt(F.pure(a)) <-> F.pure(Xor.right(a)) + + def handleErrorWithConsistentWithRecoverWith[A](fa: F[A], f: E => F[A]): IsEq[F[A]] = + F.handleErrorWith(fa)(f) <-> F.recoverWith(fa)(PartialFunction(f)) + + def handleErrorConsistentWithRecover[A](fa: F[A], f: E => A): IsEq[F[A]] = + F.handleError(fa)(f) <-> F.recover(fa)(PartialFunction(f)) + + def recoverConsistentWithRecoverWith[A](fa: F[A], pf: PartialFunction[E, A]): IsEq[F[A]] = + F.recover(fa)(pf) <-> F.recoverWith(fa)(pf andThen F.pure) + + def attemptConsistentWithAttemptT[A](fa: F[A]): IsEq[XorT[F, E, A]] = + XorT(F.attempt(fa)) <-> F.attemptT(fa) +} + +object ApplicativeErrorLaws { + def apply[F[_], E](implicit ev: ApplicativeError[F, E]): ApplicativeErrorLaws[F, E] = + new ApplicativeErrorLaws[F, E] { def F: ApplicativeError[F, E] = ev } +} diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 3a2acbaf44..d1b129e3a0 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -1,44 +1,12 @@ package cats package laws -import cats.data.{Xor, XorT} - // Taken from http://functorial.com/psc-pages/docs/Control/Monad/Error/Class/index.html -trait MonadErrorLaws[F[_], E] extends MonadLaws[F] { +trait MonadErrorLaws[F[_], E] extends ApplicativeErrorLaws[F, E] with MonadLaws[F] { implicit override def F: MonadError[F, E] def monadErrorLeftZero[A, B](e: E, f: A => F[B]): IsEq[F[B]] = F.flatMap(F.raiseError[A](e))(f) <-> F.raiseError[B](e) - - def monadErrorHandleWith[A](e: E, f: E => F[A]): IsEq[F[A]] = - F.handleErrorWith(F.raiseError[A](e))(f) <-> f(e) - - def monadErrorHandle[A](e: E, f: E => A): IsEq[F[A]] = - F.handleError(F.raiseError[A](e))(f) <-> F.pure(f(e)) - - def handleErrorWithPure[A](a: A, f: E => F[A]): IsEq[F[A]] = - F.handleErrorWith(F.pure(a))(f) <-> F.pure(a) - - def handleErrorPure[A](a: A, f: E => A): IsEq[F[A]] = - F.handleError(F.pure(a))(f) <-> F.pure(a) - - def raiseErrorAttempt(e: E): IsEq[F[E Xor Unit]] = - F.attempt(F.raiseError[Unit](e)) <-> F.pure(Xor.left(e)) - - def pureAttempt[A](a: A): IsEq[F[E Xor A]] = - F.attempt(F.pure(a)) <-> F.pure(Xor.right(a)) - - def handleErrorWithConsistentWithRecoverWith[A](fa: F[A], f: E => F[A]): IsEq[F[A]] = - F.handleErrorWith(fa)(f) <-> F.recoverWith(fa)(PartialFunction(f)) - - def handleErrorConsistentWithRecover[A](fa: F[A], f: E => A): IsEq[F[A]] = - F.handleError(fa)(f) <-> F.recover(fa)(PartialFunction(f)) - - def recoverConsistentWithRecoverWith[A](fa: F[A], pf: PartialFunction[E, A]): IsEq[F[A]] = - F.recover(fa)(pf) <-> F.recoverWith(fa)(pf andThen F.pure) - - def attemptConsistentWithAttemptT[A](fa: F[A]): IsEq[XorT[F, E, A]] = - XorT(F.attempt(fa)) <-> F.attemptT(fa) } object MonadErrorLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala new file mode 100644 index 0000000000..581c25e3aa --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala @@ -0,0 +1,57 @@ +package cats +package laws +package discipline + +import cats.data.{ Xor, XorT } +import cats.laws.discipline.MonoidalTests.Isomorphisms +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq.unitEq +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop.forAll + +trait ApplicativeErrorTests[F[_], E] extends ApplicativeTests[F] { + def laws: ApplicativeErrorLaws[F, E] + + def applicativeError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + ArbE: Arbitrary[E], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqE: Eq[E], + EqFXorEU: Eq[F[E Xor Unit]], + EqFXorEA: Eq[F[E Xor A]], + EqXorTFEA: Eq[XorT[F, E, A]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + def name: String = "applicativeError" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "applicativeError handleWith" -> forAll(laws.applicativeErrorHandleWith[A] _), + "applicativeError handle" -> forAll(laws.applicativeErrorHandle[A] _), + "applicativeError handleErrorWith pure" -> forAll(laws.handleErrorWithPure[A] _), + "applicativeError handleError pure" -> forAll(laws.handleErrorPure[A] _), + "applicativeError raiseError attempt" -> forAll(laws.raiseErrorAttempt _), + "applicativeError pure attempt" -> forAll(laws.pureAttempt[A] _), + "applicativeError handleErrorWith consistent with recoverWith" -> forAll(laws.handleErrorWithConsistentWithRecoverWith[A] _), + "applicativeError handleError consistent with recover" -> forAll(laws.handleErrorConsistentWithRecover[A] _), + "applicativeError recover consistent with recoverWith" -> forAll(laws.recoverConsistentWithRecoverWith[A] _), + "applicativeError attempt consistent with attemptT" -> forAll(laws.attemptConsistentWithAttemptT[A] _) + ) + } + } +} + +object ApplicativeErrorTests { + def apply[F[_], E](implicit FE: ApplicativeError[F, E]): ApplicativeErrorTests[F, E] = + new ApplicativeErrorTests[F, E] { + def laws: ApplicativeErrorLaws[F, E] = ApplicativeErrorLaws[F, E] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 2b7ee10dfb..7714fac34e 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -4,12 +4,11 @@ package discipline import cats.data.{ Xor, XorT } import cats.laws.discipline.MonoidalTests.Isomorphisms -import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq.unitEq import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll -trait MonadErrorTests[F[_], E] extends MonadTests[F] { +trait MonadErrorTests[F[_], E] extends ApplicativeErrorTests[F, E] with MonadTests[F] { def laws: MonadErrorLaws[F, E] def monadError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -32,19 +31,9 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { new RuleSet { def name: String = "monadError" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(monad[A, B, C]) + def parents: Seq[RuleSet] = Seq(applicativeError[A, B, C], monad[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _), - "monadError handleWith" -> forAll(laws.monadErrorHandleWith[A] _), - "monadError handle" -> forAll(laws.monadErrorHandle[A] _), - "monadError handleErrorWith pure" -> forAll(laws.handleErrorWithPure[A] _), - "monadError handleError pure" -> forAll(laws.handleErrorPure[A] _), - "monadError raiseError attempt" -> forAll(laws.raiseErrorAttempt _), - "monadError pure attempt" -> forAll(laws.pureAttempt[A] _), - "monadError handleErrorWith consistent with recoverWith" -> forAll(laws.handleErrorWithConsistentWithRecoverWith[A] _), - "monadError handleError consistent with recover" -> forAll(laws.handleErrorConsistentWithRecover[A] _), - "monadError recover consistent with recoverWith" -> forAll(laws.recoverConsistentWithRecoverWith[A] _), - "monadError attempt consistent with attemptT" -> forAll(laws.attemptConsistentWithAttemptT[A] _) + "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _) ) } } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index ea575a8d86..683f774b1f 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,9 +1,9 @@ package cats package tests -import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor} +import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor, XorT} import cats.data.Validated.{Valid, Invalid} -import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests, MonoidalTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeErrorTests, SerializableTests, MonoidalTests} import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import cats.laws.discipline.arbitrary._ @@ -17,12 +17,15 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", MonoidalTests[Validated[String,?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Validated[String,?]]", SerializableTests.serializable(Monoidal[Validated[String,?]])) - checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) checkAll("Validated[?, ?]", BifunctorTests[Validated].bifunctor[Int, Int, Int, Int, Int, Int]) - checkAll("Applicative[Validated[String,?]]", SerializableTests.serializable(Applicative[Validated[String,?]])) + + implicit val eq0 = XorT.xorTEq[Validated[String, ?], String, Int] + + checkAll("Validated[String, Int]", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) + checkAll("ApplicativeError[Xor, String]", SerializableTests.serializable(ApplicativeError[Validated[String, ?], String])) checkAll("Validated[String, Int] with Option", TraverseTests[Validated[String,?]].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[Validated[String,?]]", SerializableTests.serializable(Traverse[Validated[String,?]])) + checkAll("Traverse[Validated[String, ?]]", SerializableTests.serializable(Traverse[Validated[String,?]])) checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) From 086495251013d76420f361de3d995d9eb0465b9e Mon Sep 17 00:00:00 2001 From: Juan Pedro Moreno Date: Tue, 19 Jan 2016 00:50:37 +0100 Subject: [PATCH 28/34] cats#813 - Adds CoflatMap type class to the Vector Instance --- core/src/main/scala/cats/std/vector.scala | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 88d9797ceb..c1d496eee6 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -4,9 +4,13 @@ package std import cats.data.Streaming import cats.syntax.show._ +import scala.annotation.tailrec +import scala.collection.+: +import scala.collection.mutable.{ArrayBuffer, ListBuffer} + trait VectorInstances { - implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] = - new Traverse[Vector] with MonadCombine[Vector] { + implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] = + new Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] { def empty[A]: Vector[A] = Vector.empty[A] @@ -23,6 +27,15 @@ trait VectorInstances { override def map2[A, B, Z](fa: Vector[A], fb: Vector[B])(f: (A, B) => Z): Vector[Z] = fa.flatMap(a => fb.map(b => f(a, b))) + def coflatMap[A, B](fa: Vector[A])(f: Vector[A] => B): Vector[B] = { + @tailrec def loop(buf: ArrayBuffer[B], as: Vector[A]): Vector[B] = + as match { + case _ +: rest => loop(buf += f(as), rest) + case _ => buf.to[Vector] + } + loop(ArrayBuffer.empty[B], fa) + } + def foldLeft[A, B](fa: Vector[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) From 5d8750642e0c22ccdf95c3ce963b42dccc0dd130 Mon Sep 17 00:00:00 2001 From: Juan Pedro Moreno Date: Tue, 19 Jan 2016 00:51:25 +0100 Subject: [PATCH 29/34] cats#813 - Provides CoflatMap tests for VectorTests --- tests/src/test/scala/cats/tests/VectorTests.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index 423e101a15..a15b6c2020 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -1,13 +1,17 @@ package cats package tests -import cats.laws.discipline.{MonadCombineTests, SerializableTests, TraverseTests, MonoidalTests} +import cats.laws.discipline.{MonadCombineTests, CoflatMapTests, SerializableTests, TraverseTests, MonoidalTests} import cats.laws.discipline.eq.tuple3Eq class VectorTests extends CatsSuite { + checkAll("Vector[Int]", MonoidalTests[Vector].monoidal[Int, Int, Int]) checkAll("Monoidal[Vector]", SerializableTests.serializable(Monoidal[Vector])) + checkAll("Vector[Int]", CoflatMapTests[Vector].coflatMap[Int, Int, Int]) + checkAll("CoflatMap[Vector]", SerializableTests.serializable(CoflatMap[Vector])) + checkAll("Vector[Int]", MonadCombineTests[Vector].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Vector]", SerializableTests.serializable(MonadCombine[Vector])) From 4da172f83ba00973a828be145bdf62ca02c9b8a8 Mon Sep 17 00:00:00 2001 From: Juan Pedro Moreno Date: Tue, 19 Jan 2016 11:51:58 +0100 Subject: [PATCH 30/34] cats#813 - Removes non-necessary import --- core/src/main/scala/cats/std/vector.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index c1d496eee6..28df6c2946 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -6,7 +6,7 @@ import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.+: -import scala.collection.mutable.{ArrayBuffer, ListBuffer} +import scala.collection.mutable.ArrayBuffer trait VectorInstances { implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] = From c20f6ed73d9df6500224823a447e09731d861a48 Mon Sep 17 00:00:00 2001 From: Juan Pedro Moreno Date: Tue, 19 Jan 2016 15:53:37 +0100 Subject: [PATCH 31/34] cats#813 - Changes the coflatMap implementation in order to use ListBuffer instead of ArrayBuffer --- core/src/main/scala/cats/std/vector.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 28df6c2946..b7cb2ba76b 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -6,7 +6,7 @@ import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.+: -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.ListBuffer trait VectorInstances { implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] = @@ -28,12 +28,12 @@ trait VectorInstances { fa.flatMap(a => fb.map(b => f(a, b))) def coflatMap[A, B](fa: Vector[A])(f: Vector[A] => B): Vector[B] = { - @tailrec def loop(buf: ArrayBuffer[B], as: Vector[A]): Vector[B] = + @tailrec def loop(buf: ListBuffer[B], as: Vector[A]): Vector[B] = as match { case _ +: rest => loop(buf += f(as), rest) case _ => buf.to[Vector] } - loop(ArrayBuffer.empty[B], fa) + loop(ListBuffer.empty[B], fa) } def foldLeft[A, B](fa: Vector[A], b: B)(f: (B, A) => B): B = From 7ee5f2fe56879beffa0ffb868f5fd62b6f4d858b Mon Sep 17 00:00:00 2001 From: Juan Pedro Moreno Date: Tue, 19 Jan 2016 19:55:10 +0100 Subject: [PATCH 32/34] cats#813 - Addresses a code review comment, changing the coflatMap implementation in order to use VectorBuilder instead of ListBuffer --- core/src/main/scala/cats/std/vector.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index b7cb2ba76b..bb32ea8988 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -6,7 +6,7 @@ import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.+: -import scala.collection.mutable.ListBuffer +import scala.collection.immutable.VectorBuilder trait VectorInstances { implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] = @@ -28,12 +28,12 @@ trait VectorInstances { fa.flatMap(a => fb.map(b => f(a, b))) def coflatMap[A, B](fa: Vector[A])(f: Vector[A] => B): Vector[B] = { - @tailrec def loop(buf: ListBuffer[B], as: Vector[A]): Vector[B] = + @tailrec def loop(builder: VectorBuilder[B], as: Vector[A]): Vector[B] = as match { - case _ +: rest => loop(buf += f(as), rest) - case _ => buf.to[Vector] + case _ +: rest => loop(builder += f(as), rest) + case _ => builder.result() } - loop(ListBuffer.empty[B], fa) + loop(new VectorBuilder[B], fa) } def foldLeft[A, B](fa: Vector[A], b: B)(f: (B, A) => B): B = From 4c714fdcda9b1a4435cf056009b21f3289a1b572 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Tue, 19 Jan 2016 20:53:50 +0100 Subject: [PATCH 33/34] Update list of authors --- AUTHORS.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index 9dbc1073ff..adf5005c64 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -14,47 +14,66 @@ possible: * Adelbert Chang * Alessandro Lacava + * Alexey Levan * Alissa Pajer * Alistair Johnson * Amir Mohammad Saied * Andrew Jones + * Antoine Comte * Arya Irani + * Ash Pook * Benjamin Thuillier - * Bobby + * Bobby Rauchenberg * Brendan McAdams * Cody Allen * Colt Frederickson * Dale Wijnand * Dave Rostron * David Allsopp + * David Gregory + * Denis Mikhaylov * Derek Wickern * Edmund Noble + * Erik LaBianca * Erik Osheim * Eugene Burmako * Eugene Yokota * Feynman Liang * Frank S. Thomas + * Jean-Rémi Desjardins * Jisoo Park * Josh Marcus + * Julien Richard-Foy * Julien Truffaut * Kenji Yoshida + * Long Cao * Luis Angel Vicente Sanchez + * Luke Wyman * Marc Siegel + * Markus Hauck + * Matthias Lüneberg * Michael Pilquist * Mike Curry * Miles Sabin + * Olli Helenius * Owen Parry * Pascal Voitot + * Paul Phillips * Philip Wills + * Raúl Raja Martínez * Rintcius Blok * Rob Norris * Romain Ruetschi * Ross A. Baker + * Sarunas Valaskevicius + * Shunsuke Otani * Sinisa Louc * Stephen Judkins * Stew O'Connor + * Sumedh Mungee * Travis Brown * Wedens + * Yosef Fertel * Zach Abbott We've tried to include everyone, but if you've made a contribution to From f3c4ebaf8c42c32b341619f303bed9112817024b Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 20 Jan 2016 09:02:32 -0500 Subject: [PATCH 34/34] Upgrade to simulacrum 0.6.1, which makes type classes universal and serializable by default --- build.sbt | 2 +- core/src/main/scala/cats/Foldable.scala | 2 +- core/src/main/scala/cats/SemigroupK.scala | 2 +- core/src/main/scala/cats/Show.scala | 2 +- core/src/main/scala/cats/functor/Invariant.scala | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 355259364e..8ca3ec076a 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,7 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("snapshots") ), libraryDependencies ++= Seq( - "com.github.mpilquist" %%% "simulacrum" % "0.5.0", + "com.github.mpilquist" %%% "simulacrum" % "0.6.1", "org.spire-math" %%% "algebra" % "0.3.1", "org.spire-math" %%% "algebra-std" % "0.3.1", "org.typelevel" %%% "machinist" % "0.4.1", diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 27c0695556..63bc0a0d33 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -24,7 +24,7 @@ import simulacrum.typeclass * * See: [[http://www.cs.nott.ac.uk/~pszgmh/fold.pdf A tutorial on the universality and expressiveness of fold]] */ -@typeclass trait Foldable[F[_]] extends Serializable { self => +@typeclass trait Foldable[F[_]] { self => /** * Left associative fold on 'F' using the function 'f'. diff --git a/core/src/main/scala/cats/SemigroupK.scala b/core/src/main/scala/cats/SemigroupK.scala index adfdb3504b..9313ffbaf5 100644 --- a/core/src/main/scala/cats/SemigroupK.scala +++ b/core/src/main/scala/cats/SemigroupK.scala @@ -20,7 +20,7 @@ import simulacrum.{op, typeclass} * The combination operation just depends on the structure of F, * but not the structure of A. */ -@typeclass trait SemigroupK[F[_]] extends Serializable { self => +@typeclass trait SemigroupK[F[_]] { self => /** * Combine two F[A] values. diff --git a/core/src/main/scala/cats/Show.scala b/core/src/main/scala/cats/Show.scala index aa1fd19f3a..214ad82ceb 100644 --- a/core/src/main/scala/cats/Show.scala +++ b/core/src/main/scala/cats/Show.scala @@ -10,7 +10,7 @@ import cats.functor.Contravariant * made a toString method, a Show instance will only exist if someone * explicitly provided one. */ -@typeclass trait Show[T] extends Serializable { +@typeclass trait Show[T] { def show(f: T): String } diff --git a/core/src/main/scala/cats/functor/Invariant.scala b/core/src/main/scala/cats/functor/Invariant.scala index b8a7788c57..ac2653f7b5 100644 --- a/core/src/main/scala/cats/functor/Invariant.scala +++ b/core/src/main/scala/cats/functor/Invariant.scala @@ -6,7 +6,7 @@ import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.InvariantLaws. */ -@typeclass trait Invariant[F[_]] extends Any with Serializable { self => +@typeclass trait Invariant[F[_]] { self => def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] /**